• EFcore与动态模型


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

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

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

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

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

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class RuntimeModelMeta
       {
           public int ModelId { getset; }
           public string ModelName { getset; }//模型名称
           public string ClassName { getset; }//类名称
           public ModelPropertyMeta[] ModelProperties { getset; }
            
           public class ModelPropertyMeta
           {
               public string Name { getset; }//对应的中文名称
               public string PropertyName { getset; } //类属性名称 
          public int Length { getset; }//数据长度,主要用于string类型
     
           public bool IsRequired { getset; }//是否必须输入,用于数据验证
           public string ValueType { getset; }//数据类型,可以是字符串,日期,bool等
           }
       }

      

      然后定义个配置类:

    1
    2
    3
    4
    public class RuntimeModelMetaConfig
      {
          public RuntimeModelMeta[] Metas { getset; }
      }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {
      "RuntimeModelMetaConfig": {
        "Metas": [
          {
            "ModelId": 1,
            "ModelName""衣服",
            "ClassName""BareDiamond",
            
            "ModelProperties": [
              {
                "Name""尺寸",
                "PropertyName""Size",
              },
              {
                "Name""颜色",
                "PropertyName""Color",
              }
            ]
          }
        ]
      }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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)方法中,获取到配置信息,代码如下:

    1
    2
    3
    4
    5
    6
    public void ConfigureServices(IServiceCollection services)
       {
           。。。。。。
           services.Configure<RuntimeModelMetaConfig>(Configuration.GetSection("RuntimeModelMetaConfig"));
          。。。。。。
       }

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

    1
    2
    3
    4
    5
    public interface IRuntimeModelProvider
        {
            Type GetType(int modelId);
         Type[] GetTypes();
        }

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class TypeMeta
      {
          public TypeMeta()
          {
              PropertyMetas = new List<TypePropertyMeta>();
              AttributeMetas = new List<AttributeMeta>();
          }
          public Type BaseType { getset; }
          public string TypeName { getset; }
          public List<TypePropertyMeta> PropertyMetas { getset; }
          public List<AttributeMeta> AttributeMetas { getset; }
     
          public class TypePropertyMeta
          {
              public TypePropertyMeta()
              {
                  AttributeMetas = new List<AttributeMeta>();
              }
              public Type PropertyType { getset; }
              public string PropertyName { getset; }
              public List<AttributeMeta> AttributeMetas { getset; }
          }
     
          public class AttributeMeta
          {
              public Type AttributeType { getset; }
              public Type[] ConstructorArgTypes { getset; }
              public object[] ConstructorArgValues { getset; }
              public string[] Properties { getset; }
              public object[] PropertyValues { getset; }
          }
      }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    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是所有动态类型的基类,主要是方便属性的操作,具体代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    public class DynamicEntity: IExtensible
        {
            private Dictionary<objectobject> _attrs;
            public DynamicEntity()
            {
                _attrs = new Dictionary<objectobject>();
            }
            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<objectobject> 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 { getset; }
        }

      

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    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, nullnew 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中,具体方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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类补充完整,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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);
          }<br>}

      

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

    1
    2
    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配置信息,就可以动态的把数据呈现到页面上了。

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

  • 相关阅读:
    jQuery 插件开发——Menu(导航菜单)
    jQuery 插件开发——PopupLayer(弹出层)
    jQuery 插件开发——GridData(表格)
    angularjs+requlirejs 搭建前端框架(1)
    探讨js闭包
    python的特殊方法介绍
    收集TCP端口的访问延迟和丢包率
    【IT界的厨子】酱香鲈鱼
    Centos 6.5 安装Python 3.7
    python技巧 python2中的除法结果为0
  • 原文地址:https://www.cnblogs.com/huilixieqi/p/6494307.html
Copyright © 2020-2023  润新知