概要:有點老套,因為早在 .net frmework的時候(core還沒出來),我們在使用 ef(4.。。。6)的時候就已經這么用,這里我在搭建框架,所以隨手寫下,讓后來人直接拿去用用。
1.使用前提
使用前我們一定要明白的是,通過fluent api去映射實體關系和屬性的,也就是說core里面,要實現IEntityTypeConfiguration<TEntity>接口對象,示例如下:

public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole> { public override void Configure(EntityTypeBuilder<UserRole> builder) { builder.HasMany(x => x.UserRolePermissionCollection).WithOne(x => x.UserRole).HasForeignKey(x => x.UserRoleID).IsRequired(); builder.HasDataRole(); } }
這時候我們可以在 DBContext的 onModelCreating中如下方式注入:

public class DbContextBase : DbContext, IDbContext { private readonly IEntityConfigurationFinder _configurationFinder; public DbContextBase(DbContextOptions options, IEntityConfigurationFinder configurationFinder) : base(options) { _configurationFinder = configurationFinder; } protected override void OnModelCreating(ModelBuilder modelBuilder) { Type contextType = GetType(); IEntityRegister[] entityConfigures = _configurationFinder.GetEntityRegisters(); foreach (var config in entityConfigures) { config.Apply(modelBuilder); } } }
這是其中一個實體的映射方式,假設我們有十幾或幾十個,那么我們需要在這些十幾或者幾十遍,累得慌吧,累就對了,所以換個方式實現:
我們在定義一個IEntityRegister對象,所有的 所有實體映射類都需要實現這個接口對象,接口如下:

public interface IEntityRegister { void Apply(ModelBuilder builder); }
同時修改上面的 roleEntityTypeConfiguration

public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole>,IEntityRegister { public override void Configure(EntityTypeBuilder<UserRole> builder) { builder.HasMany(x => x.UserRolePermissionCollection).WithOne(x => x.UserRole).HasForeignKey(x => x.UserRoleID).IsRequired(); builder.HasDataRole(); } public void Apply(ModelBuilder modelBuilder){ modelBuilder.ApplyConfiguration(this); } }
這時候我們其他的幾十個 實體的配置對象,依舊按照如上寫法即可,現在我們要做的就是找到所有實現了IEntityRegister接口的對象,也就是實體的映射對象。
2.查找實體配置對象
之前我們在上一篇說 dependencyInjection對象的時候,有寫過一個類,其中查找程序及對象的方法,這里我們就又用到了,再貼一次完整的:
接口實現:

/// <summary> /// 查找應用程序中的程序集對象 /// </summary> public interface IAppAssemblyFinder { /// <summary> /// 查詢所有程序集對象 /// </summary> /// <param name="filterAssembly">是否排除非業務程序集對象</param> /// <returns></returns> Assembly[] FindAllAssembly(bool filterAssembly = true); /// <summary> /// 獲取指定類型的對象集合 /// </summary> /// <typeparam name="ItemType">指定的類型</typeparam> /// <param name="expression"> /// 過濾表達式: /// 查詢接口(type=>typeof(ItemType).IsAssignableFrom(type)); /// 查詢實體:type => type.IsDeriveClassFrom<ItemType>() /// </param> /// <param name="fromCache">是否從緩存查詢</param> /// <returns></returns> Type[] FindTypes<ItemType>(Func<Type, bool> expression, bool fromCache = true) where ItemType : class; }
對應實現類:

public class AppAssemblyFinder : IAppAssemblyFinder { private List<Assembly> _assemblies = new List<Assembly>(); public Assembly[] FindAllAssembly(bool filterAssembly = true) { var filter = new string[]{ "System", "Microsoft", "netstandard", "dotnet", "Window", "mscorlib", "Newtonsoft", "Remotion.Linq" }; //core中獲取依賴對象的方式 DependencyContext context = DependencyContext.Default; if (context != null) { List<string> names = new List<string>(); string[] dllNames = context.CompileLibraries.SelectMany(m => m.Assemblies).Distinct().Select(m => m.Replace(".dll", "")).ToArray(); if (dllNames.Length > 0) { names = (from name in dllNames let index = name.LastIndexOf('/') + 1 select name.Substring(index)) .Distinct() .WhereIf(name => !filter.Any(name.StartsWith), filterAssembly) .ToList(); } return LoadFromFiles(names); } //傳統方式 string pathBase = AppDomain.CurrentDomain.BaseDirectory; string[] files = Directory.GetFiles(pathBase, "*.dll", SearchOption.TopDirectoryOnly) .Concat(Directory.GetFiles(pathBase, ".exe", SearchOption.TopDirectoryOnly)) .ToArray(); if (filterAssembly) { files = files.WhereIf(f => !filter.Any(n => f.StartsWith(n, StringComparison.OrdinalIgnoreCase)), filterAssembly).Distinct().ToArray(); } _assemblies = files.Select(Assembly.LoadFrom).ToList(); return _assemblies.ToArray(); } /// <summary> /// 獲取指定類型的對象集合 /// </summary> /// <typeparam name="ItemType">指定的類型</typeparam> /// <param name="expression"> 過濾表達式: 查詢接口(type=>typeof(ItemType).IsAssignableFrom(type)); 查詢實體:type => type.IsDeriveClassFrom<ItemType>()</param> /// <param name="fromCache">是否從緩存查詢</param> /// <returns></returns> public Type[] FindTypes<ItemType>(Func<Type, bool> expression, bool fromCache = true) where ItemType : class { List<Assembly> assemblies; if (fromCache) assemblies = _assemblies; if (_assemblies == null || _assemblies.Count() == 0) assemblies = this.FindAllAssembly().ToList(); Type[] types = _assemblies.SelectMany(a => a.GetTypes()) .Where(expression).Distinct().ToArray(); return types; } /// <summary> /// 從文件加載程序集對象 /// </summary> /// <param name="files">文件(名稱集合)</param> /// <returns></returns> private static Assembly[] LoadFromFiles(List<string> files) { List<Assembly> assemblies = new List<Assembly>(); files?.ToList().ForEach(f => { AssemblyName name = new AssemblyName(f); try { Assembly assembly = Assembly.Load(name); assemblies.Add(assembly); } catch { } }); return assemblies.ToArray(); } }
需要注意的是,這個接口以及實現類,需要注冊為 singleton對象,保證生命周期和應用程序一致,否則,參數的fromCache無效,性能也會急劇下降。
查找IEntityRegister對象:

public class EntityConfigFinder : IEntityConfigFinder { public EntityConfigFinder(IAppAssemblyFinder assemblyFinder) { _assemblyFinder = assemblyFinder; } private readonly IAppAssemblyFinder _assemblyFinder; public IEntityRegister[] EntityRegisters() { var baseType = typeof(IEntityRegister); var types = _assemblyFinder.FindTypes<IEntityRegister>(type => baseType.IsAssignableFrom(type)); var entityRegisters = types.Select(t => (IEntityRegister)Activator.CreateInstance(t))?.ToArray(); return entityRegisters; } }
這時候我們就可以很簡單的使用了:
3.使用

public class DbContextBase : DbContext, IDbContext { public DbContextBase(DbContextOptions options, IEntityConfigFinder entityFinder) : base(options) { _entityConfigFinder = entityFinder; } private readonly IEntityConfigFinder _entityConfigFinder; protected override void OnModelCreating(ModelBuilder modelBuilder) { var dbContextType = GetType(); IEntityRegister[] entityRegisters = _entityConfigFinder.EntityRegisters(); foreach (var entityConfig in entityRegisters) { entityConfig.RegistTo(modelBuilder); Console.WriteLine($"成功注冊實體:{entityConfig.EntityType}"); } Console.WriteLine($"成功注冊實體:{entityRegisters.Length}個"); } } }
4.其他
在 ef(6.x)中我們使用EntityTypeConfiguration的時候,可以直接使用該對象,但是core中沒有了,所以我們可以再封裝一個實現類:

public abstract class EntityTypeConfigurationBase<TEntity, TKey> : IEntityTypeConfiguration<TEntity>, IEntityRegister where TEntity : class, IEntity<TKey> { /// <summary> /// 將當前實體類映射對象注冊到數據上下文模型構建器中 /// </summary> /// <param name="modelBuilder">上下文模型構建器</param> public void Apply(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(this); } /// <summary> /// 重寫以實現實體類型各個屬性的數據庫配置 /// </summary> /// <param name="builder">實體類型創建器</param> public abstract void Configure(EntityTypeBuilder<TEntity> builder); }
這時候,我們的實體的配置類只需要繼承該類,並實現其方法就可以了,比如:

public class UserRoleConfiguration : EntityTypeConfigurationBase<UserRole, Guid> { public override void Configure(EntityTypeBuilder<UserRole> builder) { builder.HasMany(x => x.UserRolePermissionCollection).WithOne(x => x.UserRole).HasForeignKey(x => x.UserRoleID).IsRequired(); builder.HasDataRole(); } }
DbContext的 OnModelCreating中不變。
結束!