2016-04-04 18 views
2

Nehmen wir die folgenden Demoklassen an.Lambdas in Linq AST - warum unterschiedliches Verhalten?

public class Foo { 
    public int key1 {get; set;} 
    public Foo(int _key1) { 
     key1 = _key1; 
    } 
} 
public class Bar { 
    public int key2 {get; set;} 
    public Bar(int _key2) { 
     key2 = _key2; 
    } 
} 

Sie sind in einem einfachen Linq Join zusammen.

Foo[]aSet = new Foo[3]{new Foo(1),new Foo(2),new Foo(3)}; 
Bar[]bSet = new Bar[3]{new Bar(1),new Bar(3),new Bar(5)}; 

Func<int,Func<Foo,bool>> VisibleLambda = w => x => x.key1 > w; 
var pb = Expression.Parameter(typeof(Bar),"z"); 
var pf = Expression.Parameter(typeof(Foo), "y"); 
PropertyInfo BarId = typeof(Bar).GetProperty("key2"); 
PropertyInfo FooId = typeof(Foo).GetProperty("key1"); 
var eqexpr = Expression.Equal(Expression.Property(pb, BarId), Expression.Property(pf, FooId)); 
var lambdaInt = Expression.Lambda<Func<Bar, bool>>(eqexpr, pb); 
var InvisibleLambda = Expression.Lambda<Func<Foo,Func<Bar, bool>>>(lambdaInt,pf); 

var query = from a in aSet.Where(VisibleLambda(1)) 
    from b in bSet.Where(InvisibleLambda.Compile()(a)) 
    select new Tuple<Foo,Bar>(a,b); 

Nun wird die Abfrage durch eine Erweiterung implementiert

IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression) 
{ 
    if (expression == null) 
     throw new ArgumentNullException("expression"); 

    return new ExpressionQueryImpl<TElement>(DataContextInfo, expression); 
} 

Die Einzelheiten der Durchführung sind irrelevant: meine Frage ist nur für den Ausdruck im Zusammenhang von der IQueryable abgeleitet. Es gibt zwei Lambdas: Eines ("visible") wird als Argument des Ausdrucks mit einem NodeType Quote generiert, das sehr einfach zu analysieren ist, während das andere ("invisible") als zweites Argument des Ausdrucks mit generiert wird "where" -Klausel von NodeType Invoke, die in Bezug auf seine SQL-Rendering fast unsichtbar ist. Warum passiert das und gibt es einen Weg, um zu arbeiten und es zu touren?

+1

Sie sprechen über 'Expression's, aber zeigen Beispiele mit' Func's. Bitte aktualisieren Sie Ihre Post entsprechend und lassen Sie nur die relevanten Informationen, die den Fall darstellen. –

+0

@IvanStoev Getan, für die "unsichtbare" LambdaExpression, die eine, die relevanter ist, weil es die Ursache für das "Problem" –

+1

Nun sehe ich, was Sie meinen, aber Ausdrücke, die ** zurückkehren ** -Funktionen sind ziemlich seltsam, so bin ich nicht überrascht, dass der Abfrageanbieter sie nicht verwenden kann. Man beachte beispielsweise, dass 'Queryable.Where'' Expression > 'erwartet, d. H. Lambda, das den' T'-Parameter akzeptiert und 'bool' (nicht' Func'!) Zurückgibt. –

Antwort

2

Wie bereits durch die Kommentare aus 1 und 2 von Ivan Stoev, das unterschiedlichen Verhalten, insbesondere das Problem in der SQL-Generierung, um von den Queryable.Where erwartete andere Signatur zurückzuführen

Here ist die Lösung von Igor Tkachev, für jeden, der interessiert wäre.

Alles läuft darauf hinaus, die Umsetzung die hilfreich Erweiterung nach unten, wo man die Linq Methode mit der entsprechenden Signatur nutzen kann: dh den Queryable.GroupJoin :-)

static class ExpressionTestExtensions 
{ 
    public class LeftJoinInfo<TOuter,TInner> 
    { 
     public TOuter Outer; 
     public TInner Inner; 
    } 

    [ExpressionMethod("LeftJoinImpl")] 
    public static IQueryable<LeftJoinInfo<TOuter,TInner>> LeftJoin<TOuter, TInner, TKey>(
     this IQueryable<TOuter> outer, 
     IEnumerable<TInner> inner, 
     Expression<Func<TOuter, TKey>> outerKeySelector, 
     Expression<Func<TInner, TKey>> innerKeySelector) 
    { 
     return outer 
      .GroupJoin(inner, outerKeySelector, innerKeySelector, (o, gr) => new { o, gr }) 
      .SelectMany(t => t.gr.DefaultIfEmpty(), (o,i) => new LeftJoinInfo<TOuter,TInner> { Outer = o.o, Inner = i }); 
    } 

    static Expression<Func< 
     IQueryable<TOuter>, 
     IEnumerable<TInner>, 
     Expression<Func<TOuter,TKey>>, 
     Expression<Func<TInner,TKey>>, 
     IQueryable<LeftJoinInfo<TOuter,TInner>>>> 
     LeftJoinImpl<TOuter, TInner, TKey>() 
    { 
     return (outer,inner,outerKeySelector,innerKeySelector) => outer 
      .GroupJoin(inner, outerKeySelector, innerKeySelector, (o, gr) => new { o, gr }) 
      .SelectMany(t => t.gr.DefaultIfEmpty(), (o,i) => new LeftJoinInfo<TOuter,TInner> { Outer = o.o, Inner = i }); 
    } 

} 

solche Erweiterung definiert hat, meine " generic „zu

 static internal IQueryable<ExpressionTestExtensions.LeftJoinInfo<T2,T1>> NewJoin<T1, T2, TKey>(Expression<Func<T2, TKey>> outer, Expression<Func<T1, TKey>> inner) 
     where T2: class 
     where T1 : class 
    { 


     using (var db = new MyContext()) { 

     var query = (from b in db.GetTable<T2>() select b).LeftJoin <T2,T1, TKey>((from f in db.GetTable<T1>() select f), outer, inner); 


      return query; 

     } 
    } 
} 

Schließlich wird der elegante Anwendungsfall einfach

schaltet join
 public static void Main(string[] args) 
    { 
     Console.WriteLine("Hello World!"); 

     //var queryList = Test.Join<Bar, Foo>(b => q => q.id == b.id); 


     var queryList = Test.NewJoin<Bar, Foo, int>(q => q.id, b => b.id); 

     foreach (var telement in queryList) 
     { 
      var bar = telement.Inner as Bar; 
      var element = telement.Outer as Foo; 
      Console.WriteLine(element.id.ToString() + " " + element.FromDate.ToShortDateString() +" " 
           +bar.id.ToString() + " " + bar.Name 
          ); 
     } 

     Console.Write("Press any key to continue . . . "); 
     Console.ReadKey(true); 
    } 
Verwandte Themen