zoukankan      html  css  js  c++  java
  • EFcore与动态模型(二)

    上篇文章中介绍了如何使用ef进行动态类型的管理,比如我们定义了ShopDbContext并且注册了动态模型信息,下面的代码实现了动态信息的增加:

    Type modelType = IRuntimeModelProvider.GetType(1);//获取id=1的模型类型
    object obj = Activator.CreateInstance(modelType);//创建实体
    entity = obj as DynamicEntity;//类型转换,目的是进行赋值
    entity["Id"]=1;
    entity["Name"]="名称";
    ShopDbContext.Add(entity);
    ShopDbContext.SaveChanges();
    

    上面的方式只能在程序运行前,先把模型配置好,然后再启动程序,无法做到程序运行期间动态改变模型的信息,现在我们来改进下前面的功能:

    1,实现在线的模型结构配置管理

    2,模型配置变化后动态生成数据库表

    3,运行时注册模型信息到DbContext

    一、实现在线的模型结构配置管理

    上一篇文章内容中,我们是把模型信息保存了配置文件中,程序启动时加载配置文件并解析,完成模型的编译。现在我们要把配置信息放到数据库中,用一个数据表存储配置信息。首先改造下前面提到的RuntimeModelMeta类,代码如下:

    public class RuntimeModelMeta
       {
           public int ModelId { get; set; }
           public string ModelName { get; set; }//模型名称
           public string ClassName { get; set; }//类名称
           public string Properties{get;set;}//属性集合json序列化结果
            
           public class ModelPropertyMeta
           {
               public string Name { get; set; }//对应的中文名称
               public string PropertyName { get; set; } //类属性名称 
          public int Length { get; set; }//数据长度,主要用于string类型
     
           public bool IsRequired { get; set; }//是否必须输入,用于数据验证
           public string ValueType { get; set; }//数据类型,可以是字符串,日期,bool等
           }
       }
    

      就是把 public ModelPropertyMeta[] ModelProperties { get; set; }属性改成了String类型,然后我们直接定义个用于模型配置管理的DbContext,代码如下:

      public class ModelDbContext : DbContext
        {
            public ModelDbContext(DbContextOptions<ShopDbContext> options) :base(options)
            {
            }
    
            public DbSet<RuntimeModelMeta> Metas { get; set; }
        }
    

      有了这个DbContext,操作RuntimeModelMeta就比较简单了。另外为了方便模型属性数据的操作,增加一些扩展方法,如下:

    public static class RuntimeModelMetaExtensions
        {
            //反序列化获得集合
            public static RuntimeModelMeta.ModelPropertyMeta[] GetProperties(this RuntimeModelMeta meta)
            {
                if (string.IsNullOrEmpty(meta.Properties))
                {
                    return null;
                }
    
                return JsonConvert.DeserializeObject<RuntimeModelMeta.ModelPropertyMeta[]>(meta.Properties);
            }
          //把集合序列化成字符串,用于保存
            public static void SetProperties(this RuntimeModelMeta meta, RuntimeModelMeta.ModelPropertyMeta[] properties)
            {
                meta.Properties = JsonConvert.SerializeObject(properties);
            }
        }
    

      

      操作很简单,但是问题是模型信息变化时如何告诉DbContext,我们到第三部分的时候,再详细说,这里只需要完成配置信息管理即可。

    二、模型配置变化后动态生成数据库表

     我们这里直接采用SQL语句来操作数据库,下面是简单的封装类:

    public static class ModelDbContextExtensions
        {
           //添加字段
            public static void AddField(this ModelDbContext context, RuntimeModelMeta model, RuntimeModelMeta.ModelPropertyMeta property)
            {
                using (DbConnection conn = context.Database.GetDbConnection())
                {
                    if (conn.State != System.Data.ConnectionState.Open)
                    {
                        conn.Open();
                    }
    
                    DbCommand addFieldCmd = conn.CreateCommand();
                    addFieldCmd.CommandText = $"alert table {model.ClassName} add {property.PropertyName} ";
    
                    switch (property.ValueType)
                    {
                        case "int":
                            addFieldCmd.CommandText += "int";
                            break;
                        case "datetime":
                            addFieldCmd.CommandText += "datetime";
                            break;
                        case "bool":
                            addFieldCmd.CommandText += "bit";
                            break;
                        default:
                            addFieldCmd.CommandText += "nvarchar(max)";
                            break;
                    }
    
                    addFieldCmd.ExecuteNonQuery();
                }
            }
            //删除字段
            public static void RemoveField(this ModelDbContext context, RuntimeModelMeta model,string property)
            {
                using (DbConnection conn = context.Database.GetDbConnection())
                {
                    if (conn.State != System.Data.ConnectionState.Open)
                    {
                        conn.Open();
                    }
    
                    DbCommand removeFieldCmd = conn.CreateCommand();
                    removeFieldCmd.CommandText = $"alert table {model.ClassName} DROP COLUMN  {property}";
    
                    removeFieldCmd.ExecuteNonQuery();
                }
            }
            //创建模型表
            public static void CreateModel(this ModelDbContext context,RuntimeModelMeta model)
            {
                using (DbConnection conn = context.Database.GetDbConnection())
                {
                    if (conn.State != System.Data.ConnectionState.Open)
                    {
                        conn.Open();
                    }
    
                    DbCommand createTableCmd = conn.CreateCommand();
                    createTableCmd.CommandText = $"create table {model.ClassName}";
                    createTableCmd.CommandText += "{id int identity(1,1)";
                    foreach (var p in model.GetProperties())
                    {
                        createTableCmd.CommandText += $",{p.PropertyName} ";
                        switch (p.ValueType)
                        {
                            case "int":
                                createTableCmd.CommandText += "int";
                                break;
                            case "datetime":
                                createTableCmd.CommandText += "datetime";
                                break;
                            case "bool":
                                createTableCmd.CommandText += "bit";
                                break;
                            default:
                                createTableCmd.CommandText += "nvarchar(max)";
                                break;
                        }
                    }
    
                    createTableCmd.CommandText += "}";
                    createTableCmd.ExecuteNonQuery();
                }
    
            }
        }
    

      

     在模型配置信息发生变化的时候,通过上面的封装类直接操作数据库完成数据表结构的变化,当然这里提供的方法很少,大家可以再扩展,比如修改字段类型,删除表等操作。

    三、运行是注册模型信息到DbContext

    我们在前面通过重写OnModelCreating方法,注册模型到DbContext,但是这个方法只会被执行一次,在运行时期间如果模型信息发生了变化,DbContext是无法同步的,所以这个方法就行不通了。DbContext还提供了另外一个方法叫void OnConfiguring(DbContextOptionsBuilder optionsBuilder),这个方法在每次实例化DbContext的时候都会被调用,那我们如何利用这个方法完成模型信息的注册。这个方法包含一个参数DbContextOptionsBuilder,这个类型提供了一个方法,可以让我们注册模型,方法如下:

    DbContextOptionsBuilder UseModel(IModel model)

    IModel就是模型信息维护的类,自然我们会想到,自己去创建一个IModel,然后通过上面的方法完成注册。那现在的问题是IModel如何得到?我们重写OnModelCreating方法的时候发现有一个ModelBuilder参数,从这个类型的名字我们可能立马想到,能不能通过它得到我们所需要的信息?通过查看EntityFramework.Core源码,发现它就是我们要找的东西。首先我们看下ModelBuilder的构造方法:

    ModelBuilder(ConventionSet conventions)

    它需要一个ConventionSet,直接翻译过来就是约束的集合(如果有错误欢迎大家拍砖),那如何得到这样的对象?通过查看ef源码,框架里通过IConventionSetBuilder创建的ConventionSet,所以我们也用它,我们先在DbContext中通过依赖注入的方式引用ICoreConventionSetBuilder,代码如下:

    public class ShopDbContext:DbContext
        {
            private readonly ICoreConventionSetBuilder _builder;
            public ShopDbContext(DbContextOptions<ShopDbContext> options, ICoreConventionSetBuilder builder) :base(options)
            {
                _builder = builder;
            }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //完成ModelBuilder实例化
                var modelBuilder = new ModelBuilder(_builder.CreateConventionSet());
                
            }
        }

      有了ModelBuilder后,我们可以通过ModelBuilder.Model获取一个IMutableModel,通过这个对象可以完成模型信息注册,代码如下:

    public class ShopDbContext:DbContext
    {     
         private readonly ICoreConventionSetBuilder _builder;
            private readonly IRuntimeModelProvider _modelProvider;
            public ShopDbContext(DbContextOptions<ShopDbContext> options, ICoreConventionSetBuilder builder, IRuntimeModelProvider modelProvider) :base(options)
            {
                _builder = builder;
                _modelProvider = modelProvider;
            }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                var modelBuilder = new ModelBuilder(_builder.CreateConventionSet());
                //_modelProvider就是上一篇文章提到的,但是实现上需要修改下,因为现在的模型信息是存到数据库中了
                Type[] runtimeModels = _modelProvider.GetTypes();
                foreach (var item in runtimeModels)
                {
                    //添加模型信息
                    modelBuilder.Model.AddEntityType(item);
                }
                //完成注册
                optionsBuilder.UseModel(modelBuilder.Model);
                base.OnConfiguring(optionsBuilder);
            }
    }
    

      

      

     这样我们就完成了注册动态模型信息的功能。如果生成的表名称需要个性化,我们可以通过下面的方式修改:

     modelBuilder.Model.AddEntityType(item).SqlServer().TableName=""

     由于我们在上面用到了ICoreConventionSetBuilder,所以我们需要在Startup中需要调用AddEntityFramework进行服务注册,代码如下:

         public void ConfigureServices(IServiceCollection services)
            {
                。。。。。。
                services.AddEntityFramework().AddDbContext<ShopDbContext>(option => {
                    option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), sql => {
                        sql.UseRowNumberForPaging();
                        sql.MaxBatchSize(50);
    
                    });
                });
             
                。。。。。。
            }
    

      

    我们上面提到,OnConfiguring方法在每次DbContext实例化的时候都会调用,那我们的模型信息每次都要build一下,也不是很好,ef是采用了缓存的办法,那我们自然也可以采用。最终ShopDbContext的完整代码如下:

     public class ShopDbContext:DbContext
        {
            private readonly ICoreConventionSetBuilder _builder;
            private readonly IRuntimeModelProvider _modelProvider;
            private readonly IMemoryCache _cache;
            private static string DynamicCacheKey = "DynamicModel";
            public ShopDbContext(DbContextOptions<ShopDbContext> options, ICoreConventionSetBuilder builder, IRuntimeModelProvider modelProvider, IMemoryCache cache) :base(options)
            {
                _builder = builder;
                _modelProvider = modelProvider;
                _cache = cache;
            }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //直接从缓存读取model,如果不存在再build
                IMutableModel model = _cache.GetOrCreate(DynamicCacheKey, entry => {
                    var modelBuilder = new ModelBuilder(_builder.CreateConventionSet());
                    Type[] runtimeModels = _modelProvider.GetTypes();
                    foreach (var item in runtimeModels)
                    {
                        modelBuilder.Model.AddEntityType(item).SqlServer().TableName = "";
                    }
                    _cache.Set(DynamicCacheKey, modelBuilder.Model);
                    return modelBuilder.Model;
                });
                
    
                optionsBuilder.UseModel(model);
                base.OnConfiguring(optionsBuilder);
            }
    

     当模型配置发生变化时,把缓存清理一下,这样下次再访问的时候,就能够按照新的配置重新Build。

     Ok了,所有的工作做完后,就完全可以实现运行时动态模型配置的功能了。

       后面的文章会继续介绍动态模型与动态表单的实现方法。

      

  • 相关阅读:
    UITableView的简单使用
    Oracle相关博客转移
    使用Outlook 2010,拖拽大于20M附件发生“附件大小超过了允许的范围”提示的解决方法
    Use OpenLDAP as security provider in Oracle UCM 11g
    PostBuild event不能注册到全局缓存解决办法
    WIN7下丢失的光驱解决办法:由于其配置信息不完整或已损坏,Windows 无法启动这个硬件设备
    我与计算机的故事
    nova http 409 虚拟机状态重置
    百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换(JS版代码)
    未能从程序集加载类型
  • 原文地址:https://www.cnblogs.com/dxp909/p/6478250.html
Copyright © 2011-2022 走看看