为具有嵌套实体的实体生成动态选择表达式
Posted
技术标签:
【中文标题】为具有嵌套实体的实体生成动态选择表达式【英文标题】:Generating Dynamic Select Expression for entities with nested entity in it 【发布时间】:2016-02-12 17:03:45 【问题描述】:我有一个类,该类具有另一个实体类型的属性。如下所示:
public class InspectionTopicDataAllViewModel
public Guid Id get; set;
public Guid? UploadedFileId get; set;
public int InspectionId get; set;
public int InspectionTopicId get; set;
public SparseDataViewModel SparseData get; set;
public class SparseDataViewModel : SparseData
我需要编写一个能够只选择我想要的列的动态选择表达式。
我写如下:
private static Func<InspectionTopicDataAll, InspectionTopicDataAllViewModel> GetSparseInitExpression(List<string> columns)
Expression<Func<InspectionTopicDataAll, InspectionTopicDataAllViewModel>> commonSelector =
x => new InspectionTopicDataAllViewModel()
Id = x.Id,
InspectionId = x.InspectionId,
InspectionTopicId = x.InspectionTopicId,
UploadedFileId = x.UploadedFileId,
;
// input parameter "x"
var xParameter = Expression.Parameter(typeof(InspectionTopicDataAll), "x");
var xNew = Expression.New(typeof(InspectionTopicDataAllViewModel));
var sNew = Expression.New(typeof(SparseDataViewModel));
var bindings = new List<MemberBinding>();
// create initializers
foreach (var column in columns)
Expression srcBody = xParameter;
foreach (var member in column.Split('.'))
srcBody = Expression.PropertyOrField(srcBody, member);
var destMember = srcBody as MemberExpression;
// property "Field1"
var propInfo = destMember?.Member as PropertyInfo;
if (propInfo != null)
bindings.Add(Expression.Bind(propInfo, srcBody));
var sInit = Expression.MemberInit(sNew, bindings);
var zeroth = ((MemberInitExpression)commonSelector.Body);
var param = commonSelector.Parameters[0];
List<MemberBinding> newBindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
var spNestedType = typeof (InspectionTopicDataAllViewModel).GetProperty(nameof(SparseData));
newBindings.Add(Expression.Bind(spNestedType, sInit));
var newInit = Expression.MemberInit(xNew, newBindings);
var childSelector = Expression.Lambda<Func<InspectionTopicDataAll, InspectionTopicDataAllViewModel>>(newInit, xParameter);
return childSelector.Compile();
并在我的主要查询中使用以下函数:
var model = worksheetService.GetInspectionTopicData(inspectionTopicId, inspectionId, discrimintor, value).Select(GetSparseInitExpression(columns)).AsQueryable();
使用此函数,我可以生成所需的表达式,如下所示:
x => new InspectionTopicDataAllViewModel()
Id = x.Id,
InspectionId = x.InspectionId,
InspectionTopicId = x.InspectionTopicId,
UploadedFileId = x.UploadedFileId,
SparseData = new SparseDataViewModel()
MusteriNo = x.SparseData.MusteriNo,
MusteriAdi = x.SparseData.MusteriAdi,
CekNo = x.SparseData.CekNo,
Banka = x.SparseData.Banka,
CekTarihi = x.SparseData.CekTarihi,
Meblag = x.SparseData.Meblag,
PCN = x.SparseData.PCN,
Kesideci = x.SparseData.Kesideci
但是当我开始运行表达式时,抛出了这个错误:
类型的变量“x” 'InternalControl.Domain.Entities.InspectionTopicDataAll' 从范围 '' 中引用,但未定义
我不知道如何解决这个错误...
更新:感谢@MBoros 的帮助,对于谁可能以后需要这种类型的代码,使用上面的代码,即使代码有效并且可以从数据库中检索结果,select 语句不是按需要工作,并且所有列都从数据库中获取,所以为了解决这个问题,我使用了下面的代码:希望能帮助其他尝试做这种事情的人。
private static Expression<Func<InspectionTopicDataAll, InspectionTopicDataAllViewModel>> GetCustomSelectExpression(List<string> columns)
Expression<Func<InspectionTopicDataAll, InspectionTopicDataAllViewModel>> commonSelector =
x => new InspectionTopicDataAllViewModel()
Id = x.Id,
InspectionId = x.InspectionId,
InspectionTopicId = x.InspectionTopicId,
UploadedFileId = x.UploadedFileId,
;
// input parameter "x"
var xParameter = commonSelector.Parameters[0];
var xNew = Expression.New(typeof(InspectionTopicDataAllViewModel));
var sNew = Expression.New(typeof(SparseDataViewModel));
var bindings = new List<MemberBinding>();
// create initializers
foreach (var column in columns)
Expression srcBody = xParameter;
foreach (var member in column.Split('.'))
srcBody = Expression.PropertyOrField(srcBody, member);
var destColName = column.Substring(column.LastIndexOf('.')+1);
// property "Field1"
var propInfo = typeof(SparseDataViewModel).GetProperty(destColName);
if (propInfo != null)
bindings.Add(Expression.Bind(propInfo, srcBody));
var sInit = Expression.MemberInit(sNew, bindings);
var zeroth = ((MemberInitExpression)commonSelector.Body);
List<MemberBinding> newBindings = new List<MemberBinding>(zeroth.Bindings.OfType<MemberAssignment>());
var spNestedType = typeof (InspectionTopicDataAllViewModel).GetProperty(nameof(SparseData));
newBindings.Add(Expression.Bind(spNestedType, sInit));
var newInit = Expression.MemberInit(xNew, newBindings);
var childSelector = Expression.Lambda<Func<InspectionTopicDataAll, InspectionTopicDataAllViewModel>>(newInit, xParameter);
return childSelector;
并使用它来代替 select 语句,如下所示:
var model =
worksheetService.GetInspectionTopicData(inspectionTopicId, inspectionId, discrimintor, value)
.Select(GetCustomSelectExpression(columns));
【问题讨论】:
你的方法worksheetService.GetInspectionTopicData
,是Linq-To-Entities 方法吗?还是 Linq-To-Objects?它返回 IQueryable 还是 IEnumerable?
它返回一个 iqueryable
【参考方案1】:
您的childSelector
不知道commonSelector
的x
参数。在表达式树中,参数是通过实例识别的,而不仅仅是名称,因此这两个 x
参数实际上是不同的(即使在调试视图中它们看起来相同)。
最简单的解决方案可能是改变你的
// input parameter "x"
var xParameter = Expression.Parameter(typeof(InspectionTopicDataAll), "x");
到
var xParameter = commonSelector.Parameters[0];
这应该可以解决问题:)
【讨论】:
以上是关于为具有嵌套实体的实体生成动态选择表达式的主要内容,如果未能解决你的问题,请参考以下文章
不允许使用源类型“动态”或具有“动态”类型连接序列的查询表达式
如何为实体框架 Sql 提供程序编写测试并访问生成的 Sql 命令