在我们使用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的更多相关文章

  1. 扩展EF的Fluent API中的 OnModelCreating方法 实现全局数据过滤器

    1.生成过滤的表达式目录树 protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression&l ...

  2. EntityFrameworkCore中的实体状态

    Entry表示一个追踪,里面有state属性,是EntityState的枚举类型. 每一个实体都有一个相对应的Entry: var entry = dbContext.ChangeTracker.En ...

  3. EntityFrameworkCore 中的 Attach 方法

    Attach 的坑 Model Filed Database Value Console Value User Phone +123000000000 +12333333333 User Email ...

  4. 在EntityFrameworkCore中记录EF修改日志,保存,修改字段的原始值,当前值,表名等信息

    突发奇想,想把业务修改的所有字段原始值和修改后的值,做一个记录,然后发现使用EF可以非常简单的实现这个功能 覆盖父类中的 SaveShanges() 方法 public new int SaveCha ...

  5. ASP.NET EntityFrameworkCore code first 多对多设计

    摘要:参考网址:https://docs.microsoft.com/zh-cn/ef/core/get-started/full-dotnet/new-db场景:使用ASP.NETEntityFra ...

  6. 如何在AbpNext项目中使用Mysql数据库

    配置步骤: 1.更改.Web项目的appsettings.json的数据库连接字符串.如:server=0.0.0.0;database=VincentAbpTest;uid=root;pwd=123 ...

  7. EntityFrameworkCore数据迁移(一)

    .net core出来已经有很长一段时间了,而EentityFrameworkCore(后面简称EFCore)是.net framework的EntityFramework在.net core中的实现 ...

  8. 在Code First中使用Migrations对实体类和数据库做出变更

    在Code First中使用Migrations对实体类和数据库做出变更,Mirgration包含一系列命令. 工具--库程序包管理器--程序包管理器控制台 运行命令:Enable-Migration ...

  9. ABP框架 将EntityFrameworkCore生成的SQL语句输出到控制台

    首先 在 EntityFrameworkCore中安装 Microsoft.Extensions.Logging.Console nuget install Microsoft.Extensions. ...

随机推荐

  1. 使用 ASP.NET Core MVC 创建 Web API(二)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 六.添加数据库上下文 数据库上下文是使用Entity Framewor ...

  2. java线程通信与协作小结 多线程中篇(十六)

      在锁与监视器中我们对Object中的方法进行了简单介绍 以监视器原理为核心,三个方法:wait,notify.notifyAll,可以完成线程之间的通信 当然,不会像“语言”似的,有多种多样的沟通 ...

  3. 关于px,分辨率,ppi的辨析

    概述  在本篇文章的开始,我先为大家解释一下这三个名词的概念.  px全称为pixel--像素.pc及移动设备的屏幕就是通过往像素矩阵中填充颜色,从而在宏观上体现出图像.像素越小,图像越清晰.  分辨 ...

  4. Python Ast介绍及应用

    Abstract Syntax Trees即抽象语法树.Ast是python源码到字节码的一种中间产物,借助ast模块可以从语法树的角度分析源码结构.此外,我们不仅可以修改和执行语法树,还可以将Sou ...

  5. django-restframework之缓存系统

    django-restframework之缓存系统 一 前言 一 为什么需要缓存 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增.删.查.改,渲染模块,执行业务逻辑,最后生成用户看到的 ...

  6. python 文件和目录操作题库

    1. 把一个目录下所有的文件删除,在所有的目录下新建一个a.txt的文件,并在文件下写入"python"关键字.   解题思路:        1.如果目录存在则切换进入目录    ...

  7. acrobat pdf 按页拆分

    百度 https://jingyan.baidu.com/article/37bce2be7098a21002f3a2f2.html 百度acrobat版本比我的高,操作不同了: 我的方案: 组织页面 ...

  8. Java虚拟机判定对象存活算法

    1.引用计数算法 描述:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器值为0的对象就是不可能再被使用的. 特点:实现简单,判定效率高. ...

  9. iis读取不到本地证书问题

    导入证书时,通过mmc命令打开控制台->添加管理单元或删除单元->选择本地计算机账号->然后导入证书,解决 ssl证书无法与www.xxx通信. 证书导入后,不能正常读取.有两个问题 ...

  10. GDAL坐标转换

    一.引言 最近研究了一下GIS.测绘学的坐标转换的问题,感觉大部分资料专业性太强,上来就是一通专业性论述:但感觉对于相关从业者来说,其实不必了解那么多背景知识的:就通过GDAL这个工具,来简单总结下坐 ...