找回密码
 立即注册
首页 业界区 业界 【EF Core】“DB First”方案下用编程方式生成数据库模 ...

【EF Core】“DB First”方案下用编程方式生成数据库模型代码

茅香馨 昨天 12:40
大伙伴们只要学过三天 EF Core 一定知道,.NET SDK 有一个 dotnet-ef 工具(需要安装),可以用来创建/迁移数据库、生成模型代码、优化模型和查询代码等。必要时还能生一个单独的 exe,可以运行它来更新数据库结构。
不过,按照官方的设计思路,肯定不会把所有功能都堆在 exe 项目中的,这不,dotnet-ef 只是做个封装,可以通过命令行执行罢了,其实核心功能是写在 Design 包里面(Nuget 包名:Microsoft.EntityFrameworkCore.Design)。于是,咱们可以开发自己的 EF 辅助工具。比如,你可以把命令行操作的功能搞成窗口图形化操作。当然,这些功能仅限开发者使用,用户一般不需要(不一般的用户除外)。
如此,在 DB First 方案(先有数据库)中,咱们可以把生成实体类以及 dbContext 类的功能直接写到项目代码中,然后加上一个条件编译,在需要生成代码时开启一下编译符号,运行一个项目就能生成实体模型了。其他情况下把条件编译符号注释掉就可以。
这个功能就有点像 Sugar 的玩法。老周在某些项目中就是这么干的。不过老周更喜欢 EF,理由有:
1、EF Core 更灵活。
2、EF Core 的表达树翻译功能比 Sugar 完善,功能更多。
3、有官方支持的优先用原则,没有才考虑第三方。
好了,不扯废话了,咱们开始!
一、基础知识

首先,咱们要明确功能:数据库已经有了,可能是你创建的,可能是别人创建的。很多团队都会把搞数据库,写存储过程的单独一堆人去干,然后,项目的非数据库部分另一堆人去做。所以,小型数据库才考虑用 EF Core 去创建,复杂的数据库还是先创建数据库好一些。咱们要做的就是根据现有的数据库和表,直接生成实体类和 DbContext 的派生类
在分析思路之前,既然大伙儿都是玩 .NET 的,那就坚守这个原则:处处都是服务容器和依赖注入
好,有这个思想准备,咱们才能讲知识点。咱们来认识几个新朋友,熟悉一下,以后才能好好利用他们,嗯,朋友是拿来利用的。
第一位,本名 IDatabaseModelFactory。他的绝活本领是爆库。你要从数据库生成实体,那你得知道数据库里有哪些表,表中有哪些列,哪个是主键,列的类型是什么……没事,这些信息交给这位朋友就行。
调用以下方法,你能得到一个 DatabaseModel 对象。
  1. DatabaseModel Create(string connectionString, DatabaseModelFactoryOptions options);
复制代码
第一个参数就是连接字符串,这个不用介绍了吧。要爆库你得知道库在哪里吧。第二个参数是选项类,用来配置相关参数的。
  1. public DatabaseModelFactoryOptions(IEnumerable<string>? tables = null, IEnumerable<string>? schemas = null)
  2. {
  3.     Tables = tables ?? [];
  4.     Schemas = schemas ?? [];
  5. }
复制代码
上述是它的构造函数,Tables 是你要告诉朋友,你想要哪些表;Schemas 表示你要的架构,比如 dbo。这两个参数都可以是 null,如果是 null 表示要全库爆。
Create 方法返回的 DatabaseModel 对象包含数据库名、数据库中表、列、主键、外键、索引等相关信息。
好了,拿到数据库信息了,轮到第二位朋友出场—— IScaffoldingModelFactory。他的绝活是加工,把你从数据库中爆出来的信息处理后,直接返回一个 IModel。对,就是 EF Core 中用的数据库模型,所以,如果你不打算生成代码,这时候你完全可以把这个 IModel 作为 DbContext 的外部模型使用。还记得老周写过使用外部模型的水文吗?在 DbContext 选项配置时,用 UseModel 方法。
但,建议你不要这么干,你想想每次运行程序都要爆一次数据库再转换为 EF Core 模型,既浪费性能也没啥实际意义。所以,这个生成的 IModel 还要进一步处理。
第三位朋友叫 IModelCodeGeneratorSelector。他是一名零件选配师,他会根据你的需要帮你找到合适的专属文员(代码生成器)。这位朋友有一个 Select 方法,调用后进行筛选。
  1. [Obsolete("Use the overload that takes ModelCodeGenerationOptions instead.")]
  2. IModelCodeGenerator Select(string? language);
  3. // 注意,上面的方法过时,而下面的方法又反过去调用它
  4. IModelCodeGenerator Select(ModelCodeGenerationOptions options)
  5. #pragma warning disable CS0618 // Type or member is obsolete
  6.         => Select(options.Language);
  7. #pragma warning restore CS0618
复制代码
Select 的旧版本已被标记为过时,但下面的方法又调用它。这是什么骚操作?这是官方团队的兼容操作。过时的方法只有一个字符串参数,表示生成的代码语言(“VB”,“C#”)。而新方法的参数是一个 ModelCodeGenerationOptions 选项类,可以配置更多东西。
Select 方法帮你选好了心仪的文员妹妹,她叫 IModelCodeGenerator。她虽然学历不高,但很勤奋很务实,你可以相信她。调用她的 GenerateModel 方法,她会帮你生成代码。
  1. ScaffoldedModel GenerateModel(
  2.     IModel model,
  3.     ModelCodeGenerationOptions options);
复制代码
model 参数就是第二位朋友 IScaffoldingModelFactory 帮你生成的模型;options 参数是选项,和 IModelCodeGeneratorSelector.Select 方法用的是同一个。
到这里基本工作就完成了,返回的 ScaffoldedModel 对象中已经包含代码,以及代码要存放的路径了。不过,这些目前还在内存中,未真正写入磁盘文件。程序退出后就没了。
  1. public class ScaffoldedModel
  2. {
  3.     // dbContext 类的代码,以及文件路径
  4.     public virtual ScaffoldedFile ContextFile { get; set; } = null!;
  5.     // 附加文件,通常是实体类的代码以及文件路径,每个实体类占一个文件
  6.     public virtual IList<ScaffoldedFile> AdditionalFiles { get; } = new List<ScaffoldedFile>();
  7. }
  8. public class ScaffoldedFile(string path, string code)
  9. {
  10.     // 代码要存入的文件路径
  11.     public virtual string Path { get; set; } = path;
  12.     // 已生成的代码
  13.     public virtual string Code { get; set; } = code;
  14. }
复制代码
总结一下,流程如下:
A、获取数据库信息;
B、生成设计时模型;
C、生成代码;
D、保存代码。
你一定会抱怨了,这过程有点复杂。别急,还没完呢,继续往下看,简单的来了。
上面提到的几位朋友,你一个个地告诉他们干什么是有些麻烦的,所以,把他们组成一个团队,设立一名管理者,有事只要跟他们的老大说行了。这位由不民主制度任命的老大叫 IReverseEngineerScaffolder(位于 Microsoft.EntityFrameworkCore.Scaffolding 命名空间),默认实现类是 ReverseEngineerScaffolder(位于 Microsoft.EntityFrameworkCore.Scaffolding.Internal 命名空间)。虽然这个类是 public 的,但官方团队让它藏在 Internal 命名空间下,这表明:在功能上是不希望外部代码访问的。在使用时,咱们的确不用访问该类,而是通过 IReverseEngineerScaffolder 接口来调用。
前面介绍的几位朋友,吃过几回饭后你可以忘记,现在你只要记住 IReverseEngineerScaffolder 即可。有事找他。
IReverseEngineerScaffolder 接口定义了两个方法:
  1. ScaffoldedModel ScaffoldModel(
  2.     string connectionString,
  3.     DatabaseModelFactoryOptions databaseOptions,
  4.     ModelReverseEngineerOptions modelOptions,
  5.     ModelCodeGenerationOptions codeOptions);
  6. SavedModelFiles Save(
  7.     ScaffoldedModel scaffoldedModel,
  8.     string outputDir,
  9.     bool overwriteFiles);
复制代码
ScaffoldModel 方法根据数据库生成代码,Save 方法把代码写入文件。这样一来,是不是变得简单了?只要一个服务接口,调用两个方法成员就完事了。
 
二、如何使用

既然处处是注入,那就得先初始化服务集合。Design 库提供了两个扩展方法:
  1. public static IServiceCollection AddDbContextDesignTimeServices(
  2.     this IServiceCollection services,
  3.     DbContext context);
  4. public static IServiceCollection AddEntityFrameworkDesignTimeServices(
  5.     this IServiceCollection services,
  6.     IOperationReporter? reporter = null,
  7.     Func<IServiceProvider>? applicationServiceProviderAccessor = null);
复制代码
第一个扩展方法在生成实体代码时不需要,它的作用是把现有 DbContext 实例中的服务添加到服务容器中。咱们今天要实现的功能要用到第二个方法,它会添加设计时的一些基础服务,包括我们上面提到的几位新朋友。
当然,这是设计时的基础服务,不包括 EF 核心服务。核心服务一般可以通过实现 IDesignTimeServices 接口来添加。
  1. public interface IDesignTimeServices
  2. {
  3.     void ConfigureDesignTimeServices(IServiceCollection serviceCollection);
  4. }
复制代码
实现接口时,在 ConfigureDesignTimeServices 方法中,向服务容器添加需要的服务。
然后,在程序集级别使用 [DesignTimeProviderServices] 特性指定你实现 IDesignTimeServices 接口的类的完整名称(连同命名空间)。其他工具(如 dotnet-ef,或你自己实现的工具)可以通过反射获取它,动态实例化并调用 ConfigureDesignTimeServices 方法。当然,你不用反射,直接在代码中 new 也可以的。
其实,你完全可以偷懒,不用去实现 IDesignTimeServices 接口,因为每种数据库的提供者都会实现专用的类。比如 SQL Server 的提供者,会有一个 SqlServerDesignTimeServices 类,并且应用 [DesignTimeProviderServices] 特性。
  1. [assembly: DesignTimeProviderServices("Microsoft.EntityFrameworkCore.SqlServer.Design.Internal.SqlServerDesignTimeServices")]
复制代码
对于 SQLite 数据库,会有一个 SqliteDesignTimeServices 类,同样也会在程序集上应用 [DesignTimeProviderServices] 特性。
  1. [assembly: DesignTimeProviderServices("Microsoft.EntityFrameworkCore.Sqlite.Design.Internal.SqliteDesignTimeServices")]
复制代码
这些默认实现的设计时服务提供类默认会把 EF 的核心服务、关系数据库相关、数据库专用的服务全部添加到服务容器,你不需要额外去处理。
当所有需要的服务都添加到容器后,生成 ServiceProvider。然后你直接从服务容器中获取 IReverseEngineerScaffolder 接口,配置好相关参数(如输出目录、DbContext 类的名称等),先调用 ScaffoldModel 方法生成代码,再调用 Save 方法写入文件就行了。
 
三、实例演示

光说不练,惨过失恋。前文已介绍完所有基础知识了,该练练手了。
老周以 SQL Server 来演示,先创建数据库,以及两张表。一张表是客户表,一张是照片表。因为这是一家照相馆的信息管理系统。一位客户可以有多张照片,所以是“一对多”的关系(别问我怎么没有多对多,你一张照片给多个客户?这么有分享精神的吗?除非是大合照)。
  1. USE [master]
  2. GO
  3. CREATE DATABASE [SomeDB]
  4. GO
  5. CREATE TABLE [dbo].[tb_customers] (
  6.     [cust_id] INT            IDENTITY (1, 1) NOT NULL,
  7.     [name]    NVARCHAR (12)  NOT NULL,
  8.     [age]     INT            NULL,
  9.     [address] NVARCHAR (100) NULL,
  10.     [phone]   CHAR (11)      NOT NULL,
  11.     [email]   NVARCHAR (64)  NULL,
  12.     [remark]  NTEXT          NULL,
  13.     CONSTRAINT [PK_customers] PRIMARY KEY CLUSTERED ([cust_id] ASC)
  14. );
  15. CREATE TABLE [dbo].[tb_photos] (
  16.     [Id]      INT           IDENTITY (1, 1) NOT NULL,
  17.     [tags]    NVARCHAR (30) NOT NULL,
  18.     [dpi]     REAL          DEFAULT ((300.0)) NULL,
  19.     [width]   FLOAT (53)    NOT NULL,
  20.     [height]  FLOAT (53)    NOT NULL,
  21.     [cust_id] INT           DEFAULT ((0)) NOT NULL,
  22.     CONSTRAINT [PK_photos] PRIMARY KEY CLUSTERED ([Id] ASC),
  23.     CONSTRAINT [FK_photos_custs] FOREIGN KEY ([cust_id]) REFERENCES [dbo].[tb_customers] ([cust_id]) ON DELETE CASCADE ON UPDATE CASCADE
  24. );
复制代码
注意外键是在 tb_photos 表中定义的,这里外键不唯一,要是搞唯一了就变成“一对一”关系了。
创建一个最简单的.NET项目(控制台),你需要向项目添加以下 nuget 包:
  1.   <ItemGroup>
  2.     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
  3.       <PrivateAssets>all</PrivateAssets>
  4.       
  5.     </PackageReference>
  6.     <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
  7.   </ItemGroup>
复制代码
严重注意:Design 是开发工具包,默认是不让你的代码访问的。当然,如果你考虑用反射的方法调用,那无所谓。这里咱们没必要去反射,应该直接访问,所以,在 PackageReference 元素下,把 IncludeAssets 整个节点注释掉,这样就能直接访问了。
其他的包都常规操作了,老周这里用的是 SQL Server就用 Sqlserver 包,你用的如果是 SQLite 那就 Sqlite 包。这个就不多说了,都懂的。
咱们并不是每次运行程序都需要生成代码的,除非是有改动(通常不会改,小改动的话也不用重新生成,直接手动改代码就行),所以定义一个符号,当需要生成实体代码时启用,毕竟这是为开发者服务的功能,不是面向最终用户。
  1. <strong>#define GEN_CODES</strong>
  2. ……
  3.     static void Main(string[] args)
  4.     {
  5. #if GEN_CODES
  6.         GenModelCodes();
  7. #endif
  8.     }
复制代码
下面我们把注意力集中到 GenModelCodes 方法上。
  1. #if GEN_CODES
  2.     private static void GenModelCodes()
  3.     {
  4.           ……
  5.     }<br>#endif
复制代码
用常量定义一些基本参数。
  1. // 连接字符串
  2. const string connectStr = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB";
  3. // 输出目录
  4. const string outputDir = "..\\..\\..\\DBModels";
  5. // 命名空间
  6. const string myNamespace = "DB";
  7. // 数据库上下文名称
  8. const string contextName = "SomeDbContext";
复制代码
outputDir 常量指定的是输出目录,可以用相对路径(相对于当前程序),老周这里用了三个 ..,即往上跳三层目录。你猜猜是啥目录?(项目根目录)
对于命名空间,可以有两个,一个是 DbContext 派生类所在命名空间,一个是实体类所在命名空间。当然,老周这里只用了一个 DB,意思就是它们都位于 DB 命名空间内。
contextName 常量指示生成的 DbContext 子类的名字,这里老周给了它一个风雅的名字 SomeDbContext。
接着,咱们需要配置三个选项类(上文介绍过了,虽然名字有点臭长,但不用死记,大概记得就行)。
  1. DatabaseModelFactoryOptions dmfacOpts = new(
  2.         // 选择你要的表
  3.         tables: ["tb_customers", "tb_photos"],
  4.         // 选择你要的架构
  5.         schemas: ["dbo"]
  6.     );
  7. ModelCodeGenerationOptions modgenOpt = new()
  8. {
  9.     Language = "C#",    // 语言可以不设置,默认 C#
  10.     ContextDir = "",
  11.     ModelNamespace = myNamespace,
  12.     ContextNamespace = myNamespace,
  13.     ContextName = contextName,
  14.     UseDataAnnotations = false,
  15.     UseNullableReferenceTypes = true
  16. };
  17. ModelReverseEngineerOptions modreverOpt = new()
  18. {
  19.     UseDatabaseNames = true,
  20.     NoPluralize = false
  21. };
复制代码
1、DatabaseModelFactoryOptions 选项:配置一下我们需要用的表和架构,其实这里可以全 null,毕竟咱们全部生成。
2、ModelCodeGenerationOptions 选项:生成代码相关。

  • Language 属性可以忽略的,默认就是 C#;
  • ContextDir 属性可以为 dbContext 类指定一个子目录(相对于 outputDir),空字符串表示只放在 outputDir 下,不用单独子目录;
  • ModelNamespace 属性指定实体类代码的命名空间;
  • ContextNamespace 属性指定 dbContext 类的命名空间。可以与实体类在同一命名空间;
  • ContextName 属性指定 dbcontext 类的类名;
  • UseDataAnnotations 属性表示用不用数据批注来配置模型,false 表示用 ModelBuilder 来配置,重写 DbContext.OnModelCreating 方法;
  • UseNullableReferenceTypes 属性配置用不用可以为 null 类型,比如 string?、int?;
  • ConnectionString 属性是连接字符串,不用配置,因为 IReverseEngineerScaffolder.ScaffoldModel 方法的第一个参数就是连接字符串;
  • ProjectDir 属性是 .NET 项目所在目录,这里不用配置,因为连 DbContext 都没有,这里用不上。
3、ModelReverseEngineerOptions 选项:UseDatabaseNames 表示是否用数据库原有的名字,即实体属性等命名与数据库中一样;如果不用,那么会生成 C# 命名风格的名称,如 Name、TbCustomer 等。NoPluralize 表示禁用复数,这个主要是 dbcontext 类中 DbSet 类型属性的命名,如 Students、Customers 等,false 表示不禁用。
下一步就是服务容器配置了。
  1. ServiceCollection services = new();
复制代码
添加设计时基础服务。
  1. services.AddEntityFrameworkDesignTimeServices();
复制代码
然后就是获取 IDesignTimeServices 服务了。有两种方法:
第一种方法,我们是知道的,面向 SQL Server 的设计时服务类叫 SqlServerDesignTimeServices。对,直接 new 一下就好,最简单。
  1. IDesignTimeServices designtimeSvc = new SqlServerDesignTimeServices();
  2. designtimeSvc.<strong>ConfigureDesignTimeServices(services)</strong>;
复制代码
记得调用 ConfigureDesignTimeServices 方法,否则白干活。SqlServerDesignTimeServices 类虽然声明上是 public,但功能意义上是内部类型,编译器会发出 EF1001 警告。在使用类之前的任意位置禁用这个警告。
  1. #pragma warning disable EF1001
复制代码
第二种方法是运用反射,代码虽然多一点,但不用禁用 EF1001 警告。
  1. Assembly sqlserverProvdAssm = typeof(SqlServerServiceCollectionExtensions).Assembly;
  2. DesignTimeProviderServicesAttribute? attr = sqlserverProvdAssm.GetCustomAttribute<DesignTimeProviderServicesAttribute>();
  3. if (attr == null)
  4. {
  5.     Console.WriteLine("这个程序集不对劲!");
  6.     return;
  7. }
  8. Type? designSvcType = sqlserverProvdAssm.GetType(attr.TypeName);
  9. if (designSvcType == null)
  10. {
  11.     Console.WriteLine("闹鬼了,居然找不到类型");
  12.     return;
  13. }
  14. // 创建实例
  15. IDesignTimeServices designtimeSvc = (IDesignTimeServices)Activator.CreateInstance(designSvcType)!;
  16. // 调用方法配置服务
  17. designtimeSvc.ConfigureDesignTimeServices(services);
复制代码
由于我们项目已经引用了 Microsoft.EntityFrameworkCore.SqlServer 包,所以不需要再 Load 程序集了,它已经 Load 了。所以你在这个程序集中随便选个公共类,获取其 Type,就能得到 Assembly 了。我选的是 SqlServerServiceCollectionExtensions 类,是个定义扩展方法的类。
获取到程序集后,拿到 DesignTimeProviderServices 特性。
  1. DesignTimeProviderServicesAttribute? attr = sqlserverProvdAssm.GetCustomAttribute<DesignTimeProviderServicesAttribute>();
复制代码
这个特性实例的 TypeName 属性就是 SqlServerDesignTimeServices 类的全名。然后用 Activator.CreateInstance 方法动态创建其实例,赋值给 IDesignTimeServices 接口类型的变量,最后调用 ConfigureDesignTimeServices 方法就行了。
 
配置完服务容器后,生成一下服务 Provider。
  1. IServiceProvider serviceProvider = services.BuildServiceProvider();
复制代码
接下来,见证奇迹的时候到了。从服务容器中获取 IReverseEngineerScaffolder。
  1. IReverseEngineerScaffolder scaffolder = serviceProvider.GetRequiredService<IReverseEngineerScaffolder>();
复制代码
轻松地调用 ScaffoldModel 方法。
  1. var scaffModel = scaffolder.ScaffoldModel(
  2.         connectionString: connectStr,
  3.         databaseOptions: dmfacOpts,
  4.         modelOptions: modreverOpt,
  5.         codeOptions: modgenOpt
  6.     );
复制代码
代码已经生成,要保存到文件。
  1. if (scaffModel != null)
  2. {
  3.     var res = scaffolder.Save(
  4.              scaffoldedModel: scaffModel,
  5.              outputDir: outputDir,
  6.              overwriteFiles: true    // 覆盖文件
  7.          );
  8.     if (res is not null)
  9.     {
  10.         Console.WriteLine("dbContext路径:{0}", res.ContextFile);
  11.         Console.WriteLine("实体路径:");
  12.         foreach (string f in res.AdditionalFiles)
  13.         {
  14.             Console.WriteLine("  {0}", f);
  15.         }
  16.     }
  17. }
复制代码
整个 GenModelCodes 方法的代码如下:
  1. #if GEN_CODES    private static void GenModelCodes()    {        // 连接字符串        const string connectStr = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB";        // 输出目录        const string outputDir = "..\\..\\..\\DBModels";        // 命名空间        const string myNamespace = "DB";        // 数据库上下文名称        const string contextName = "SomeDbContext";        // 准备选项        DatabaseModelFactoryOptions dmfacOpts = new(                // 选择你要的表                tables: ["tb_customers", "tb_photos"],                // 选择你要的架构                schemas: ["dbo"]            );        ModelCodeGenerationOptions modgenOpt = new()        {            Language = "C#",    // 语言可以不设置,默认 C#            ContextDir = "",            ModelNamespace = myNamespace,            ContextNamespace = myNamespace,            ContextName = contextName,            UseDataAnnotations = false,            UseNullableReferenceTypes = true        };        ModelReverseEngineerOptions modreverOpt = new()        {            UseDatabaseNames = true,            NoPluralize = false        };        // 服务集合        ServiceCollection services = new();        // 1、设计时基础服务        services.AddEntityFrameworkDesignTimeServices();        // 2、数据库提供的设计时服务,它已包含框架基础服务        // 直接实例化        IDesignTimeServices designtimeSvc = new SqlServerDesignTimeServices();        designtimeSvc.ConfigureDesignTimeServices(services);        // 或使用反射        /*        Assembly sqlserverProvdAssm = typeof(SqlServerServiceCollectionExtensions).Assembly;        DesignTimeProviderServicesAttribute? attr = sqlserverProvdAssm.GetCustomAttribute<DesignTimeProviderServicesAttribute>();        if (attr == null)        {            Console.WriteLine("这个程序集不对劲!");            return;        }        Type? designSvcType = sqlserverProvdAssm.GetType(attr.TypeName);        if (designSvcType == null)        {            Console.WriteLine("闹鬼了,居然找不到类型");            return;        }        // 创建实例        IDesignTimeServices designtimeSvc = (IDesignTimeServices)Activator.CreateInstance(designSvcType)!;        // 调用方法配置服务        designtimeSvc.ConfigureDesignTimeServices(services);        */        // 构建服务        IServiceProvider serviceProvider = services.BuildServiceProvider();        // 生成代码        IReverseEngineerScaffolder scaffolder = serviceProvider.GetRequiredService<IReverseEngineerScaffolder>();        var scaffModel = scaffolder.ScaffoldModel(                connectionString: connectStr,                databaseOptions: dmfacOpts,                modelOptions: modreverOpt,                codeOptions: modgenOpt            );        // 完事后还得保存        if (scaffModel != null)        {            var res = scaffolder.Save(                     scaffoldedModel: scaffModel,                     outputDir: outputDir,                     overwriteFiles: true    // 覆盖文件                 );            if (res is not null)            {                Console.WriteLine("dbContext路径:{0}", res.ContextFile);                Console.WriteLine("实体路径:");                foreach (string f in res.AdditionalFiles)                {                    Console.WriteLine("  {0}", f);                }            }        }    }#endif
复制代码
现在,你可以运行一下试试(连接字符串记得改一下,别照抄)。然后你会得到以下宝藏:
1.png

看看生成的代码。
  1. using System;
  2. using System.Collections.Generic;
  3. namespace DB;
  4. public partial class tb_customer
  5. {
  6.     public int cust_id { get; set; }
  7.     public string name { get; set; } = null!;
  8.     public int? age { get; set; }
  9.     public string? address { get; set; }
  10.     public string phone { get; set; } = null!;
  11.     public string? email { get; set; }
  12.     public string? remark { get; set; }
  13.     public virtual ICollection<tb_photo> tb_photos { get; set; } = new List<tb_photo>();
  14. }
  15. /************************************************************/
  16. using System;
  17. using System.Collections.Generic;
  18. namespace DB;
  19. public partial class tb_photo
  20. {
  21.     public int Id { get; set; }
  22.     public string tags { get; set; } = null!;
  23.     public float? dpi { get; set; }
  24.     public double width { get; set; }
  25.     public double height { get; set; }
  26.     public int cust_id { get; set; }
  27.     public virtual tb_customer cust { get; set; } = null!;
  28. }
复制代码
  1. using System;
  2. using System.Collections.Generic;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace DB;
  5. public partial class SomeDbContext : DbContext
  6. {
  7.     public SomeDbContext()
  8.     {
  9.     }
  10.     public SomeDbContext(DbContextOptions<SomeDbContext> options)
  11.         : base(options)
  12.     {
  13.     }
  14.     public virtual DbSet<tb_customer> tb_customers { get; set; }
  15.     public virtual DbSet<tb_photo> tb_photos { get; set; }
  16.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  17. #warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
  18.         => optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB");
  19.     protected override void OnModelCreating(ModelBuilder modelBuilder)
  20.     {
  21.         modelBuilder.Entity<tb_customer>(entity =>
  22.         {
  23.             entity.HasKey(e => e.cust_id).HasName("PK_customers");
  24.             entity.Property(e => e.address).HasMaxLength(100);
  25.             entity.Property(e => e.email).HasMaxLength(64);
  26.             entity.Property(e => e.name).HasMaxLength(12);
  27.             entity.Property(e => e.phone)
  28.                 .HasMaxLength(11)
  29.                 .IsUnicode(false)
  30.                 .IsFixedLength();
  31.             entity.Property(e => e.remark).HasColumnType("ntext");
  32.         });
  33.         modelBuilder.Entity<tb_photo>(entity =>
  34.         {
  35.             entity.HasKey(e => e.Id).HasName("PK_photos");
  36.             entity.Property(e => e.dpi).HasDefaultValue(300f);
  37.             entity.Property(e => e.tags).HasMaxLength(30);
  38.             entity.HasOne(d => d.cust).WithMany(p => p.tb_photos)
  39.                 .HasForeignKey(d => d.cust_id)
  40.                 .HasConstraintName("FK_photos_custs");
  41.         });
  42.         OnModelCreatingPartial(modelBuilder);
  43.     }
  44.     partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
  45. }
复制代码
看看 SomeDbContext 类的 tb_customers 和 tb_photos 属性,ModelReverseEngineerOptions 选项类中的 NoPluralize 属性配置的就是这里(属性命名使用复数,当然,如果你生成的是中文名,那无所谓)。
重写 OnConfiguring 方法,配置数据库连接的地方有个警告。
  1.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  2. #warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
  3.         => optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB");
复制代码
意思是提醒你不要把连接字符串硬编码,这个后面咱们可以自己改代码,使用配置文件中的连接字符串。
 
好了,今天的话题聊到这儿。下一篇咱们聊 Code First 方案下,用编程方式去生成迁移代码,并迁移数据库。
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册