EFcore与动态模型(二)

摘要:
此外,为了方便模型属性数据的操作,添加一些扩展方法,如下所示:publicstaticassRuntimeModelMetaExtensions{//反序列化以获取集合publicstaticRuntimeModelMeta.ModelPropertyMeta〔〕GetProperties{if{return null;}return JsonConvert。反序列化对象;}//将集合序列化为字符串,该字符串用于保存publicstaticvoidSetProperties{meta.Properties=JsonConvert.SerializeObject;}}操作非常简单,但问题是如何在模型信息更改时告诉DbContext。当我们进入第三部分时,我们需要完成配置信息管理。

上篇文章中介绍了如何使用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了,所有的工作做完后,就完全可以实现运行时动态模型配置的功能了。

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

  

免责声明:文章转载自《EFcore与动态模型(二)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SkyWalking 客户端配置PyQt5单元格操作大全下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

论文笔记:(2021CVPR)PAConv: Position Adaptive Convolution with Dynamic Kernel Assembling on Point Clouds

目录 摘要 1、引言 2、相关工作 将点云映射到常规二维或三维栅格(体素) 基于MLPs的点表示学习 基于点卷积的点表示学习 动态卷积和条件卷积 3、方法 3.1 回顾 3.2 动态内核组装 Weight Bank ScoreNet. Kernel generation 3.3 权重正则化 3.4 与前期工作的关系 4、骨干网体系结...

EFcore与动态模型(三)

紧接着上面的内容,我们继续看下动态模型页面交互实现方式,内容如下: 1,如何实现动态表单 2,如何接收表单数据并绑定到动态模型上 一、如何实现动态表单 由于模型信息都是后台自定义配置的,并不是固定不变的结构,所以没有办法直接在页面上写出对应的表单数据,而需要通过解析模型的结构,动态的生成对应的表单。在说具体实现方法前,我们先来看下我们想要达到的效果。 Ht...

对象建模

面向对象建模 建模:为了理解事物而对事物作出的一种抽象,是对事物的一种无歧义的书面描述。 建模的目的:减少复杂性。 面向对象方法最基本的原则:按照人们习惯的思维方式,用面向对象观点建立问题域的模型,开发出尽可能自然地表现求解方法的软件。 用面向对象方法开发软件,通常需要建立3种形式的建模,它们分别是描述系统数据结构的对象模型,描述系统控制结构的动态模型和...