使用反射创建用于通过模板进行动态方法调用的工厂类
如何使用反射动态调用模板
引言
我的团队正在使用 Entity Framework,并且我们试图将自己从返回与客户紧密绑定的数据合同的遗留框架中解耦出来。我们使用扩展方法将实体转换为数据合同。这实际上比听起来要复杂,因为我们还有一个业务实体概念,它嵌入在我们的数据合同中。两者都不实现接口,因此从一个到另一个以及从另一个转换回来需要一系列循环和逻辑开关。
此外,我们在数据访问层中使用称为存储库的类,这些类调用 Entity Framework,然后通过扩展方法将 Entity Framework 实体映射到数据合同和业务实体。
总之,问题来了
- 我们的实体存储库与数据合同和实体紧密耦合,并且只返回数据合同。
- 我们的数据合同和业务实体是紧密耦合的,并且不返回接口。
- 我们已经有很多想要重用的扩展方法。
这是提议
- 我想在实体存储库和映射器类之间创建一个工厂,以便我们可以将实体存储库与数据合同和业务实体解耦。
- 我想重用现有的扩展方法。
- 我想编写新的、针对特定合同的映射器类,而不是扩展方法。
- 我想考虑重载方法。
我还希望工厂方法尽可能通用和灵活,我不想让工厂了解映射器、实体和返回类型。我决定使用反射,但以一种非常优化的方式,以便我能保持良好的性能。如果你想了解更多关于应用程序性能陷阱和反射的知识,Joel Pobar 在 MSDN 杂志上有一篇非常好的文章;你可以在 这里 阅读。
Using the Code
方法 A
首先,用于重用数据扩展的工厂方法。由于遗留的 EF 映射器类是一个包含重载 TODC()
方法的扩展方法类,因此无法传入要调用的方法,所以我必须检查方法的输入和输出类型,看它是否与合同的输入和输出类型匹配。让我们创建我的工厂类的签名。
/// <summary>
/// Factory class for mapping entity framework entities to their output types
/// <summary>
public class EntityFactory
{
public OutputType Create<OutputType, InputType>(InputType entity,
Type mapperTemplate) where OutputType : class
{}
}
好的,没问题;我有一个通用的返回类型、输入类型、映射器模板类型和一些约束。注意:mapperTemplate
是我想要重用的带有扩展方法的 static
扩展类。
使用反射,我将在映射器模板类中查找方法,然后检查返回类型和输入参数。我需要使用反射的 MemberInfo
来做到这一点。正如你在下面看到的,一旦我找到方法,我就调用它并返回结果。这样做是可行的,并且是经过优化的,但由于我正在重用现有的扩展方法类,所以它不如它可能的那样优化。
注意:如果这看起来有点乱,请继续看;帖子后面会更清晰。
public OutputType Create<OutputType, InputType>(
InputType entity, Type mapperTemplate) where OutputType : class
{
Type[] inputTypes = new Type[1] { typeof(InputType) };
foreach (MemberInfo mi in mapperTemplate.GetMethods())
{
if (((MethodInfo)mi).ReturnType == typeof(OutputType))
{
MethodInfo method =
mapperTemplate.GetMethod(((MethodInfo)mi).Name,inputTypes);
if (method != null)
{
return method.Invoke(null, new object[] { entity }) as OutputType;
}
}
}
return null;
}
你会注意到,在我获取匹配的返回类型后,我尝试通过名称和传入方法参数类型来检索方法。如果返回了方法,那么我就调用该方法;否则,我再试一次。如果你不传入方法参数类型,你可能会得到一个同名但重载的方法,然后调用时会导致反射抛出“歧义匹配异常”。
这是我在单元测试中调用上述方法的方式
DAL
是数据访问层的命名空间。DC
是我的数据合同的命名空间。
List<MyItem> myItems = new List<MyItem>();
var results = this.repository.SearchResults(criteria);
foreach (var item in results)
{
myItems.Add(entityFactory.Create<DC::Item, DAL::ItemTypeTemplate>(
results, typeof (DAL.EFExtensionMappers))
);
}
方法 B
接下来,我希望我的工厂方法使用新的映射器模板类。由于这个类将有唯一的方法名,我可以直接传入方法名和模板类并调用 Invoke
。非常简单,而且非常优化。
public OutputType Create<OutputType, InputType>(
InputType entity, Type mapperTemplate, string methodName)
where OutputType : class
{
Type[] inputTypes = new Type[1] { typeof(InputType) };
MethodInfo mi = mapperTemplate.GetMethod(methodName, inputTypes);
return mi.Invoke(null, new object[] { entity }) as OutputType;
}
请注意,我再次传入了输入参数类型,以避免由重载方法引起的“歧义匹配异常”。
这是我在单元测试中调用上述方法的方式
DAL
是数据访问层的命名空间。DC
是我的数据合同的命名空间。
List<MyItem> myItems = new List<MyItem>();
var results = this.repository.SearchResults(criteria);
foreach (var item in results)
{
myItems.Add(entityFactory.Create<DC::Item, DAL::ItemTypeTemplate>(
results, typeof(DAL.NewtemMappers), "GetItems")
);
}
到目前为止,我只能调用带有一个参数的方法。现在,我想调用带多个参数的方法。这是我处理这种情况的方式
public OutputType Create<OutputType>(Type[] inputTypes,
object[] arguments, Type mapperTemplate, string methodName)
where OutputType : class
{
MethodInfo mi = mapperTemplate.GetMethod(methodName, inputTypes);
return mi.Invoke(null, arguments) as OutputType;
}
最后,这是我调用此方法的方式
List<MyItem> myItems = new List<MyItem>();
var args = new object[] { results, "test" };
Type[] parms = new Type[2] { typeof(DAL::AgreementTypeTemplate), typeof(String) };
foreach (var item in results)
{
myItems.Add(entityFactory.Create<DC:: Item >(parms, args,
typeof(DAL. ItemTypeTemplate), "GetItems")
);
}
我希望您发现这种动态工厂很有帮助。
在我的博客上阅读更多内容:http://caloia.wordpress.com。
祝好!
历史
- 2010 年 3 月 5 日:初稿