【发布时间】:2019-01-16 02:25:49
【问题描述】:
目前我们有一个包,可以从字符串中的字段动态生成 linq select。它适用于平面属性,但不适用于像 someObj.NestedObj.SomeField 这样的嵌套字段。
我们当前的代码在服务方法中的工作方式如下:
_context.Shipments
.Where(s => s.Id == request.Id) // it does not matter just an example
.Select(request.Fields)
.ToPage(request); // ToPage extension comes from a nuget package
请求对象的参数“fields”只是一个字符串,用逗号分隔,包括Shipment对象的属性。
我对 Shipment 进行了一些重构,我将一些字段分组到一个名为 Address 的新类中,并将其添加到 Shipment 中,如下所示:
// before refactoring
class Shipment {
// other fields...
public string SenderAddress;
public string SenderCityName;
public string SenderCityId;
public string RecipientAddress;
public string CityName;
public string CityId;
}
// after refactoring
class Shipment {
// other fields...
public Address Sender;
public Address Recipient;
}
class Address {
public string AddressText;
public string CityName;
public string CityId;
}
为了当前的数据库映射,我添加了相应的映射:
public class ShipmentMap : DataEntityTypeConfiguration<Shipment>
{
public ShipmentMap()
{
ToTable("Shipments");
// other property mappings
Property(s => s.Recipient.AddressText).HasMaxLength(1100).HasColumnName("RecipientAddress");
Property(s => s.Recipient.CityName).HasMaxLength(100).HasColumnName("CityName");
Property(s => s.Recipient.CityId).IsOptional().HasColumnName("CityId");
Property(s => s.Sender.AddressText).HasMaxLength(1100).HasColumnName("SenderAddress");
Property(s => s.Sender.CityName).HasMaxLength(100).HasColumnName("SenderCityName");
Property(s => s.Sender.CityId).IsOptional().HasColumnName("SenderCityId");
}
}
DataEntityTypeConfiguration 来自 nuget 包:
public abstract class DataEntityTypeConfiguration<T> : EntityTypeConfiguration<T> where T : class
{
protected virtual void PostInitialize();
}
所以,我的问题是 select(fields) 在 fields = "Recipient.CityId" 时不起作用。
如何动态生成用于选择嵌套字段的 linq?
我在下面尝试使用LINQ : Dynamic select,但它不起作用。
// assume that request.Fields= "Recipient.CityId"
// in the service method
List<Shipment> x = _context.Shipments
.Where(s => s.Id == request.Id)
.Select(CreateNewStatement(request.Fields))
.ToList();
// I tried to generate select for linq here
Func<Shipment, Shipment> CreateNewStatement(string fields)
{
// input parameter "o"
var xParameter = Expression.Parameter( typeof( Shipment ), "o" );
// new statement "new Data()"
var xNew = Expression.New( typeof( Shipment ) );
// create initializers
var bindings = fields.Split( ',' ).Select( o => o.Trim() )
.Select(o =>
{
string[] nestedProps = o.Split('.');
Expression mbr = xParameter;
foreach (var prop in nestedProps)
mbr = Expression.PropertyOrField(mbr, prop);
// property "Field1"
PropertyInfo mi = typeof( Shipment ).GetProperty( ((MemberExpression)mbr).Member.Name );
//
// original value "o.Field1"
var xOriginal = Expression.Property( xParameter, mi );
MemberBinding bnd = Expression.Bind( mi, xOriginal );
return bnd;
});
// initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var xInit = Expression.MemberInit( xNew, bindings );
// expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
var lambda = Expression.Lambda<Func<Shipment,Shipment>>( xInit, xParameter );
// compile to Func<Data, Data>
return lambda.Compile();
}
它抛出异常是因为 mbr 在循环之后变为 CityId 并且“mi”为空,因为在发货时没有字段 CityId。我在这里想念什么?如何为具有嵌套属性的给定字符串创建动态选择?
更新:
我找到了解决方案并将其添加为答案,我还为解决方案创建了一个 github gist:
https://gist.github.com/mstrYoda/663789375b0df23e2662a53bebaf2c7c
【问题讨论】:
-
在
foreach循环中,您反复用新值覆盖mbr而不使用它。你是故意的吗? -
实际上我尝试在另一个问题中混合两种不同的解决方案:stackoverflow.com/questions/17647627/… 我看到接受的答案在循环中做同样的事情。