合并实体框架表达式树

时间:2016-11-28 19:09:04

标签: entity-framework

我正在寻找一种合并多个表达式树的方法,以便为Entity Framework查询构建选择器。查询根据用户提供的参数知道要选择的列。例如,基本查询返回实体的ID / Name列。如果显式设置参数也检索Description列,则查询将返回ID / Name / Description。

所以,我需要以下代码中 MergeExpressions 方法的代码。

Expression<Func<T, TDto>> selector1 = x => new TDto
{
    Id = x.Id,
    Name = x.Name
}

Expression<Func<T, TDto>> selector2 = x => new TDto
{
    Description = x.Description
}

var selector = selector1;
if (includeDescription)
    selector = MergeExpressions(selector1, selector2);

var results = repo.All().Select(selector).ToList();

谢谢。

3 个答案:

答案 0 :(得分:1)

对于一般情况不确定,但在样本中合并MemberInitExpression曲率的lambdas相对容易。您只需要创建另一个MemberInitExpression合并Bindings

static Expression<Func<TInput, TOutput>> MergeExpressions<TInput, TOutput>(Expression<Func<TInput, TOutput>> first, Expression<Func<TInput, TOutput>> second)
{
    Debug.Assert(first != null && first.Body.NodeType == ExpressionType.MemberInit);
    Debug.Assert(second != null && second.Body.NodeType == ExpressionType.MemberInit);
    var firstBody = (MemberInitExpression)first.Body;
    var secondBody = (MemberInitExpression)second.Body.ReplaceParameter(second.Parameters[0], first.Parameters[0]);
    var body = firstBody.Update(firstBody.NewExpression, firstBody.Bindings.Union(secondBody.Bindings));
    return first.Update(body, first.Parameters);
}

请注意,lambda表达式必须绑定到一个相同的参数,因此上面的代码使用以下参数replacer helper将第二个lambda body重新绑定到第一个lambda参数:

public static partial class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

答案 1 :(得分:0)

查看PredicateBuilder

示例:

Yes

Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A");
Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.Contains("B");

var expr3 = PredicateBuilder.And(expr1, expr2);
var query = context.Customers.Where(expr3);

答案 2 :(得分:0)

我用扩展方法做这件事。它在语法上比在各处使用表达式树好一点。我称之为composable repositories

我还编写了一个工具(LinqExpander)来组合不同扩展方法togeather的表达式树,这对于从数据库进行投影(选择)特别有用。当你使用子实体做事时,这只是nessacary。 (见我的帖子:Composable Repositories - Nesting extensions

用法将是:

var dtos = context.Table
              .ThingsIWant() //filter the set
              .ToDtos() //project from database model to something else (your Selector)
              .ToArray();//enumerate the set

ToDtos可能看起来像:

public static IQueryable<DtoType> ToDtos(this IQueryable<DatabaseType> things)
{
     return things.Select(x=> new DtoType{ Thing = x.Thing ... });
}

你想合并两个选择togeather(我假设避免使用underfetch,但这看起来有点奇怪)。我会通过使用这样的投影来做到这一点:

context.Table
              .AsExpandable()
              .Select(x=>new {
                 Dto1 = x.ToDto1(),
                 Dto2 = x.ToDto2()
              })
              .ToArray();

如果您真的希望它返回这样的单个实体,您可能会执行以下操作:

context.Table
              .AsExpandable()
              .Select(x=> ToDto1(x).ToDto2(x));

但我从未尝试过这个。

由于这会使用子投影,因此您需要.AsExpandable扩展名。