当前位置: 首页 > 游戏攻略 > 如何在EF Core中自定义主键生成策略

如何在EF Core中自定义主键生成策略

来源:网络 作者:趣玩小编 发布时间:2024-08-05 09:46:50

在使用EF Core时,自动生成字段值通常会涉及到主键列(带有IDENTITY的主键)。EF Core默认的主键配置是启用Identity自增长的,而且可以自动标识主键。前提是代表主键的实体属性名要符合以下规则:

1、名字叫ID、id、或Id,不区分大小写;

2、名字由实体类名+Id构成。例如,Car实体类,包含一个属性叫CarID或CarId;

3、属性类型是整数类型(int、long、ushort等,但不是byte)或GUID。

这些识别主键的规则是由一种叫“约定”(Convension)的东西实现的,具体来说,是一个叫KeyDiscoveryConvention的类。

老周放一小段源代码给各位瞧瞧。

这几个逻辑And其实就是查找<类名>Id格式的属性名,如StudentID、CarId、OrderID……外键的发现原理也跟主键一样。

用Sqlite数据库举一个简单的例子。下面是实体类(假设它用来表示输入法信息):

如你所见,这个类作为主键的属性是RecoId,但是,它的命名是无法被自动识别的,咱们必须明确地告诉EF,它是主键。方法有二:

1、批注法。直接在属性上应用相关的特性类。如

2、重写DbContext类的OnModelCreating方法。如

如果使用了上面重写OnModelCreating方法,那么,你的DbContext派生类已经能识别InputMethod实体类了。但如果你用的是在属性上应用[Key]特性的方式,那么DbContext的派生类是识别不到实体类的,你需要将它的集合声明为DbContext的属性。

注意,数据记录的集合要用DbSet<>,其他类型的集合是不行的哟。比如,你改成这样,就会报错。

说明人家只认DbSet集合,其他集合无效。

这里老周选用服务容器来配置。

连接字符串你可以直接用字符串写,不用ConnectionStringBuilder。默认的SQLite库是不支持密码的,所以老周就不设置密码了。在调用AddSqlite方法时,有一个名为optionsAction的参数,咱们可以用它配置日志输出。LogTo方法配置简单,只要提供一个委托,它绑定的方法只要有一个string类型的输入参数就行,这个字符串参数就是日志文本。

配置日志功能后,运行程序时,控制台能看到执行的SQL语句。

下面咱们来创建数据库,然后插入两条InputMethod记录。

这里是为了测试,调用EnsureDeleted方法,实际应用时一般不要调用。因为这个方法的功能是把现存的数据库删除。如果调用了此方法,那应用程序每次启动都会删掉数据库,那用户肯定会投诉你的。EnsureCreated方法可以使用,它的功能是如果数据库不存在,就创建新数据库;如果数据库存在,那啥也不做。所以,调用EnsureCreated方法不会造成数据丢失,放心用。

插入数据和调用SaveChanges方法保存到数据库的代码,相信大伙都很熟了,老周就不介绍了。

程序运行之后,将得到这样的日志:

这样你会发现,对于整数类型的主键,默认是自动生成递增ID的。注意,这个是由数据库生成的,而不是EF Core的生成器。不同数据库的SQL语句会有差异。

为了对比,咱们不防改为SQL Server,看看输出的日志。

其他代码不变,再次运行。输出的日志如下:

A、使用Sqlite数据库时,生成的CREATE TABLE语句,主键列是PRIMARY KEY AUTOINCREMENT;

B、使用SQL Server时,主键列使用的是IDENTITY,默认以1为种子,增量是1。所以插入记录的键值是1和2。

有时候我们并不希望主键列自动生成值,同样有两种配置方法:

1、通过特性类来批注。如

将DatabaseGeneratedOption设置为None,就取消列的自动生成了。

2、通过模型配置,即重写OnModelCreating方法实现。

这种情况下,插入数据时主键列就需要咱们手动赋值了。

======================================================================================

上面的是热身运动,是比较简单的应用方案。下面老周给大伙伴解决一个问题。老周看到在GitHub等平台上有人提问,但没有得到解决。如果你看到老周这篇水文并且你有此困惑,那你运气不错。好,F话不多说,咱们看问题。

需求: 主键不变,但是我不想让它带有IDENTITY,插入记录时用我自定义的方式生成主键的值 。这个需要的本质就是:我不要数据库给我生成递增ID,我要在程序里生成。

前面老周提过,默认行为下主键列如果是整数类型或GUID,就会产生自增长的列。所以,咱们有一个很关键的步骤——就是怎么禁止EF去产生IDENTITY列。如果你看到EF Core SQL Server的源代码,可能你会知道有个约定类叫SqlServerValueGenerationStrategyConvention。这个约定类默认会设置主键列的自动生成策略为IdentityColumn。

于是,有大伙伴可能会想到,那我从SqlServerValueGenerationStrategyConvention派生出一个类,重写ProcessModelInitialized方法,把自动生成策略改为None,然后在约定集合中替换掉SqlServerValueGenerationStrategyConvention。

这个思路不是不行,就是工作量大一些。你不仅要定义个新类,还要把它注册到服务容器中替换SqlServerValueGenerationStrategyConvention。毕竟EF Core框架内部也是使用了服务容器和依赖注入的方式来组织各种组件的。具体做法是在初始化DbContext类(包括你派生的类)时会传递一个DbContextOptions<TContext>对象,它有一个ReplaceService方法,可以替换容器中的服务。在调用AddSqlServer方法时就可以配置。

上述方案太麻烦,故老周未采用。其实,就算服务初始化时设置了生成策略是Identity,可我们可以在构建模型时修改它呀。做法就是重写DbContext类的OnModelCreating方法,然后通过IConventionModelBuilder.HasValueGenerationStrategy方法就能修改生成策略。当然,这里头是有点波折的,我们不能在ModelBuilder实例上调用,因为这货并不是直接实现IConventionModelBuilder接口的,它是这么搞的:

IInfrastructure< T>接口的作用是把T隐藏,不希望程序代码访问类型T。DbContext类也实现这个接口,但它隐藏的是IServiceProvider对象,不想让咱们访问里面注册的服务。也就是说,IConventionModelBuilder的实现者被隐藏了。不过,EF Core并没有把事情做得太绝,好歹给了一个扩展方法GetInfrastructure。用这个扩展方法我们能得到IConventionModelBuilder类型的引用。

弄清楚这个原理,代码就好写了。

把生成策略改为None后,生成主键列时就不会有IDENTITY了。

如果你乐意,可以在插入记录时手动给主键列赋值也行的。不过,为了能自动生成值,我们应该写一个自己的生成类。

我这里的逻辑是这样的,值是随机生成的,但要用一个循环去检查这个值是不是已存在数据库中,如果存在,继续生成,直到数值不重复。

实现自定义生成器,有两个抽象类可供选择:

1、如果你生成的值,类型不确定(可能是int,可能是long,可能是……),那就实现ValueGenerator类;

2、如果要生成的值是明确类型的,比如这里是int,那就实现带泛型参数的ValueGenerator<TValue>类。

这两个类有继承关系,ValueGenerator<TValue>派生自ValueGenerator类。需要实现的抽象成员:

A、GeneratesTemporaryValues属性:只读,返回bool值。如果你生成的值是临时的,返回true,不是临时的,返回false。临时的值表示暂时赋值给属性/字段,但insert、update时,这个值不会存入数据库;如果不是临时的值,最终会存进数据库。上面例子中,老周让它返回false,就说明生成的这个值,要写入数据库的。

B、如果继承ValueGenerator类,请实现NextValue抽象方法,返回类型是object,就是生成的值;如果继承的是ValueGenerator<TValue>,请实现Next方法,此方法返回的类型由泛型参数决定。上面例子中是int。

写好生成类后,要把它应用到实体模型中,同样是重写DbContext类的OnModelCreating方法。

ValueGeneratedOnAdd方法表示在记录插入数据库时自动生成值,HasValueGenerator方法设置你自定义的生成器。

现在,有了自定义生成规则,在插入数据时,主键不能赋值。一旦赋值,生成器就无效了。

运行应用程序,你会发现,这次生成的CREATE TABLE语句中,RecoId列已经没有IDENTITY关键字了。

怎么样,这玩法是不是很高端?当然,如果主键是字符串类型,你也可以生成字符串的值,一切看你需求,反正原理是相同的。

最后,咱们顺便聊聊如何自动更改日期时间的问题。这个在实际开发中也很常用,比如一个计划表,其实体如下:

最后一个字段UpdateTime表示在插入后更新的时间,所以在插入时这个字段可以留NULL。比如我修改计划完成数Completed,在写入数据库时自动给UpdateTime字段赋当前时间。这个不能用值生成器来做,因为生成器只能在数据插入前或插入后产生一次值,后面更新数据时不会再生成新值,就做不到自动设置更新时间了。所以,这里咱们可以换个思路:重写DbContext类的SaveChanges方法,在命令发送到数据库之前找出哪些记录被修改过,然后设置UpdateTime属性,最后才发送SQL语句。这样也能达到自动记录更新时间的功能。

Modified表示实体被更改过的状态。修改属性值时,应赋值给CurrentValue,它代表的是实体当前的值,不要改OriginalValue的值,它指的是从数据库中读到的值,多数情况下不用去改,除非你要把当前DbContext实例的数据复制到另一个DbContext实例。

这样当Plan对象被修改后,在提交前会自动设置更新时间。下面是测试代码:

先插入三条数据,然后输入记录ID来修改Completed的值。更改后会看到更新时间。

好了,今天咱们就水到这里了。

相关攻略
热门推荐 更多 +
休闲益智 | 945.71MB
我的世界是一款风靡全球的3D第一人称沙盒...
9.6
角色扮演 | 878.96MB
最新版《汉家江湖》是一款以武侠为题材、以...
9.5
飞行射击 | 262.79MB
《荒野乱斗》是快节奏射击类多人对战游戏。...
9.5
飞行射击 | 102.9M
掌上飞车手游app是由腾讯特别为QQ飞车...
9.2
休闲益智 | 263.56MB
开心消消乐是一款轻松休闲的手游,也是一款...
9.6