2009-10-20 14 views
5

Mam metodę, która obecnie przyjmuje jako parametr Func<Product, string>, ale potrzebuję go jako Expression<Func<Product, string>>. Korzystając z AdventureWorks, oto przykład tego, co chciałbym zrobić za pomocą Func.Refaktoryzacja Func <T> w Wyrażenie <Func<T>>

private static void DoSomethingWithFunc(Func<Product, string> myFunc) 
{ 
    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(product => new 
     { 
      SubCategoryName = myFunc(product), 
      ProductNumber = product.ProductNumber 
     }); 
    } 
} 

Chciałabym to wyglądać mniej więcej tak:

private static void DoSomethingWithExpression(Expression<Func<Product, string>> myExpression) 
{ 
    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(product => new 
      { 
       SubCategoryName = myExpression(product), 
       ProductNumber = product.ProductNumber 
      }); 
    } 
} 

Jednak problem biegnę na to, że myExpression(product) jest nieprawidłowy (nie będzie kompilować). Po przeczytaniu kilku innych postów rozumiem dlaczego. A jeśli to nie dla faktu, że muszę zmienną product do drugiej części mojego klucza pewnie mógł powiedzieć coś takiego:

var result = db.Products.GroupBy(myExpression); 

Ale potrzebujemy zmiennej product bo trzeba zrobić drugi część klucza (numer produktu). Więc nie jestem naprawdę pewien, co teraz zrobić. Nie mogę tego zostawić jako Func, ponieważ powoduje to problemy. Nie mogę się dowiedzieć, jak użyć wyrażenia, ponieważ nie widzę sposobu, w jaki mogę przekazać mu zmienną product. Jakieś pomysły?

EDIT: Oto przykład jak nazwałbym metodę:

DoSomethingWithFunc(product => product.ProductSubcategory.Name); 

Odpowiedz

4

Nie ma mowy, aby splatać drzewo ekspresyjny, który jest reprezentowany jako obiekt Expression<T> w środku „drzewa dosłownym” reprezentowana przez lambda wyrażenie. Będziesz musiał skonstruować drzewa wyrażenie, aby przejść do GroupBy ręcznie:

// Need an explicitly named type to reference in typeof() 
private class ResultType 
{ 
    public string SubcategoryName { get; set; } 
    public int ProductNumber { get; set; }| 
} 

private static void DoSomethingWithExpression(
    Expression<Func<Product, 
    string>> myExpression) 
{ 
    var productParam = Expression.Parameter(typeof(Product), "product"); 
    var groupExpr = (Expression<Func<Product, ResultType>>)Expression.Lambda(
     Expression.MemberInit(
      Expression.New(typeof(ResultType)), 
      Expression.Bind(
       typeof(ResultType).GetProperty("SubcategoryName"), 
       Expression.Invoke(myExpression, productParam)), 
      Expression.Bind(
       typeof(ResultType).GetProperty("ProductNumber"), 
       Expression.Property(productParam, "ProductNumber"))), 
     productParam); 
    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(groupExpr); 
    } 
} 
+0

Nice! Jednak ostatnia linia nie jest dla mnie kompilacją, gdzie wynik jest przypisany. "Argumentów typu dla metody ... nie można wywnioskować z użycia." Czy czegoś brakuje? – Ecyrb

+1

Wartość zwracana z 'Expression.Lambda' powinna być rzutowana na' Expression > '. –

+0

Doskonale! Okazało się to bardziej złożone, niż się spodziewałem. Nigdy wcześniej nie robiłem tego w taki sposób, więc będę studiować ten kod, aby upewnić się, że w pełni rozumiem, co się dzieje. Dzięki! – Ecyrb

3

Na drugim myśli, kompilacja wyrażenia nie będzie działać.

Będziesz musiał ręcznie skonstruować wyrażenie GroupBy, co oznacza, że ​​nie możesz używać typu anonimowego. Sugerowałbym zbudowanie reszty twojego wyrażenia, a następnie jego dekompilację, aby zobaczyć wygenerowane drzewo wyrażeń. Efektem końcowym będzie wyglądać tak, przy użyciu części myExpression odpowiednio:

private static void DoSomethingWithExpression(Expression<Func<Product, string>> myExpression) 
{ 
    var productParam = myExpression.Parameters[0]; 

    ConstructorInfo constructor = ...; // Get c'tor for return type 

    var keySelector = Expression.Lambda(
          Expression.New(constructor, 
           new Expression[] { 
            productParam.Body, 
            ... // Expressions to init other members 
           }, 
           new MethodInfo[] { ... }), // Setters for your members 
          new [] { productParam }); 

    using (AdventureWorksDataContext db = new AdventureWorksDataContext()) 
    { 
     var result = db.Products.GroupBy(keySelector); 

     // ... 
    } 
} 
+0

Przegapiłeś dlaczego on rusza z '' Expression' Func' aby w pierwszej kolejności - chce to do pracy w LINQ to SQL kontekst, który nie zezwoli na losowe wywoływanie funkcji (lub delegowanie) w zapytaniu. –

+0

Tak, widzę to teraz - pracuję nad edycją. – dahlbyk

+0

Pokonałeś Pavela, ale jego odpowiedź była dla mnie bardziej oczywista. +1 za twoją pomoc. Dzięki! – Ecyrb

Powiązane problemy