为具有嵌套实体的实体生成动态选择表达式

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 不知道commonSelectorx 参数。在表达式树中,参数是通过实例识别的,而不仅仅是名称,因此这两个 x 参数实际上是不同的(即使在调试视图中它们看起来相同)。

最简单的解决方案可能是改变你的

// input parameter "x"
var xParameter = Expression.Parameter(typeof(InspectionTopicDataAll), "x"); 

var xParameter = commonSelector.Parameters[0]; 

这应该可以解决问题:)

【讨论】:

以上是关于为具有嵌套实体的实体生成动态选择表达式的主要内容,如果未能解决你的问题,请参考以下文章

不允许使用源类型“动态”或具有“动态”类型连接序列的查询表达式

如何为实体框架 Sql 提供程序编写测试并访问生成的 Sql 命令

用于从实体类及其导航属性中选择多个列的 Linq Lambda 表达式

使用带有实体框架的动态字段按子记录排序

如何创建可重用的实体框架投影表达式?

具有嵌套属性的动态 linq 表达式树