EntityFrameworkCore中的OnModelCreating
在我们使用EntityFrameworkCore作为数据库ORM框架的时候,不可避免的要重载DbContext中的一个虚方法OnModelCreating,那么这个方法到底是做什么的?到底有哪些作用呢?带着这些问题我们来看看在EntityFrameworkCore到底该如何使用OnModelCreating这个方法,首先我们来看看Microsoft.EntityFrameworkCore命名空间下面的DbContext中关于OnModelCreating的定义及注释。
/// <summary>
/// Override this method to further configure the model that was discovered by convention from the entity types
/// exposed in <see cref="T:Microsoft.EntityFrameworkCore.DbSet`1" /> properties on your derived context. The resulting model may be cached
/// and re-used for subsequent instances of your derived context.
/// </summary>
/// <remarks>
/// If a model is explicitly set on the options for this context (via <see cref="M:Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.UseModel(Microsoft.EntityFrameworkCore.Metadata.IModel)" />)
/// then this method will not be run.
/// </remarks>
/// <param name="modelBuilder">
/// The builder being used to construct the model for this context. Databases (and other extensions) typically
/// define extension methods on this object that allow you to configure aspects of the model that are specific
/// to a given database.
/// </param>
protected internal virtual void OnModelCreating(ModelBuilder modelBuilder)
{
}
按照官方的解释:在完成对派生上下文的模型的初始化后,并在该模型已锁定并用于初始化上下文之前,将调用此方法。 虽然此方法的默认实现不执行任何操作,但可在派生类中重写此方法,这样便能在锁定模型之前对其进行进一步的配置。通常,在创建派生上下文的第一个实例时仅调用此方法一次, 然后将缓存该上下文的模型,并且该模型适用于应用程序域中的上下文的所有后续实例另外在做数据库迁移生成迁移文件的时候也会调用OnModelCreating方法。通过在给定的 ModelBuidler 上设置 ModelCaching 属性可禁用此缓存,但注意这样做会大大降低性能。 通过直接使用 DbModelBuilder 和 DbContextFactory 类来提供对缓存的更多控制。那么这些深度的自定义配置有哪些方面的内容呢?
一 通过Fluent API配置实体
这个部分是整个配置的重点,我们能够通过Fluent API来进行各种各样的配置,下面通过一系列的例子来加以说明,这里推荐一篇非常好的文章,这里便不再赘述,这里只讲述之前不同的部分,从而使整篇文章更加完整,如果你对数据库中的关系映射不太清楚,请阅读这篇文章。
A 设置主外键关系
 #region Sales.Basis 主键及关系设定
            modelBuilder.Entity<DealerMarketDptRelation>()
                .HasOne(d => d.MarketingDepartment)
                .WithMany(e => e.DealerMarketDptRelations)
                .HasForeignKey(d => d.MarketId);
            modelBuilder.Entity<Company>()
                .HasOne(c => c.Branch)
                .WithOne(d => d.Company)
                .HasForeignKey<Branch>(d => d.Id);
            modelBuilder.Entity<Company>()
                .HasOne(c => c.Dealer)
                .WithOne(d => d.Company)
                .HasForeignKey<Dealer>(d => d.Id);
            #endregion  
B 设置索引
 modelBuilder.Entity<RepairContract>(r => {
                r.HasIndex(p => p.Code);
                r.HasIndex(p => p.Vin);
                r.HasIndex(p => p.DealerCode);
                r.HasIndex(p => p.DealerName);
                r.HasIndex(p => p.CreateTime);
                r.HasIndex(p => p.ModifyTime);
            });
在查询数据库的时候我们经常需添加索引,在Fluent API中我们可以通过上面的方式来为某一个表添加索引。
C 创建Sequence
            modelBuilder.HasSequence("S_PdiCheck");
            modelBuilder.HasSequence("S_PdiCheckItem");
            modelBuilder.HasSequence("S_PdiCheckItemDetail");
            modelBuilder.HasSequence("S_PdiCheckAth");
            modelBuilder.HasSequence("S_CancellationHandlingTime");
            modelBuilder.HasSequence("S_DealerBlacklist");
            modelBuilder.HasSequence("S_PropagateMethod");
            modelBuilder.HasSequence("S_VehicleOwnerChange");
            modelBuilder.HasSequence("S_CustomerVehicleAth");
            modelBuilder.HasSequence("S_DealerVehicleStockSnapshots");  
D 设置表级联删除
/// <summary>
/// 调整生成器的默认行为
/// <para/>生成的表名称为单数,外键关闭级联删除
/// </summary>
/// <param name="modelBuilder"></param>
public static void AdjustDbDefautAction(this ModelBuilder modelBuilder) {
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()) {
if (mutableEntityType.ClrType == null)
continue;
if (mutableEntityType.ClrType.GetCustomAttribute<TableAttribute>() == null)
mutableEntityType.Relational().TableName = mutableEntityType.ClrType.Name;
foreach (var foreignKey in mutableEntityType.GetForeignKeys()) {
//设置表级联删除方式
foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
}
}
}
但是有的时候我们由于业务的需要又需要开启级联删除怎么办,当然我们可以通过OnDelete方法进行删除,就像下面的例子
modelBuilder.Entity<RepairSettlement>(b => {
                    b.HasMany(rs => rs.RepairSettlementWorkItems)
                    .WithOne(rsi => rsi.RepairSettlement)
                    .OnDelete(DeleteBehavior.Cascade);
            });
这里表示的是一组具有主清单关系的例子,其中一个RepairSettlement实体里面可以包含多个RepairSettlementWorkItem实体,这个我们可以在RepairSettlement中定义一个List<RepairSettlementWorkItem>来表示两者之间的关系,然后通过OnDelete方法就会实现两者之间的级联删除关系。
但是如果一个系统中存在大量这种主清单关系需要进行级联删除的话,那么我们的OnModelCreating中就会存在大量这种重复代码,那么有不有一种更加合适的方法用于指定两者之间的级联删除关系呢?
答案是利用自定义属性,直接在实体的外键上面定义好级联删除关系,这样就能最大程度上简化代码。
1 定义ForeignKeyReferenceAttribute
/// <summary>
/// 当前自定义属性用于当我们需要开启实体之间的级联删除行为时候使用
/// </summary>
public class ForeignKeyReferenceAttribute : Attribute {
/// <summary>
/// 设置默认的级联删除方式
/// </summary>
public ForeignKeyReferenceAttribute() {
DeleteBehavior = DeleteBehavior.Restrict;
} /// <summary>
/// 设置当前外键的级联删除方式
/// </summary>
public DeleteBehavior DeleteBehavior { get; set; }
}
2 修改设置级联删除的方式
/// <summary>
/// 调整生成器的默认行为
/// <para/>生成的表名称为单数,外键关闭级联删除
/// </summary>
/// <param name="modelBuilder"></param>
public static void AdjustDbDefautAction(this ModelBuilder modelBuilder) {
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes()) {
if (mutableEntityType.ClrType == null)
continue;
if (mutableEntityType.ClrType.GetCustomAttribute<TableAttribute>() == null)
mutableEntityType.Relational().TableName = mutableEntityType.ClrType.Name;
foreach (var foreignKey in mutableEntityType.GetForeignKeys()) {
var canCascadeDelete = foreignKey.Properties[0]?.PropertyInfo?.GetCustomAttributes<ForeignKeyReferenceAttribute>()?.SingleOrDefault();
foreignKey.DeleteBehavior = canCascadeDelete?.DeleteBehavior ?? DeleteBehavior.Restrict;
}
}
}
这里不再统一设置foreignKey.DeleteBehavior = DeleteBehavior.Restrict;而是根据当前外键定义的ForeignKeyReferenceAttribute值来决定当前外键的级联删除关系。
3 设置外键级联删除关系
通过上面的例子我们就可以在表RepairSettlementWorkItem中的外键RepairSettlementId上面设置外键级联删除关系。
[ForeignKeyReference(DeleteBehavior = DeleteBehavior.Cascade)]
public Guid RepairSettlementId { get; set; }
E 设置不同数据库专有特性
由于数据库有多种类型,所以不可避免我们需要针对特定数据库来做一些配置,比如Oracle数据库会限定列名最大为30个字符,有比如有些实体中定义的属性,比如Level可能在Oracle数据库中作为一种关键字,而在另外的数据库系统中则没有这个限制,所以我们通过modelBuilder.Entity<FaultCategoryType>(d => d.Property(p => p.Level).HasColumnName("FLevel"))这种方式来将实体属性名称设置为FLevel,针对这个方面我们也可以在OnModelCreating中统一进行处理。
if (Database.IsOracle()) {
                // Oracle 要求列名不超过30个字符
                modelBuilder.Entity<NotificationInfo>(entity => {
                    entity.Property(e => e.EntityTypeAssemblyQualifiedName)
                        .HasColumnName("EntityTypeAssyQualifiedName");
                });
                modelBuilder.Entity<NotificationSubscriptionInfo>(entity => {
                    entity.Property(e => e.EntityTypeAssemblyQualifiedName)
                        .HasColumnName("EntityTypeAssyQualifiedName");
                });
                modelBuilder.Entity<TenantNotificationInfo>(entity => {
                    entity.Property(e => e.EntityTypeAssemblyQualifiedName)
                        .HasColumnName("EntityTypeAssyQualifiedName");
                });
                // 日志的时间需要精确
                modelBuilder.Entity<AuditLog>(entity => {
                    entity.Property(e => e.ExecutionTime).HasColumnType("TimeStamp");
                });
                modelBuilder.Entity<EntityChange>(entity => {
                    entity.Property(e => e.ChangeTime).HasColumnType("TimeStamp");
                });
                modelBuilder.Entity<EntityChangeSet>(entity => {
                    entity.Property(e => e.CreationTime).HasColumnType("TimeStamp");
                    entity.Property(e => e.ExtensionData).HasMaxLength(int.MaxValue);
                });
                foreach (var entity in modelBuilder.Model.GetEntityTypes()) {
                    if (entity.ClrType == null) continue;
                    if (!entity.ClrType.GetAttributes<TableAttribute>().Any()) {
                        entity.Relational().TableName = entity.ClrType.Name;
                    }
                    foreach (var property in entity.GetProperties().Where(p => p.PropertyInfo != null)){
                        if (property.PropertyInfo.Name.ToUpper().EndsWith("ID") && property.ClrType == typeof(string)) {
                            property.Relational().ColumnType = "CHAR(36)";
                        }
                        else if (property.Name == "RowVersion" &&
                                 (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?))) {
                            property.Oracle().ColumnType = "TIMESTAMP";
                        }
                        else if (property.ClrType == typeof(decimal) || property.ClrType == typeof(decimal?)) {
                            property.Oracle().ColumnType = "NUMBER(16,4)";
                        }
                    }
                }
                modelBuilder.Entity<ClaimApplyAth>(d => d.Property(p => p.FileId).HasColumnType("VARCHAR2(200)"));
                modelBuilder.Entity<MarketQualityAth>(d => d.Property(p => p.FileId).HasColumnType("VARCHAR2(200)"));
                modelBuilder.Entity<FaultCategoryType>(d => d.Property(p => p.Level).HasColumnName("FLevel"));
            }  
再比如表中常用的RowVersion字段在Oracle中实体可定义为DateTime类型,而在SQL Server中就只能够定义为byte[ ]这种方式了,所以在使用的时候都可以通过上面的方式来统一进行处理。
/// <summary>
/// dotConnect的默认DateTime类型是 TimeStamp,无长度限制的String 是 Clob
/// </summary>
/// <param name="modelBuilder"></param>
public static void AdjustOracleDefaultAction(this ModelBuilder modelBuilder) {
foreach (var item in modelBuilder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(string) || NoneAnnotationDateTime(p))
.Select(p => new {
p.ClrType,
p.Name,
Pb = modelBuilder.Entity(p.DeclaringEntityType.ClrType).Property(p.Name),
MaxLength = p.GetMaxLength()
})) {
if (item.ClrType == typeof(DateTime?)) {
item.Pb.HasColumnType("date");
} else
if (item.Name == "Discriminator")
item.Pb.HasMaxLength(100);
// ReSharper disable once PossibleInvalidOperationException
else if (item.ClrType == typeof(string) && item.MaxLength == null)
item.Pb.HasMaxLength(2000);
}
}
这里通过一些实际项目中经验来讲述EntityFrameworkCore的一些特性,后续有进一步的新的内容也会不断加入,从而使文章内容更加丰富。
EntityFrameworkCore中的OnModelCreating的更多相关文章
- 扩展EF的Fluent API中的 OnModelCreating方法 实现全局数据过滤器
		
1.生成过滤的表达式目录树 protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression&l ...
 - EntityFrameworkCore中的实体状态
		
Entry表示一个追踪,里面有state属性,是EntityState的枚举类型. 每一个实体都有一个相对应的Entry: var entry = dbContext.ChangeTracker.En ...
 - EntityFrameworkCore 中的 Attach 方法
		
Attach 的坑 Model Filed Database Value Console Value User Phone +123000000000 +12333333333 User Email ...
 - 在EntityFrameworkCore中记录EF修改日志,保存,修改字段的原始值,当前值,表名等信息
		
突发奇想,想把业务修改的所有字段原始值和修改后的值,做一个记录,然后发现使用EF可以非常简单的实现这个功能 覆盖父类中的 SaveShanges() 方法 public new int SaveCha ...
 - ASP.NET EntityFrameworkCore code first 多对多设计
		
摘要:参考网址:https://docs.microsoft.com/zh-cn/ef/core/get-started/full-dotnet/new-db场景:使用ASP.NETEntityFra ...
 - 如何在AbpNext项目中使用Mysql数据库
		
配置步骤: 1.更改.Web项目的appsettings.json的数据库连接字符串.如:server=0.0.0.0;database=VincentAbpTest;uid=root;pwd=123 ...
 - EntityFrameworkCore数据迁移(一)
		
.net core出来已经有很长一段时间了,而EentityFrameworkCore(后面简称EFCore)是.net framework的EntityFramework在.net core中的实现 ...
 - 在Code First中使用Migrations对实体类和数据库做出变更
		
在Code First中使用Migrations对实体类和数据库做出变更,Mirgration包含一系列命令. 工具--库程序包管理器--程序包管理器控制台 运行命令:Enable-Migration ...
 - ABP框架 将EntityFrameworkCore生成的SQL语句输出到控制台
		
首先 在 EntityFrameworkCore中安装 Microsoft.Extensions.Logging.Console nuget install Microsoft.Extensions. ...
 
随机推荐
- Docker Machine搭建并加入节点
			
对于集群服务器来讲,要在每台机器上手动安装Docker是一件及其痛苦的事情,还好有Docker Machine这一工具,Docker三剑客中的一角. 一.Docker Machine介绍 这个工具已经 ...
 - springboot~注册bean的方法
			
spring在启动时会自己把bean(java组件)注册到ioc容器里,实现控制反转,在开发人员使用spring开发应用程序时,你是看不到new关键字的,所有对象都应该从容器里获得,它们的生命周期在放 ...
 - 卷积神经网络 CNN 学习笔记
			
激活函数Relu 最近几年卷积神经网络中,激活函数往往不选择sigmoid或tanh函数,而是选择relu函数.Relu函数的定义 $$f(x)= max(0,x)$$ Relu函数图像如下图所示: ...
 - 微信小程序开发07-列表页面怎么做
			
接上文:微信小程序开发06-一个业务页面的完成 github地址:https://github.com/yexiaochai/wxdemo 我们首页功能基本完成,我对比了下实际工作中的需求,完成度有7 ...
 - 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)
			
简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...
 - 第一册:lesson 121.
			
原文:The man in a hat. question:Why didn't Caroline recognize the customer straight away? I bought tw ...
 - 杂牌机搞机之旅(二)————移植TWRP第三方Recovery并刷入
			
原本想把杂牌机作为android破解和开发的测试机,破解的话肯定是安装框架的嘛,毕竟有些是要涉及到脱壳 . 但是,我尝试安装xposed的时候,手机卡在了开机界面,也就是magisk出现了错误,如果想 ...
 - 覆盖ng-zorro样式(非style.scss)
			
之前发现在模板的样式表里写样式不起作用,然后想起vue里有个/deep/,angular会不会也有一个,果然,发现了一个::ng-deep可以在模板的样式表里覆盖ng-zorro的样式.记录一下(●' ...
 - Python 基于python操纵zookeeper介绍
			
基于python操纵zookeeper介绍 by:授客 QQ:1033553122 测试环境 Win7 64位 Python 3.3.4 kazoo-2.6.1-py2.py3-none-any.w ...
 - Android ViewPager+Fragment 在Activity中获取Fragment的控件
			
如果ViewPager+Fragment实现Tab切换,在activity中利用adapter.getItem获取到fragment然后再根据fragment.的方法获取控件 //隐藏求租,以下代码用 ...