我如何使用表达式<func <t>&gt;如果我的导航属性是单个项目,在linq语句中?

时间:2016-03-10 19:30:45

标签: c# linq

我有一个表达式,它将数据库对象从另一个系统映射到我可以在任何系统中使用的标准对象。

例如:

private static Expression<Func<excessive_location, MyLocation>> LocationMap =
    l => new MyLocation()
        {
            Id = l.unique_id,
            Code = l.location_code,
            Active = l.active

            ...Other properties
        }

这完全符合我预期的以下情况

public IEnumerable<MyLocation> GetActiveLocations()
        {
            return otherSystem.location_table
                .Select(LocationMap)
                .Where(l => l.Active == true)
                .ToList();
        }

但我似乎无法想出一种让它成为另一种表达方式的一部分的方法

private static Expression<Func<excessive_user, User>> UserMap =
    e => new User()
        {
            Id = e.unique_id,
            FirstName = e.fname,
            LastName = e.lname,
            Location = e.excessive_location
                .Select(LocationMap)  // will not work since
                                      // e.excessive_location is not a collection
        };

我知道我可以将它编译成一个函数但是它必须为每个用户执行数千个。使这项工作的正确方法是什么?

2 个答案:

答案 0 :(得分:0)

为了实现目标,您需要一些表达助手。

让我先向您展示结果代码:

private static Expression<Func<excessive_location, MyLocation>> LocationMap =
    l => new MyLocation()
    {
        Id = l.unique_id,
        Code = l.location_code,
        Active = l.active
    };

private static Expression<Func<excessive_user, User>> UserMap =
    Utils.Expr((excessive_user u) => new User
    {
        Id = u.unique_id,
        FirstName = u.fname,
        LastName = u.lname
    })
    .BindMemberInit(u => u.Location, 
        Utils.Expr((excessive_user u) => u.excessive_location).Bind(LocationMap));

现在是帮手。

第一个名为Expr的方法是一个简单的方法,它允许您定义没有局部变量的表达式。

第二个名为Bind的名称允许您将表达式绑定到另一个表达式访问器。

所以

(excessive_user u) => u.excessive_location

(excessive_location l) => new MyLocation { Id = l.unique_id, .... }

你可以制作

(excessive_user u) => new MyLocation { Id = u.excessive_location.inique_id, ...}

第三个名为BindMemberInit的名称允许您将新成员初始化添加到现有new { ... }表达式中。

最后一个名为ReplaceParameter的名称允许您将表达式参数替换为另一个表达式。

以下是完整代码:

public static class Utils
{
    public static Expression<Func<T, TResult>> Expr<T, TResult>(Expression<Func<T, TResult>> e) { return e; }

    public static Expression<Func<TOuter, TResult>> Bind<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> source, Expression<Func<TInner, TResult>> resultSelector)
    {
        var body = resultSelector.Body.ReplaceParameter(resultSelector.Parameters[0], source.Body);
        return Expression.Lambda<Func<TOuter, TResult>>(body, source.Parameters);
    }

    public static Expression<Func<TSource, TTarget>> BindMemberInit<TSource, TTarget, TMember, TValue>(this Expression<Func<TSource, TTarget>> expression, Expression<Func<TTarget, TMember>> member, Expression<Func<TSource, TValue>> value)
    {
        var binding = Expression.Bind(
            ((MemberExpression)member.Body).Member,
            value.Body.ReplaceParameter(value.Parameters[0], expression.Parameters[0]));
        var body = (MemberInitExpression)expression.Body;
        return expression.Update(body.Update(body.NewExpression, body.Bindings.Concat(new[] { binding })), expression.Parameters);
    }

    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 :(得分:-1)

所以我们需要的是两种方法。一个用另一个表达式构成一个表达式,即用一个表达式计算一个值,另一个用于获取该输出并计算一个新值的表达式,并创建一个新表达式,该表达式接受第一个表达式并产生第二个表达式的输出,使用两个表达式。

我们首先创建一个表达式来获取用户的位置:

Expression<Func<excessive_user, excessive_location>> locationProjection = 
    user => user.excessive_location;

然后,我们将使用您的表达式将该位置转换为MyLocation

Expression<Func<excessive_user, MyLocation>> mappedLocationProject = 
    locationProjection.Compose(LoactionMap);

接下来,我们需要一个非常相似但略有不同的方法,它可以将两个表达式组合在一起,能够获取表达式,另一个获取相同的输入和原始输出,并产生新的输出

此方法允许我们编写以下内容:

mappedLocationProject.Combine((user, location) => new User()
        {
            Id = user.unique_id,
            FirstName = user.fname,
            LastName = user.lname,
            Location = location
        };

这是组合表达式的方法:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

这是Combine

的实施
public static Expression<Func<TFirstParam, TResult>>
    Combine<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], param)
        .Replace(second.Parameters[1], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

他们都使用以下方法将一个表达式的所有实例替换为另一个:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}
public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}