概要:有点老套,因为早在 .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中不变。
结束!