上篇文章中介绍了如何使用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与动态模型(二)的更多相关文章

  1. EFcore与动态模型

    在开发商城系统的时候,大家会遇到这样的需求,商城系统里支持多种商品类型,比如衣服,手机,首饰等,每一种产品类型都有自己独有的参数信息,比如衣服有颜色,首饰有材质等,大家可以上淘宝看一下就明白了.现在的 ...

  2. EFcore与动态模型(三)

    紧接着上面的内容,我们继续看下动态模型页面交互实现方式,内容如下: 1,如何实现动态表单 2,如何接收表单数据并绑定到动态模型上 一.如何实现动态表单 由于模型信息都是后台自定义配置的,并不是固定不变 ...

  3. EntityFramework Core如何映射动态模型?

    前言 本文我们来探讨下映射动态模型的几种方式,相信一部分童鞋项目有这样的需求,比如每天/每小时等生成一张表,此种动态模型映射非常常见,经我摸索,这里给出每一步详细思路,希望能帮助到没有任何头绪的童鞋, ...

  4. (动态模型类,我的独创)Django的原生ORM框架如何支持MongoDB,同时应对客户使用时随时变动字段

    1.背景知识 需要开发一个系统,处理大量EXCEL表格信息,各种类别.表格标题多变,因此使用不需要预先设计数据表结构的MongoDB,即NoSQL.一是字段不固定,二是同名字段可以存储不同的字段类型. ...

  5. Jquery.Qrcode在客户端动态生成二维码并添加自定义Logo

    0 Jquery.Qrcode简介 Jquery.Qrcode.js是一个在浏览器端基于Jquery动态生成二维码的插件,支持Canvas和Table两种渲染方式,它的优点是在客户端动态生成,减轻了服 ...

  6. UML动态模型图简单介绍

    UML动态模型图描述了系统动态行为的各个方面,包括用例图.序列图.协作图.活动图和状态图.下面就每种图做一个简单介绍: 用例图 用例图描述系统外部的执行者与系统提供的用例之间的某种联系.所谓用例是指对 ...

  7. [Unity3D][Vuforia][IOS]vuforia在unity3d中添加自己的动态模型,识别自己的图片,添加GUI,播放视频

    使用环境 unity3D 5 pro vuforia 4 ios 8.1(6.1) xcode 6.1(6.2) 1.新建unity3d工程,添加vuforia 4.0的工程包 Hierarchy中 ...

  8. 动态创建二维vector数组 C和C++ 及指针与引用的区别

    二维vectorvector<vector <int> > ivec(m ,vector<int>(n));    //m*n的二维vector 动态创建m*n的二 ...

  9. OSGI(面向Java的动态模型系统)

    基本简介编辑 OSGI服务平台提供在多种网络设备上无需重启的动态改变构造的功能.为了最小化耦合度和促使这些耦合度可管理,OSGi技术提供一种面向服务的架构,它能使这些组件动态地发现对方.OSGi联 O ...

随机推荐

  1. iOS 程序测试、程序优化、提交前检测

    1. 数据显示如果是数值要考虑到0的情况 2. 数据变化对前一个页面及相关页面的影响,也即数据同步问题.如果是有其它设备改变数据,那数据请求就应该在willappear(视图将要显示事件)进行请求,以 ...

  2. Html 定位position

    CSS position属性和实例应用   目前几乎所有主流的浏览器都支持position属性("inherit"除外,"inherit"不支持所有包括IE8和 ...

  3. idea 15破解方法记录

    So easy! Only one step.     注册时选择 License server ,填 http://idea.lanyus.com  对于Clion等同样适用.

  4. C#子窗口与父窗口交互(使用委托和事件)

    目标:在子窗口Form2上单击按钮时向Form1传递一组自定义参数,并显示在父窗口Form1上. 方法:有很多方法,这里只介绍委托和事件的实现方式. 思路:Form2中定义事件,Form1创建Form ...

  5. java 之 Spring 框架(Java之负基础实战)

    1.Spring是什么 相当于安卓的MVC框架,是一个开源框架.一般用于轻型或中型应用. 它的核心是控制反转(IoC)和面向切面(AOP). 主要优势是分层架构,允许选择使用哪一个组件.使用基本的Ja ...

  6. QT移植

    QT下载地址:http://download.qt.io/archive/qt/1.编译tslib(touch screen lib) 准备工作:确保以下工具安装完成 sudo apt-get ins ...

  7. ubuntu vi编辑insert时上下左右建为ABCD

    ubuntu  在vi编辑insert时上下左右建不能移动光标而是输出ABCD,backspace也不能起删除作用, 开始我退出insert模式就能够移动和删除了,不过这样太麻烦很不适应, 只要一次执 ...

  8. js模块化开发——前端模块化

    在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可:如今CPU.浏览器性能得到了极大的提升,很多页面逻辑迁移到了客 户端(表单验证等),随着web2.0时代的到来,Ajax技术 ...

  9. --@angularJS--一个最简单的指令demo

    <!DOCTYPE HTML><html ng-app="app"><head>    <title>custom-directiv ...

  10. Unity跨平台原理

    An ahead-of-time (AOT) compiler is a compiler that implements ahead-of-time compilation. This refers ...