所以我正在使用一个旧的数据模型,我必须在我所处理的范围内工作。
当我执行数据库查询时,模型将数据作为
返回List<Dictionary<string, object>>
每个字典的位置,键是列名,值是列值。你可以想象,使用它是foreach循环和类型转换的噩梦
我希望定义一些POCO视图模型,然后制作一些使用LINQ /反射的东西,以及一个“赋值绑定贴图”,从可怕的返回值转到我漂亮的干净POCO。所以我可以使用列名和lambda来定义“地图”到我的POCO上的属性,类似于...
var Map; // type???
Map.Add("Id", p => p.Id);
Map.Add("Code", p => p.Code);
Map.Add("Description", p => p.Description);
Map.Add("Active", p => p.Active);
然后像这样转换......
List<Dictionary<string, object>> Results = MyModel.Query(...);
List<ProductViewModel> POCOs = new List<ProductViewModel>();
foreach (var Result in Results) // Foreach row
{
ProductViewModel POCO = new ProductViewModel();
foreach (var i in Result) // Foreach column in this row
{
// This is where I need help.
// i.Key is the string name of my column.
// I can get the lambda for this property from my map using this column name.
// For example, need to assign to POCO.Id using the lambda expression p => p.Id
// Or, assign to POCO.Code using the lambda expression p => p.Code
}
POCOs.Add(POCO);
}
return POCOs;
可以使用某种反射来完成,如果是这样,怎么做?
答案 0 :(得分:17)
以下是使用expression trees的方法。首先,定义地图的API:
public class PropertyMap<T> where T : new()
{
public void Add(string sourceName, Expression<Func<T, object>> getProperty);
public T CreateObject(IDictionary<string, object> values);
}
您可以这样使用它:
var map = new PropertyMap<ProductViewModel>();
map.Add("Id", p => p.Id);
map.Add("Code", p => p.Code);
map.Add("Description", p => p.Description);
map.Add("Active", p => p.Active);
var productViewModel = map.CreateObject(values);
要实现它,首先要声明一个字典,将数据源中的名称与属性关联起来:
private readonly IDictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>();
接下来,您将根据该字典实现Add
方法(所有错误处理留作读者的练习):
public void Add(string sourceName, Expression<Func<T, object>> getProperty)
{
_properties[sourceName] = (PropertyInfo) ((MemberExpression) getProperty.Body).Member;
}
然后,您将动态编译一个方法,使用表达式树来完成分配(听起来比它更可怕)。可视化此过程的最简单方法是查看我们正在构建的示例。我们想要的是一些代码:
new ProductViewModel
{
Id = ...,
Code = ...,
Description = ...,
Active = ...
}
但是,由于动态映射,我们无法在编译时知道这一点。因此,我们将构建一个功能,它是确切的代码,但在运行时编译。表达式树只是运行时数据,表示您在编译时可以编写的相同代码。
首先,我们需要为属性获取一组绑定(赋值):
private IEnumerable<MemberBinding> GetPropertyBindings(IDictionary<string, object> values)
{
return
from sourceName in _properties.Keys
select Expression.Bind(_properties[sourceName], Expression.Constant(values[sourceName]));
}
我们在这里说的是,对于映射属性中的每个属性,查找值并使其成为常量(对于Id
,这可能是值7)并将相应的属性绑定到它。这给了我们表达式Id = 7
。我们对所有属性重复此操作,为我们提供所有分配。
一旦我们有了这些绑定,我们就可以创建完整的成员初始化,其中包括构造函数调用:
private MemberInitExpression GetMemberInit(IDictionary<string, object> values)
{
return Expression.MemberInit(Expression.New(typeof(T)), GetPropertyBindings(values));
}
因为我们在类声明中指定了where T : new()
,所以我们保证在这里调用无参数构造函数。我们传入之前创建的属性绑定,为我们提供了一个表示我们想要构建的初始化表达式的数据结构。
那么我们知道什么?我们有这种数据结构,但我们如何调用代码?为此,我们必须将该表达式包装在我们可以调用的函数中,因为实际上调用的唯一方法是一个方法。这意味着我们正在构建如下代码:
() => new ProductViewModel
{
Id = ...,
Code = ...,
Description = ...,
Active = ...
}
这是一个无参数函数,在调用时,它将返回初始化对象。这也称为lambda表达式。我们可以像这样得到数据结构:
private Func<T> GetInitializationFunction(IDictionary<string, object> values)
{
var initializationLambda = Expression.Lambda<Func<T>>(GetMemberInit(values));
return initializationLambda.Compile();
}
我们创建一个lambda表达式,其主体是成员初始化,这正是我们上面编写的代码。我们指定委托类型Func<T>
,因为它不接受任何参数并返回映射类型的对象。
然后,我们编译它。这个调用生成一个带有我们可以调用的签名Func<T>
的方法,它的主体是我们创建的代码作为数据结构。这是一种不使用反射直接进行反射的简洁方法。
最后,我们通过创建函数并调用它来实现我们之前定义的CreateObject
方法,为我们提供T
(ProductViewModel
此处)的实例:
public T CreateObject(IDictionary<string, object> values)
{
var initializationFunction = GetInitializationFunction(values);
return initializationFunction();
}
答案 1 :(得分:2)
你可以做的是使用类似这样的东西从类p => p.Id
的linq表达式中提取属性名称
public static string GetPropertyName<T>(Expression<Func<T>> expression)
{
MemberExpression body = (MemberExpression)expression.Body;
return body.Member.Name;
}
..然后使用普通旧反射实际将值赋给对象实例。例如,创建一个方法
private void Assign(object objInstance, Expression<Func<T>> propertyExpression, object value)
{
string propertyNameToAssign = GetPropertyName(propertyExpression);
//TODO use reflection to assign "value" to the property "propertyNameToAssign" of "objInstance"
}
(没有编译代码;对于反射部分,网上有很多文章。希望这有帮助)
答案 2 :(得分:0)
这似乎是dynamic
的完美匹配。您可以创建一个动态类,该类具有基于字典中的键的属性。查看DynamicObject
课程。