2013-10-28 43 views
6

设想以下表结构LINQ多个连接的IQueryable修改结果选择表达

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

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

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

我希望定义连接这三个表和接受一个Expression<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); 
} 

如何可以这样做的任何想法?

回答

10

您可以定义一个一次性的中介目标选择进入,而不是使用匿名类型:

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, 
          (ab) => 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中表达你想要做的更清楚吗?我会争辩说,每次创建此查询所需的额外行都会比使用此方法更清晰。

+0

该方法将从几个不同的地方被调用,并且需要能够在数据库上执行选择。我不认为提供的解决方案将在数据库上运行,实际上可能会得到一个TargetInvocationException – mhand

+0

我纠正了,EF确实允许在IQueryable表达式树中使用未映射的类型。 – mhand

+1

是的,只要生成的表达式树可以转换为SQL,那就很好。 – Ocelot20

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

,然后你需要某种形式的“映射”,这代表了你的“选择”,或者至少是我认为你的意思selecto的R:

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纳入方法,但我认为这是行不通的,但也许有人想出了一个解决方案。

1

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

我们要做的第一件事就是开始了与这个简单的辅助类和方法,使我们能够在给定的表达另一种表达式替换一个表达式的所有实例:

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

其余的你已经拥有。