一、问题:
通过反射,可以查找到某个命名空间下的全部类(Class),但是这时是一个数组Type[],数据的内容是Type ClassType。我需要在循环迭达当中,把这个数组里的类型,当作类型参数,传入另一个泛型的方法Func<TEntity>()当中。
在反射当中,得到的类型是Type ClassType;在泛型当中,类型是TEntity。
在构建泛型方法的时候,我们会只定义类型约束TEntity,具体的TEntity交给调用的时候决定。
但是现在的条件是,调用的时候,也不知道具体是哪一个类型。
从ClassType到TEntity中间缺了一环,缺少的这一环是什么?
二、背景:
我做的项目当中,EF(Entity Framework)使用的是DB First,客户的需求是不断变动的,数据库里面的表、视图需要经常维护,增减字段是家常便饭。所以平常的流程是,先在文档(word doc)当中写好数据库字典,然后去数据库管理器修改表、视图,然后到模型所在的类库修改类,最后运行整个WEB项目,验证模型的映射是否正常。
数据库-EF-DAL,这个架构是以前公司同事设计的,后来我做别的项目,也是直接套用了这个模式。几个项目之后,我经常想,应该可以写出一个单元测试,以后每添加修改新的表/模型,直接在测试里面验证模型映射的对错。于是几天前就动手了,然后就被这个问题卡住了。
三、详细说明
3.1 相关介绍
项目最终的输出是WEB,asp.net MVC。中间还有缓存、XX.BLL什么的。在最底层,XX.DAL封装了EF和一些基础的方法。XX.Model是所有的模型所在的类库。这里XX指代项目的2个字母缩写,或者客户的2个字母缩写,有时候也用我的xpnew前两个字母。
我另外创建了一个类库,XX.DAL.UnitTest。我的目标是在XX.DAL.UnitTest当中找到XX.Model上所有的实体类,可能包含的的“System.Data.Entity.Validation.DbEntityValidationException”和“System.InvalidOperationException.InvalidOperationException”。前者是模型验证的错误,比如说必填字段为空。后者是模型映射错误,比如说数据库里面某个表字段类型是nvarchar,而在XX.Model当中,相应的类的属性定义为int。
3.2 代码
代码1,单元测试方法:
1 [TestMethod] 2 public void TestMethod_循环获取实体() 3 { 4 5 string AssemblyName = "SA.Model"; 6 Assembly asm2 = Assembly.Load(AssemblyName); 7 var All = asm2.GetTypes(); 8 foreach (Type T in All) 9 { 10 string ClassName = AssemblyName + "." + T.Name; 11 if (T.FullName == ClassName && T.IsClass) 12 { 13 Loger.Debug("准备测试" + T.Name); 14 dynamic InstanceModel = asm2.CreateInstance(T.FullName); 15 TryEitity<dynamic>(); 16 } 17 else 18 { 19 Loger.Info(T.FullName + "不是定义的模型内容,跳过"); 20 } 21 } 22 23 }
代码2,单元测试代码,在泛型的方法中输出错误信息
1 private void TryEitity<T>() where T : class 2 { 3 try 4 { 5 var d = new DAO(); 6 d.TestEntity<T>(); 7 8 } 9 catch (DbEntityValidationException DbEx) 10 { 11 foreach (var Err in DbEx.EntityValidationErrors) 12 { 13 object CurrentEntity = Err.Entry.Entity; 14 string TypeName = CurrentEntity.ToString(); 15 StringBuilder sb = new StringBuilder(); 16 17 sb.Append(TypeName); 18 sb.Append("发现实体验证错误:"); 19 foreach (var ProErr in Err.ValidationErrors) 20 { 21 sb.Append(ProErr.PropertyName); 22 sb.Append(" 属性错误:"); 23 sb.Append(ProErr.ErrorMessage); 24 } 25 SA.Tools.Loger.Error("实体验证错误:" + sb.ToString()); 26 } 27 } 28 catch (Exception ex) 29 { 30 //if (ex is System.Data.Entity.Validation.DbEntityValidationException) 31 //{ 32 // var DbEx = ex as DbEntityValidationException; 33 //} 34 35 SA.Tools.Loger.Error("发现异常:" + ex.Message); 36 37 } 38 }
代码3,DAL层,专门提供了一个获取实体的方法,面向单元测试:
1 /// <summary> 2 /// 测试实体,这是专门为了单元测试而准备的方法 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public List<T> TestEntity<T>() where T : class 6 { 7 var AllList = DataContext.Set<T>(); 8 if (null == AllList) 9 { 10 return null; 11 } 12 else 13 { 14 return AllList.ToList(); 15 } 16 }
3.3 已知的不行的方案(也有可能是我没搞明白)
3.3.1 没搜索到确切的办法
在网上找了一下相关的资料,基本上没有专门解决这个问题的。比如说,有人在zhihu上提出了一个问题,也被人解答了,但是那个问题实际上只是动态调用实体的方法、属性,属于反射概念范围的。
我现在面临的问题是要跨跃 反射和泛型两个概念
3.3.2 dynamic 关键字
使用 dynamic关键字的办法已经试过了,上面的代码也是这样。但是 dynamic代入到泛型之后是object,而不是我期待的 class UserInfoT,然后在DAL里面执行的时候,无法映射,抛出异常:"实体类型 Object 不是当前上下文的模型的一部分。"
最后,我有70豆,但是这里好像只能给60个。但是,不管是提供答案的,还是进来看过,都非常感谢!
仍然是反射调用的问题,想到两个方案:
1.直接反射调用泛型方法
http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic-method
2.声明一个泛型类
public class MapValidator<T>
{
public void Validate()
{
TryEitity<T>();
}
}
然后通过反射构造对应类型的Validator,调用Validate()方法。
两个方案原理都一样。
第一个链接是英文的,我看不了。
第2个,你写的,我看不出跟我提出的问题,有什么关联。
我重复一下(稍后会补充到问题里):通过反射,得到的是XX.Model.UserT ,XX.Model.UserV, XX.Model.NewsT,XX.Model.CheckInOutT。。。。。。这样的类,但是反射得到的是Type ClassType,
接下要有一步转换、变换,然后,代入到一个泛型的方法Func<TEntity>()当中。
现在的问题不是怎么把类型参数代入到方法当中,而是Type ClassType怎么变成TEntity。你说的泛型类里面使用的仍然是TEntity。Type ClassType怎么变成TEntity,仍然还是个问题。
[TestMethod] public void InvokeGenericMethod() { var method = this.GetType().GetMethod("DoWork"); var gm = method.MakeGenericMethod(this.GetType()); gm.Invoke(this, null); } public void DoWork<T>() where T : class { Console.WriteLine(typeof(T).FullName); }
上面的代码是用反射调用泛型方法,可以参考一下有没有帮助。