在开发商城系统的时候,大家会遇到这样的需求,商城系统里支持多种商品类型,比如衣服,手机,首饰等,每一种产品类型都有自己独有的参数信息,比如衣服有颜色,首饰有材质等,大家可以上淘宝看一下就明白了。现在的问题是,如果我程序发布后,要想增加一种新的商品类型怎么办,如果不在程序设计时考虑这个问题的话,可能每增加一个商品类型,就要增加对应商品类型的管理程序,并重新发布上线,对于维护来说成本会很高。有没有简单的方式可以快速增加新类型的支持?下面介绍的方案是这样的,首先把模型以配置的方式保存到配置文件中,在程序启动时解析模型信息编译成具体的类,然后通过ef实现动态编译类的数据库操作,如果新增类型,首先改下配置文件,然后在数据库中创建对应的数据库表,重启应用程序即可。

  要实现这样的功能,需要解决以下几个问题:

  1,如何实现动态模型的配置管理

  2,如何根据模型配置在运行时动态生成类型

  3,如何让ef识别动态类型

  4,如何结合ef对动态类型信息进行操作,比如查询,增加等

  一、如何实现动态模型的配置管理

  这个问题解决的方案是,把模型的信息作为系统的一个配置文件,在系统运行时可以获取到模型配置信息。

  首先定义一个类表示一个动态模型,代码如下:

 public class RuntimeModelMeta
{
public int ModelId { get; set; }
public string ModelName { get; set; }//模型名称
public string ClassName { get; set; }//类名称
public ModelPropertyMeta[] ModelProperties { get; set; } 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 class RuntimeModelMetaConfig
{
public RuntimeModelMeta[] Metas { get; set; }
}

  增加配置文件,文件名称为runtimemodelconfig.json,结构如下:

{
"RuntimeModelMetaConfig": {
"Metas": [
{
"ModelId": 1,
"ModelName": "衣服",
"ClassName": "BareDiamond", "ModelProperties": [
{
"Name": "尺寸",
"PropertyName": "Size",
},
{
"Name": "颜色",
"PropertyName": "Color",
}
]
}
]
}
}

  下一步再asp.net core mvc的Startup类的构造方法中加入配置文件

    public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("runtimemodelconfig.json", optional:true,reloadOnChange:true)
.AddEnvironmentVariables(); if (env.IsDevelopment())
{
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
}

  然后再public void ConfigureServices(IServiceCollection services)方法中,获取到配置信息,代码如下:

     public void ConfigureServices(IServiceCollection services)
{
。。。。。。
services.Configure<RuntimeModelMetaConfig>(Configuration.GetSection("RuntimeModelMetaConfig"));
   。。。。。。
}

  到此就完成了配置信息的管理,在后续代码中可以通过依赖注入方式获取到IOptions<RuntimeModelMetaConfig>对象,然后通过IOptions<RuntimeModelMetaConfig>.Value.Metas获取到所有模型的信息。为了方便模型信息的管理,我这里定义了一个IRuntimeModelProvider接口,结构如下:

public interface IRuntimeModelProvider
{
Type GetType(int modelId);
     Type[] GetTypes();
}

   IRuntimeModelProvider.GetType方法可以通过modelId获取到对应的动态类型Type信息,GetTypes方法返回所有的动态类型信息。这个接口实现请看下面介绍。

  二、如何根据模型配置在运行时动态生成类型

  我们有了上面的配置后,需要针对模型动态编译成对应的类。C#提供了多种运行时动态生成类型的方式,下面我们介绍通过Emit来生成类,上面的配置信息比较适合模型配置信息的管理,对于生成类的话我们又定义了一个方便另外一个类,代码如下:

  public class TypeMeta
{
public TypeMeta()
{
PropertyMetas = new List<TypePropertyMeta>();
AttributeMetas = new List<AttributeMeta>();
}
public Type BaseType { get; set; }
public string TypeName { get; set; }
public List<TypePropertyMeta> PropertyMetas { get; set; }
public List<AttributeMeta> AttributeMetas { get; set; } public class TypePropertyMeta
{
public TypePropertyMeta()
{
AttributeMetas = new List<AttributeMeta>();
}
public Type PropertyType { get; set; }
public string PropertyName { get; set; }
public List<AttributeMeta> AttributeMetas { get; set; }
} public class AttributeMeta
{
public Type AttributeType { get; set; }
public Type[] ConstructorArgTypes { get; set; }
public object[] ConstructorArgValues { get; set; }
public string[] Properties { get; set; }
public object[] PropertyValues { get; set; }
}
}

  上面的类信息更接近一个类的定义,我们可以把一个RuntimeModelMeta转换成一个TypeMeta,我们把这个转换过程放到IRuntimeModelProvider实现类中,实现代码如下:

public class DefaultRuntimeModelProvider : IRuntimeModelProvider
{
private Dictionary<int, Type> _resultMap;
private readonly IOptions<RuntimeModelMetaConfig> _config;
private object _lock = new object();
public DefaultRuntimeModelProvider(IOptions<RuntimeModelMetaConfig> config)
{
//通过依赖注入方式获取到模型配置信息
_config = config;
}
   //动态编译结果的缓存,这样在获取动态类型时不用每次都编译一次
public Dictionary<int, Type> Map
{
get
{
if (_resultMap == null)
{
lock (_lock)
{
_resultMap = new Dictionary<int, Type>(); foreach (var item in _config.Value.Metas)
{
//根据RuntimeModelMeta编译成类,具体实现看后面内容
var result = RuntimeTypeBuilder.Build(GetTypeMetaFromModelMeta(item));
//编译结果放到缓存中,方便下次使用
_resultMap.Add(item.ModelId, result);
}
}
}
return _resultMap;
}
} public Type GetType(int modelId)
{
Dictionary<int, Type> map = Map;
Type result = null;
if (!map.TryGetValue(modelId, out result))
{
throw new NotSupportedException("dynamic model not supported:" + modelId);
}
return result;
} public Type[] GetTypes()
{
int[] modelIds = _config.Value.Metas.Select(m => m.ModelId).ToArray();
return Map.Where(m => modelIds.Contains(m.Key)).Select(m => m.Value).ToArray();
}
//这个方法就是把一个RuntimeModelMeta转换成更接近类结构的TypeMeta对象
private TypeMeta GetTypeMetaFromModelMeta(RuntimeModelMeta meta)
{
TypeMeta typeMeta = new TypeMeta();
//我们让所有的动态类型都继承自DynamicEntity类,这个类主要是为了方便属性数据的读取,具体代码看后面
typeMeta.BaseType = typeof(DynamicEntity);
typeMeta.TypeName = meta.ClassName; foreach (var item in meta.ModelProperties)
{
TypeMeta.TypePropertyMeta pmeta = new TypeMeta.TypePropertyMeta();
pmeta.PropertyName = item.PropertyName;
//如果必须输入数据,我们在属性上增加RequireAttribute特性,这样方便我们进行数据验证
if (item.IsRequired)
{
TypeMeta.AttributeMeta am = new TypeMeta.AttributeMeta();
am.AttributeType = typeof(RequiredAttribute);
am.Properties = new string[] { "ErrorMessage" };
am.PropertyValues = new object[] { "请输入" + item.Name };
pmeta.AttributeMetas.Add(am);
} if (item.ValueType == "string")
{
pmeta.PropertyType = typeof(string);
TypeMeta.AttributeMeta am = new TypeMeta.AttributeMeta();
//增加长度验证特性
am.AttributeType = typeof(StringLengthAttribute);
am.ConstructorArgTypes = new Type[] { typeof(int) };
am.ConstructorArgValues = new object[] { item.Length };
am.Properties = new string[] { "ErrorMessage" };
am.PropertyValues = new object[] { item.Name + "长度不能超过" + item.Length.ToString() + "个字符" }; pmeta.AttributeMetas.Add(am);
}
else if(item.ValueType=="int")
{
if (!item.IsRequired)
{
pmeta.PropertyType = typeof(int?);
}
else
{
pmeta.PropertyType = typeof(int);
}
}
else if (item.ValueType=="datetime")
{
if (!item.IsRequired)
{
pmeta.PropertyType = typeof(DateTime?);
}
else
{
pmeta.PropertyType = typeof(DateTime);
}
}
else if (item.ValueType == "bool")
{
if (!item.IsRequired)
{
pmeta.PropertyType = typeof(bool?);
}
else
{
pmeta.PropertyType = typeof(bool);
}
}
typeMeta.PropertyMetas.Add(pmeta);
}
return typeMeta;
}
}

  

  DynamicEntity是所有动态类型的基类,主要是方便属性的操作,具体代码如下:

public class DynamicEntity: IExtensible
{
private Dictionary<object, object> _attrs;
public DynamicEntity()
{
_attrs = new Dictionary<object, object>();
}
public DynamicEntity(Dictionary<object,object> dic)
{
_attrs = dic;
}
public static DynamicEntity Parse(object obj)
{
DynamicEntity model = new DynamicEntity();
foreach (PropertyInfo info in obj.GetType().GetProperties())
{
model._attrs.Add(info.Name, info.GetValue(obj, null));
}
return model;
}
public T GetValue<T>(string field)
{
object obj2 = null;
if(!_attrs.TryGetValue(field, out obj2))
{
_attrs.Add(field, default(T));
}
if (obj2 == null)
{
return default(T);
}
return (T)obj2;
} public void SetValue<T>(string field, T value)
{
if (_attrs.ContainsKey(field))
{
_attrs[field] = value;
}
else
{
_attrs.Add(field, value);
}
} [JsonIgnore]
public Dictionary<object, object> Attrs
{
get
{
return _attrs;
}
}
    //提供索引方式操作属性值
public object this[string key]
{
get
{
object obj2 = null;
if (_attrs.TryGetValue(key, out obj2))
{
return obj2;
}
return null;
}
set
{
if (_attrs.Any(m => string.Compare(m.Key.ToString(), key, true) != -1))
{
_attrs[key] = value;
}
else
{
_attrs.Add(key, value);
}
}
}
[JsonIgnore]
public string[] Keys
{
get
{
return _attrs.Keys.Select(m=>m.ToString()).ToArray();
}
} public int Id
{
get
{
return GetValue<int>("Id");
}
set
{
SetValue("Id", value);
}
}
[Timestamp]
[JsonIgnore]
public byte[] Version { get; set; }
}

  

  另外在上面编译类的时候用到了RuntimeTypeBuilder类,我们来看下这个类的实现,代码如下:

public static class RuntimeTypeBuilder
{
private static ModuleBuilder moduleBuilder;
static RuntimeTypeBuilder()
{
AssemblyName an = new AssemblyName("__RuntimeType");
moduleBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run).DefineDynamicModule("__RuntimeType");
}
public static Type Build(TypeMeta meta)
{
TypeBuilder builder = moduleBuilder.DefineType(meta.TypeName, TypeAttributes.Public);
CustomAttributeBuilder tableAttributeBuilder = new CustomAttributeBuilder(typeof(TableAttribute).GetConstructor(new Type[1] { typeof(string)}), new object[] { "RuntimeModel_" + meta.TypeName });
builder.SetParent(meta.BaseType);
builder.SetCustomAttribute(tableAttributeBuilder); foreach (var item in meta.PropertyMetas)
{
AddProperty(item, builder, meta.BaseType);
}
return builder.CreateTypeInfo().UnderlyingSystemType;
} private static void AddProperty(TypeMeta.TypePropertyMeta property, TypeBuilder builder,Type baseType)
{
PropertyBuilder propertyBuilder = builder.DefineProperty(property.PropertyName, PropertyAttributes.None, property.PropertyType, null); foreach (var item in property.AttributeMetas)
{
if (item.ConstructorArgTypes==null)
{
item.ConstructorArgTypes = new Type[0];
item.ConstructorArgValues = new object[0];
}
ConstructorInfo cInfo = item.AttributeType.GetConstructor(item.ConstructorArgTypes);
PropertyInfo[] pInfos = item.Properties.Select(m => item.AttributeType.GetProperty(m)).ToArray();
CustomAttributeBuilder aBuilder = new CustomAttributeBuilder(cInfo, item.ConstructorArgValues, pInfos, item.PropertyValues);
propertyBuilder.SetCustomAttribute(aBuilder);
} MethodAttributes attributes = MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public;
MethodBuilder getMethodBuilder = builder.DefineMethod("get_" + property.PropertyName, attributes, property.PropertyType, Type.EmptyTypes);
ILGenerator iLGenerator = getMethodBuilder.GetILGenerator();
MethodInfo getMethod = baseType.GetMethod("GetValue").MakeGenericMethod(new Type[] { property.PropertyType });
iLGenerator.DeclareLocal(property.PropertyType);
iLGenerator.Emit(OpCodes.Nop);
iLGenerator.Emit(OpCodes.Ldarg_0);
iLGenerator.Emit(OpCodes.Ldstr, property.PropertyName);
iLGenerator.EmitCall(OpCodes.Call, getMethod, null);
iLGenerator.Emit(OpCodes.Stloc_0);
iLGenerator.Emit(OpCodes.Ldloc_0);
iLGenerator.Emit(OpCodes.Ret);
MethodInfo setMethod = baseType.GetMethod("SetValue").MakeGenericMethod(new Type[] { property.PropertyType });
MethodBuilder setMethodBuilder = builder.DefineMethod("set_" + property.PropertyName, attributes, null, new Type[] { property.PropertyType });
ILGenerator generator2 = setMethodBuilder.GetILGenerator();
generator2.Emit(OpCodes.Nop);
generator2.Emit(OpCodes.Ldarg_0);
generator2.Emit(OpCodes.Ldstr, property.PropertyName);
generator2.Emit(OpCodes.Ldarg_1);
generator2.EmitCall(OpCodes.Call, setMethod, null);
generator2.Emit(OpCodes.Nop);
generator2.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getMethodBuilder);
propertyBuilder.SetSetMethod(setMethodBuilder); } }

  主要部分是ILGenerator的使用,具体使用方式大家可以查阅相关资料,这里不再详细介绍。

  三、如何让ef识别动态类型

  在ef中操作对象需要借助DbContext,如果静态的类型,那我们就可以在定义DbContext的时候,增加DbSet<TEntity>类型的属性即可,但是我们现在的类型是在运行时生成的,那怎么样才能让DbContext能够认识这个类型,答案是OnModelCreating方法,在这个方法中,我们把动态模型加入到DbContext中,具体方式如下:

  protected override void OnModelCreating(ModelBuilder modelBuilder)
{
       //_modelProvider就是我们上面定义的IRuntimeModelProvider,通过依赖注入方式获取到实例
Type[] runtimeModels = _modelProvider.GetTypes("product");
foreach (var item in runtimeModels)
{
modelBuilder.Model.AddEntityType(item);
} base.OnModelCreating(modelBuilder);
}

  

  这样在我们DbContext就能够识别动态类型了。注册到DbContext很简单,关键是如何进行信息的操作。

  四、如何结合ef对动态信息进行操作

  我们先把上面的DbContext类补充完整,

  public class ShopDbContext : DbContext
{
private readonly IRuntimeModelProvider _modelProvider;
public ShopDbContext(DbContextOptions<ShopDbContext> options, IRuntimeModelProvider modelProvider)
: base(options)
{
_modelProvider = modelProvider;
} protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Type[] runtimeModels = _modelProvider.GetTypes("product");
foreach (var item in runtimeModels)
{
modelBuilder.Model.AddEntityType(item);
} base.OnModelCreating(modelBuilder);
}
}

  

  在efcore中对象的增加,删除,更新可以直接使用DbContext就可以完成,比如增加代码,

ShopDbContext.Add(entity);
ShopDbContext.SaveChanges();

  更新操作比较简单,比较难解决的是查询,包括查询条件设置等等。国外有大牛写了一个LinqDynamic,我又对它进行了修改,并增加了一些异步方法,代码我就不粘贴到文章里了,大家可以直接下载源码:下载linqdynamic

  LinqDynamic中是对IQueryable的扩展,提供了动态linq的查询支持,具体使用方法大家可以百度。efcore中DbSet泛型定义如下:

  

  public abstract partial class DbSet<TEntity>: IQueryable<TEntity>, IAsyncEnumerableAccessor<TEntity>, IInfrastructure<IServiceProvider>

  不难发现,它就是一个IQueryable<TEntity>,而IQueryable<TEntity>又是一个IQueryable,正好是LinqDynamic需要的类型,所以我们现在需要解决的是根据动态模型信息,获取到一个IQueryable,我采用反射方式获取:

   ShopDbContext.GetType().GetTypeInfo().GetMethod("Set").MakeGenericMethod(type).Invoke(context, null) as IQueryable;

  有了IQueryable,就可以使用LinqDynamic增加的扩展方式,实现动态查询了。查询到的结果是一个动态类型,但是我们前面提到,我们所有的动态类型都是一个DynamicEntity类型,所以我们要想访问某个属性的值的时候,我们可以直接采用索引的方式读取,比如obj["属性"],然后结合RuntimeModelMeta配置信息,就可以动态的把数据呈现到页面上了。

  上面的方案还可以继续改进,可以把配置信息保存到数据库中,在程序中增加模型配置管理的功能,实现在线的模型配置,配置改动可以同步操作数据库表结构,这种方案后续补充上,敬请期待。

EFcore与动态模型的更多相关文章

  1. EFcore与动态模型(二)

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

  2. EFcore与动态模型(三)

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

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

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

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

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

  5. [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中 ...

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

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

  7. 菜鸟学SSH(十八)——Hibernate动态模型+JRebel实现动态创建表

    项目用的是SSH基础框架,当中有一些信息非常相似,但又不尽同样.假设每个建一个实体的话,那样实体会太多.假设分组抽象,然后继承,又不是特别有规律.鉴于这样的情况.就打算让用户自己配置要加入的字段,然后 ...

  8. OSGI 面向Java的动态模型系统

    OSGI (面向Java的动态模型系统) OSGi(Open Service Gateway Initiative)技术是Java动态化模块化系统的一系列规范.OSGi一方面指维护OSGi规范的OSG ...

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

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

随机推荐

  1. Backbone视图渲染React组件

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title&g ...

  2. SQL Select结果增加自增自段(网转)

    http://www.cnblogs.com/haver/archive/2011/07/14/2106349.html/* 方法一*/ SELECT 序号= (SELECT COUNT(客户编号) ...

  3. bzoj2555

    开始时间:19:40 完成时间:21:00 传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2555 题目大意:(1):在当前字符串的后面插入一 ...

  4. HUSTOJ 2796 && SPOJ1811

    传送门:http://begin.lydsy.com/JudgeOnline/problem.php?id=2796 题解:后缀自动机,很裸,但是感觉对后缀自动机还不是特别理解,毕竟我太蒟蒻,等我精通 ...

  5. 基于Python,scrapy,redis的分布式爬虫实现框架

    原文  http://www.xgezhang.com/python_scrapy_redis_crawler.html 爬虫技术,无论是在学术领域,还是在工程领域,都扮演者非常重要的角色.相比于其他 ...

  6. Angular - - angular.identity和angular.noop

    angular.identity 函数返回本身的第一个参数.这个函数一般用于函数风格. 格式:angular.identity() 使用代码: (function () { angular.modul ...

  7. MySQL批量导出以某数字或字母开头的表

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://suifu.blog.51cto.com/9167728/1852178 情景:我 ...

  8. 3.1. 修改托管对象模型(Core Data 应用程序实践指南)

    托管对象模型是会变好的,有时候变化的比较小,什么添加验证规则.修改默认值.修改获取请求模板等.但是设置到结构变化,如添加.删除字段时,需要先把持久化数据区迁移到新的模型版本才行.假如没有提供迁移数据所 ...

  9. Mysql中常用的函数汇总

    Mysql中常用的函数汇总: 一.数学函数abs(x) 返回x的绝对值bin(x) 返回x的二进制(oct返回八进制,hex返回十六进制)ceiling(x) 返回大于x的最小整数值exp(x) 返回 ...

  10. Xcode 设置文件生成时的模板

    1. 目的 设置 Xcode 生成的文件的格式,如姓名.公司等. 2. 步骤 2.1. 找到文件 step 1. 右键Xcode图标 step 2. 显示包内容 step 3. 找到目录 /Conte ...