1. 程式人生 > >.net core2.1 自動注入 Entity(實體物件到上下文)

.net core2.1 自動注入 Entity(實體物件到上下文)

概要:有點老套,因為早在 .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(); } }
View Code

   這時候我們可以在 DBContext的 onModelCreating中如下方式注入:

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            
            modelBuilder.ApplyConfiguration(new RoleEntityTypeConfiguration());
           //.....其他實體對映注入。。。
        }
View Code

  這是其中一個實體的對映方式,假設我們有十幾或幾十個,那麼我們需要在這些十幾或者幾十遍,累得慌吧,累就對了,所以換個方式實現:

  我們在定義一個IEntityRegister物件,所有的 所有實體對映類都需要實現這個介面物件,介面如下:

public interface IEntityRegister
    {
        void Apply(ModelBuilder builder);
    }
View Code

  同時修改上面的 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);
        }
    }
View Code  

  這時候我們其他的幾十個 實體的配置物件,依舊按照如上寫法即可,現在我們要做的就是找到所有實現了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;
    }
View Code

  對應實現類:

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();
        }

    }
View Code

  需要注意的是,這個介面以及實現類,需要註冊為 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;
        }
    }
View Code

  這時候我們就可以很簡單的使用了:

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}個");
        }
    }
}
View Code

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);
    }
View Code

  這時候,我們的實體的配置類只需要繼承該類,並實現其方法就可以了,比如:

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();
        }
    }
View Code

   DbContext的 OnModelCreating中不變。

結束!