7

想象一下下面的表结构

---------
TableA
ID
Name

---------
TableB
ID
TableAID

---------
TableC
ID
TableBID

我想定义一个连接这三个表并接受 aExpression<Func<TableA, TableB, TableC, T>>作为选择器的函数。

所以我想要以下内容:

public IQueryable<T> GetJoinedView<T>(Expression<Func<TableA, TableB, TableC, T>> selector)
{
    return from a in DbContext.Set<TableA>()
           join b on DbContext.Set<TableB>() a.ID equals b.TableAID
           join c on DbContext.Set<TableC>() b.ID equals c.TableBID
           select selector;
}

现在,显然上面没有做我想做的事,这会给我一个IQueryable表达式类型。我可以使用方法链语法,但最终我需要多个选择器,每个方法链调用一个。有没有办法获取选择器并将其应用于匿名类型,如下面的不完整函数:

public IQueryable<T> GetJoinedView<T>(Expression<Func<TableA, TableB, TableC, T>> selector)
{
    var query = from a in DbContext.Set<TableA>()
                join b on DbContext.Set<TableB>() a.ID equals b.TableAID
                join c on DbContext.Set<TableC>() b.ID equals c.TableBID
                select new
                {
                    A = a, B = b, C = c
                };

    // I need the input selector to be modified to be able to operate on
    // the above anonymous type
    var resultSelector = ModifyInputSelectorToOperatorOnAnonymousType(selector);

    return query.Select(resultSelector);
}

关于如何做到这一点的任何想法?

4

3 回答 3

14

您可以定义一个一次性的中间对象来选择,而不是使用匿名类型:

public class JoinedItem
{
    public TableA TableA { get; set; }
    public TableB TableB { get; set; }
    public TableC TableC { get; set; }
}

新方法:

public IQueryable<T> GetJoinedView<T>(Expression<Func<JoinedItem, T>> selector)
{
    return DbContext.Set<TableA>()
                    .Join(DbContext.Set<TableB>(),
                          a => a.ID,
                          b => b.TableAID,
                          (a, b) => new { A = a, B = b})
                    .Join(DbContext.Set<TableC>(),
                          ab => ab.B.ID,
                          c => c.TableBID
                          (ab, c) => new JoinedItem
                              {
                                  TableA = ab.A,
                                  TableB = ab.B,
                                  TableC = c
                              })
                     .Select(selector);
}

你真的会加入这三个表,足以使这个方法的使用比直接在 LINQ 中表达你想要做的事情更清晰吗?我认为每次创建此查询所需的额外行会比使用此方法更清晰。

于 2013-10-28T21:48:44.133 回答
2

所以我们能做的就是从将数据加入匿名对象的确切方法开始。

我们要做的第一件事是从这个简单的帮助类和方法开始,它允许我们用给定表达式中的另一个表达式替换一个表达式的所有实例:

public 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);
}

现在是我们的实际方法。为了使用三参数构造函数映射这些匿名对象的序列,我们可以做的是让我们的方法接受一个表达式,该表达式表示将输入序列映射到第一个参数,以及其他两个参数的选择器。然后,如果“真实”选择器主体中的第一个参数替换为第一个参数选择器的主体,则我们可以替换所有实例。

请注意,我们需要在开头添加一个参数以允许对匿名类型进行类型推断。

public static Expression<Func<TInput, TOutput>>
    ModifyInputSelectorToOperatorOnAnonymousType
    <TInput, TOutput, TParam1, TParam2, TParam3>(
    //this first param won't be used; 
    //it's here to allow type inference
    IQueryable<TInput> exampleParam,
    Expression<Func<TInput, TParam1>> firstSelector,
    Expression<Func<TInput, TParam2>> secondSelector,
    Expression<Func<TInput, TParam3>> thirdSelector,
    Expression<Func<TParam1, TParam2, TParam3, TOutput>> finalSelector)
{
    var parameter = Expression.Parameter(typeof(TInput), "param");

    var first = firstSelector.Body.Replace(firstSelector.Parameters.First(),
        parameter);
    var second = secondSelector.Body.Replace(secondSelector.Parameters.First(),
        parameter);
    var third = thirdSelector.Body.Replace(thirdSelector.Parameters.First(),
        parameter);

    var body = finalSelector.Body.Replace(finalSelector.Parameters[0], first)
        .Replace(finalSelector.Parameters[1], second)
        .Replace(finalSelector.Parameters[2], third);

    return Expression.Lambda<Func<TInput, TOutput>>(body, parameter);
}

现在调用它,我们可以传入查询,只是为了满足类型推断,然后是匿名对象的第一个、第二个和第三个参数的选择器,以及我们的最终选择器:

var resultSelector = ModifyInputSelectorToOperatorOnAnonymousType(
    query, x => x.A, x => x.B, x => x.C, selector);

剩下的你已经拥有了。

于 2013-10-28T22:46:57.767 回答
0

也许这不是您正在寻找的解决方案,但我会发布它:

我会为您对数据库执行的每个“选择”推荐一个 DataModel,如下所示:

 public class JoinedDataModel
 {
     public TableA DataA { get; set; }
     public TableB DataB { get; set; }
     public TableC DataC { get; set; }
 }

你的“选择”和你已经做的一样

public IQueryable<JoinedDataModel> GetJoinedView( )
{
    return from a in DbContext.Set<TableA>()
           join b on DbContext.Set<TableB>() a.ID equals b.TableAID
           join c on DbContext.Set<TableC>() b.ID equals c.TableBID
           select new JoinedDataModel( )
           {
                DataA = a,
                DataB = b,
                DataC = c
           };
}

然后你需要某种'映射器'来代表你的'选择器'或者至少我认为你对选择器的意思:

public static Mapper( )
{
    private static Dictionary<MapTuple, object> _maps = new Dictionary<MapTuple, object>();

    public static void AddMap<TFrom, TTo>(Action<TFrom, TTo, DateTime> map)
    {
        Mapper._maps.Add(MapTuple.Create(typeof(TFrom), typeof(TTo)), map);
    }

    public static TTo Map<TFrom, TTo>( TFrom srcObj )
    {
        var typeFrom = typeof(TFrom);
        var typeTo = typeof(TTo);
        var key = MapTuple.Create(typeFrom, typeTo);
        var map = (Action<TFrom, TTo, DateTime>) Mapper._maps[key];

        TTo targetObj = new TTo( );

        map( srcObj, targetObj );

        return targetObj;
    }

那么你需要定义至少一种映射方法:

AddMap<JoinedDataModel, YourResultModel>( 
    ( src, trg ) =>
    {
        trg.SomePropertyA = src.DataA.SomeProperty;
        trg.SomePropertyB = src.DataB.SomeProperty;
    }
);

那么你可以简单地调用:

 public IList<YourResultModel> CallDb( )
 {
      return ( from item in GetJoinedView( )
               select Mapper.MapTo<JoinedDataModel, YourResultModel>( item ) 
             ).ToList( );
 }

我知道您想将某种方法传递Expression给该方法,但我认为这行不通,但也许有人想出了解决方案。

于 2013-10-28T21:48:34.603 回答