大伙伴们只要学过三天 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 对象。- DatabaseModel Create(string connectionString, DatabaseModelFactoryOptions options);
复制代码 第一个参数就是连接字符串,这个不用介绍了吧。要爆库你得知道库在哪里吧。第二个参数是选项类,用来配置相关参数的。- public DatabaseModelFactoryOptions(IEnumerable<string>? tables = null, IEnumerable<string>? schemas = null)
- {
- Tables = tables ?? [];
- Schemas = schemas ?? [];
- }
复制代码 上述是它的构造函数,Tables 是你要告诉朋友,你想要哪些表;Schemas 表示你要的架构,比如 dbo。这两个参数都可以是 null,如果是 null 表示要全库爆。
Create 方法返回的 DatabaseModel 对象包含数据库名、数据库中表、列、主键、外键、索引等相关信息。
好了,拿到数据库信息了,轮到第二位朋友出场—— IScaffoldingModelFactory。他的绝活是加工,把你从数据库中爆出来的信息处理后,直接返回一个 IModel。对,就是 EF Core 中用的数据库模型,所以,如果你不打算生成代码,这时候你完全可以把这个 IModel 作为 DbContext 的外部模型使用。还记得老周写过使用外部模型的水文吗?在 DbContext 选项配置时,用 UseModel 方法。
但,建议你不要这么干,你想想每次运行程序都要爆一次数据库再转换为 EF Core 模型,既浪费性能也没啥实际意义。所以,这个生成的 IModel 还要进一步处理。
第三位朋友叫 IModelCodeGeneratorSelector。他是一名零件选配师,他会根据你的需要帮你找到合适的专属文员(代码生成器)。这位朋友有一个 Select 方法,调用后进行筛选。- [Obsolete("Use the overload that takes ModelCodeGenerationOptions instead.")]
- IModelCodeGenerator Select(string? language);
- // 注意,上面的方法过时,而下面的方法又反过去调用它
- IModelCodeGenerator Select(ModelCodeGenerationOptions options)
- #pragma warning disable CS0618 // Type or member is obsolete
- => Select(options.Language);
- #pragma warning restore CS0618
复制代码 Select 的旧版本已被标记为过时,但下面的方法又调用它。这是什么骚操作?这是官方团队的兼容操作。过时的方法只有一个字符串参数,表示生成的代码语言(“VB”,“C#”)。而新方法的参数是一个 ModelCodeGenerationOptions 选项类,可以配置更多东西。
Select 方法帮你选好了心仪的文员妹妹,她叫 IModelCodeGenerator。她虽然学历不高,但很勤奋很务实,你可以相信她。调用她的 GenerateModel 方法,她会帮你生成代码。- ScaffoldedModel GenerateModel(
- IModel model,
- ModelCodeGenerationOptions options);
复制代码 model 参数就是第二位朋友 IScaffoldingModelFactory 帮你生成的模型;options 参数是选项,和 IModelCodeGeneratorSelector.Select 方法用的是同一个。
到这里基本工作就完成了,返回的 ScaffoldedModel 对象中已经包含代码,以及代码要存放的路径了。不过,这些目前还在内存中,未真正写入磁盘文件。程序退出后就没了。- public class ScaffoldedModel
- {
- // dbContext 类的代码,以及文件路径
- public virtual ScaffoldedFile ContextFile { get; set; } = null!;
- // 附加文件,通常是实体类的代码以及文件路径,每个实体类占一个文件
- public virtual IList<ScaffoldedFile> AdditionalFiles { get; } = new List<ScaffoldedFile>();
- }
- public class ScaffoldedFile(string path, string code)
- {
- // 代码要存入的文件路径
- public virtual string Path { get; set; } = path;
- // 已生成的代码
- public virtual string Code { get; set; } = code;
- }
复制代码 总结一下,流程如下:
A、获取数据库信息;
B、生成设计时模型;
C、生成代码;
D、保存代码。
你一定会抱怨了,这过程有点复杂。别急,还没完呢,继续往下看,简单的来了。
上面提到的几位朋友,你一个个地告诉他们干什么是有些麻烦的,所以,把他们组成一个团队,设立一名管理者,有事只要跟他们的老大说行了。这位由不民主制度任命的老大叫 IReverseEngineerScaffolder(位于 Microsoft.EntityFrameworkCore.Scaffolding 命名空间),默认实现类是 ReverseEngineerScaffolder(位于 Microsoft.EntityFrameworkCore.Scaffolding.Internal 命名空间)。虽然这个类是 public 的,但官方团队让它藏在 Internal 命名空间下,这表明:在功能上是不希望外部代码访问的。在使用时,咱们的确不用访问该类,而是通过 IReverseEngineerScaffolder 接口来调用。
前面介绍的几位朋友,吃过几回饭后你可以忘记,现在你只要记住 IReverseEngineerScaffolder 即可。有事找他。
IReverseEngineerScaffolder 接口定义了两个方法:- ScaffoldedModel ScaffoldModel(
- string connectionString,
- DatabaseModelFactoryOptions databaseOptions,
- ModelReverseEngineerOptions modelOptions,
- ModelCodeGenerationOptions codeOptions);
- SavedModelFiles Save(
- ScaffoldedModel scaffoldedModel,
- string outputDir,
- bool overwriteFiles);
复制代码 ScaffoldModel 方法根据数据库生成代码,Save 方法把代码写入文件。这样一来,是不是变得简单了?只要一个服务接口,调用两个方法成员就完事了。
二、如何使用
既然处处是注入,那就得先初始化服务集合。Design 库提供了两个扩展方法:- public static IServiceCollection AddDbContextDesignTimeServices(
- this IServiceCollection services,
- DbContext context);
- public static IServiceCollection AddEntityFrameworkDesignTimeServices(
- this IServiceCollection services,
- IOperationReporter? reporter = null,
- Func<IServiceProvider>? applicationServiceProviderAccessor = null);
复制代码 第一个扩展方法在生成实体代码时不需要,它的作用是把现有 DbContext 实例中的服务添加到服务容器中。咱们今天要实现的功能要用到第二个方法,它会添加设计时的一些基础服务,包括我们上面提到的几位新朋友。
当然,这是设计时的基础服务,不包括 EF 核心服务。核心服务一般可以通过实现 IDesignTimeServices 接口来添加。- public interface IDesignTimeServices
- {
- void ConfigureDesignTimeServices(IServiceCollection serviceCollection);
- }
复制代码 实现接口时,在 ConfigureDesignTimeServices 方法中,向服务容器添加需要的服务。
然后,在程序集级别使用 [DesignTimeProviderServices] 特性指定你实现 IDesignTimeServices 接口的类的完整名称(连同命名空间)。其他工具(如 dotnet-ef,或你自己实现的工具)可以通过反射获取它,动态实例化并调用 ConfigureDesignTimeServices 方法。当然,你不用反射,直接在代码中 new 也可以的。
其实,你完全可以偷懒,不用去实现 IDesignTimeServices 接口,因为每种数据库的提供者都会实现专用的类。比如 SQL Server 的提供者,会有一个 SqlServerDesignTimeServices 类,并且应用 [DesignTimeProviderServices] 特性。- [assembly: DesignTimeProviderServices("Microsoft.EntityFrameworkCore.SqlServer.Design.Internal.SqlServerDesignTimeServices")]
复制代码 对于 SQLite 数据库,会有一个 SqliteDesignTimeServices 类,同样也会在程序集上应用 [DesignTimeProviderServices] 特性。- [assembly: DesignTimeProviderServices("Microsoft.EntityFrameworkCore.Sqlite.Design.Internal.SqliteDesignTimeServices")]
复制代码 这些默认实现的设计时服务提供类默认会把 EF 的核心服务、关系数据库相关、数据库专用的服务全部添加到服务容器,你不需要额外去处理。
当所有需要的服务都添加到容器后,生成 ServiceProvider。然后你直接从服务容器中获取 IReverseEngineerScaffolder 接口,配置好相关参数(如输出目录、DbContext 类的名称等),先调用 ScaffoldModel 方法生成代码,再调用 Save 方法写入文件就行了。
三、实例演示
光说不练,惨过失恋。前文已介绍完所有基础知识了,该练练手了。
老周以 SQL Server 来演示,先创建数据库,以及两张表。一张表是客户表,一张是照片表。因为这是一家照相馆的信息管理系统。一位客户可以有多张照片,所以是“一对多”的关系(别问我怎么没有多对多,你一张照片给多个客户?这么有分享精神的吗?除非是大合照)。- USE [master]
- GO
- CREATE DATABASE [SomeDB]
- GO
- CREATE TABLE [dbo].[tb_customers] (
- [cust_id] INT IDENTITY (1, 1) NOT NULL,
- [name] NVARCHAR (12) NOT NULL,
- [age] INT NULL,
- [address] NVARCHAR (100) NULL,
- [phone] CHAR (11) NOT NULL,
- [email] NVARCHAR (64) NULL,
- [remark] NTEXT NULL,
- CONSTRAINT [PK_customers] PRIMARY KEY CLUSTERED ([cust_id] ASC)
- );
- CREATE TABLE [dbo].[tb_photos] (
- [Id] INT IDENTITY (1, 1) NOT NULL,
- [tags] NVARCHAR (30) NOT NULL,
- [dpi] REAL DEFAULT ((300.0)) NULL,
- [width] FLOAT (53) NOT NULL,
- [height] FLOAT (53) NOT NULL,
- [cust_id] INT DEFAULT ((0)) NOT NULL,
- CONSTRAINT [PK_photos] PRIMARY KEY CLUSTERED ([Id] ASC),
- CONSTRAINT [FK_photos_custs] FOREIGN KEY ([cust_id]) REFERENCES [dbo].[tb_customers] ([cust_id]) ON DELETE CASCADE ON UPDATE CASCADE
- );
复制代码 注意外键是在 tb_photos 表中定义的,这里外键不唯一,要是搞唯一了就变成“一对一”关系了。
创建一个最简单的.NET项目(控制台),你需要向项目添加以下 nuget 包:- <ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
- <PrivateAssets>all</PrivateAssets>
-
- </PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
- </ItemGroup>
复制代码 严重注意:Design 是开发工具包,默认是不让你的代码访问的。当然,如果你考虑用反射的方法调用,那无所谓。这里咱们没必要去反射,应该直接访问,所以,在 PackageReference 元素下,把 IncludeAssets 整个节点注释掉,这样就能直接访问了。
其他的包都常规操作了,老周这里用的是 SQL Server就用 Sqlserver 包,你用的如果是 SQLite 那就 Sqlite 包。这个就不多说了,都懂的。
咱们并不是每次运行程序都需要生成代码的,除非是有改动(通常不会改,小改动的话也不用重新生成,直接手动改代码就行),所以定义一个符号,当需要生成实体代码时启用,毕竟这是为开发者服务的功能,不是面向最终用户。- <strong>#define GEN_CODES</strong>
- ……
- static void Main(string[] args)
- {
- #if GEN_CODES
- GenModelCodes();
- #endif
- }
复制代码 下面我们把注意力集中到 GenModelCodes 方法上。- #if GEN_CODES
- private static void GenModelCodes()
- {
- ……
- }<br>#endif
复制代码 用常量定义一些基本参数。- // 连接字符串
- const string connectStr = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB";
- // 输出目录
- const string outputDir = "..\\..\\..\\DBModels";
- // 命名空间
- const string myNamespace = "DB";
- // 数据库上下文名称
- const string contextName = "SomeDbContext";
复制代码 outputDir 常量指定的是输出目录,可以用相对路径(相对于当前程序),老周这里用了三个 ..,即往上跳三层目录。你猜猜是啥目录?(项目根目录)
对于命名空间,可以有两个,一个是 DbContext 派生类所在命名空间,一个是实体类所在命名空间。当然,老周这里只用了一个 DB,意思就是它们都位于 DB 命名空间内。
contextName 常量指示生成的 DbContext 子类的名字,这里老周给了它一个风雅的名字 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
- };
复制代码 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 表示不禁用。
下一步就是服务容器配置了。- ServiceCollection services = new();
复制代码 添加设计时基础服务。- services.AddEntityFrameworkDesignTimeServices();
复制代码 然后就是获取 IDesignTimeServices 服务了。有两种方法:
第一种方法,我们是知道的,面向 SQL Server 的设计时服务类叫 SqlServerDesignTimeServices。对,直接 new 一下就好,最简单。- IDesignTimeServices designtimeSvc = new SqlServerDesignTimeServices();
- designtimeSvc.<strong>ConfigureDesignTimeServices(services)</strong>;
复制代码 记得调用 ConfigureDesignTimeServices 方法,否则白干活。SqlServerDesignTimeServices 类虽然声明上是 public,但功能意义上是内部类型,编译器会发出 EF1001 警告。在使用类之前的任意位置禁用这个警告。- #pragma warning disable EF1001
复制代码 第二种方法是运用反射,代码虽然多一点,但不用禁用 EF1001 警告。- 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);
复制代码 由于我们项目已经引用了 Microsoft.EntityFrameworkCore.SqlServer 包,所以不需要再 Load 程序集了,它已经 Load 了。所以你在这个程序集中随便选个公共类,获取其 Type,就能得到 Assembly 了。我选的是 SqlServerServiceCollectionExtensions 类,是个定义扩展方法的类。
获取到程序集后,拿到 DesignTimeProviderServices 特性。- DesignTimeProviderServicesAttribute? attr = sqlserverProvdAssm.GetCustomAttribute<DesignTimeProviderServicesAttribute>();
复制代码 这个特性实例的 TypeName 属性就是 SqlServerDesignTimeServices 类的全名。然后用 Activator.CreateInstance 方法动态创建其实例,赋值给 IDesignTimeServices 接口类型的变量,最后调用 ConfigureDesignTimeServices 方法就行了。
配置完服务容器后,生成一下服务 Provider。- IServiceProvider serviceProvider = services.BuildServiceProvider();
复制代码 接下来,见证奇迹的时候到了。从服务容器中获取 IReverseEngineerScaffolder。- IReverseEngineerScaffolder scaffolder = serviceProvider.GetRequiredService<IReverseEngineerScaffolder>();
复制代码 轻松地调用 ScaffoldModel 方法。- 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);
- }
- }
- }
复制代码 整个 GenModelCodes 方法的代码如下:- #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
复制代码 现在,你可以运行一下试试(连接字符串记得改一下,别照抄)。然后你会得到以下宝藏:
看看生成的代码。- using System;
- using System.Collections.Generic;
- namespace DB;
- public partial class tb_customer
- {
- public int cust_id { get; set; }
- public string name { get; set; } = null!;
- public int? age { get; set; }
- public string? address { get; set; }
- public string phone { get; set; } = null!;
- public string? email { get; set; }
- public string? remark { get; set; }
- public virtual ICollection<tb_photo> tb_photos { get; set; } = new List<tb_photo>();
- }
- /************************************************************/
- using System;
- using System.Collections.Generic;
- namespace DB;
- public partial class tb_photo
- {
- public int Id { get; set; }
- public string tags { get; set; } = null!;
- public float? dpi { get; set; }
- public double width { get; set; }
- public double height { get; set; }
- public int cust_id { get; set; }
- public virtual tb_customer cust { get; set; } = null!;
- }
复制代码- using System;
- using System.Collections.Generic;
- using Microsoft.EntityFrameworkCore;
- namespace DB;
- public partial class SomeDbContext : DbContext
- {
- public SomeDbContext()
- {
- }
- public SomeDbContext(DbContextOptions<SomeDbContext> options)
- : base(options)
- {
- }
- public virtual DbSet<tb_customer> tb_customers { get; set; }
- public virtual DbSet<tb_photo> tb_photos { get; set; }
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- #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.
- => optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB");
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.Entity<tb_customer>(entity =>
- {
- entity.HasKey(e => e.cust_id).HasName("PK_customers");
- entity.Property(e => e.address).HasMaxLength(100);
- entity.Property(e => e.email).HasMaxLength(64);
- entity.Property(e => e.name).HasMaxLength(12);
- entity.Property(e => e.phone)
- .HasMaxLength(11)
- .IsUnicode(false)
- .IsFixedLength();
- entity.Property(e => e.remark).HasColumnType("ntext");
- });
- modelBuilder.Entity<tb_photo>(entity =>
- {
- entity.HasKey(e => e.Id).HasName("PK_photos");
- entity.Property(e => e.dpi).HasDefaultValue(300f);
- entity.Property(e => e.tags).HasMaxLength(30);
- entity.HasOne(d => d.cust).WithMany(p => p.tb_photos)
- .HasForeignKey(d => d.cust_id)
- .HasConstraintName("FK_photos_custs");
- });
- OnModelCreatingPartial(modelBuilder);
- }
- partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
- }
复制代码 看看 SomeDbContext 类的 tb_customers 和 tb_photos 属性,ModelReverseEngineerOptions 选项类中的 NoPluralize 属性配置的就是这里(属性命名使用复数,当然,如果你生成的是中文名,那无所谓)。
重写 OnConfiguring 方法,配置数据库连接的地方有个警告。- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- #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.
- => optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SomeDB");
复制代码 意思是提醒你不要把连接字符串硬编码,这个后面咱们可以自己改代码,使用配置文件中的连接字符串。
好了,今天的话题聊到这儿。下一篇咱们聊 Code First 方案下,用编程方式去生成迁移代码,并迁移数据库。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |