2013-02-28 10 views
6

Mam niektóre wyrażenia generujące kod do przekazania jako "where" w czytaniu bazy danych, a ja próbuję trochę przyspieszyć.Zmiana wartości ConstantExpression w istniejącym BinaryExpression

Ten przykład poniżej sprawia, gdzie oświadczenie dopasować PK stole z przekazywane wartości:

private Expression MakeWhereForPK(int id) 
{ 
    var paramExp = Expression.Parameter(typeof(Brand),"b"); 

    //Expression to get value from the entity 
    var leftExp = Expression.Property(paramExp,"ID"); 

    //Expression to state the value to match (from the passed in variable) 
    var rightExp = Expression.Constant(id,typeof(int)); 

    //Expression to compare the two 
    var whereExp = Expression.Equal(leftExp,rightExp); 

    return Expression.Lambda<Func<Brand,bool>>(whereExp,paramExp); 
} 

Powyższe jest uproszczeniem na pytanie - prawdziwych zawiera kod do podjęcia tabeli na zapytanie i znaleźć jego PK itd. To jest skutecznie robi to samo, co można zrobić w kodzie zwykle:

ctx.Brands.Where(b => b.ID = id); 

To działa OK, ale robiąc z badaniem w celu optymalizacji rzeczy trochę Znalazłem jego raczej wolno - wykonanie powyższych 1000000 razy zajmuje około 25 sekund. Lepiej, jeśli pominę ostatnią linię powyżej (ale oczywiście wtedy jest bezużyteczna!), Więc wydaje się, że Expression.Lamba zajmuje około 2/3 czasu, ale reszta też nie jest wspaniała.

Jeśli wszystkie zapytania miałyby się wydarzyć od razu, mógłbym zamienić je na wyrażenie w stylu IN i wygenerować je raz, ale niestety to nie jest możliwe, więc mam nadzieję, że uda się uratować większość pokolenia powyżej, i po prostu ponownie użyj wygenerowanego wyrażenia, ale przechodząc w innej wartości id.

Należy zauważyć, że ponieważ zostanie to przekazane do Linq, nie mogę skompilować wyrażenia, aby mieć parametr liczby całkowitej, który można przekazać przy wywołaniu - musi pozostać jako drzewo wyrażeń.

Więc dodaje może być prosta wersja dla celów tego ćwiczenia rozrządu:

Expression<Func<Brand,bool>> savedExp; 

private Expression MakeWhereForPKWithCache(int id) 
{ 
    if (savedExp == null) 
    { 
     savedExp = MakeWhereForPK(id); 
    } 
    else 
    { 
     var body = (BinaryExpression)savedExp.Body; 
     var rightExp = (ConstantExpression)body.Right; 

     //At this point, value is readonly, so is there some otherway to "inject" id, 
     //and save on compilation? 
     rightExp.Value = id; 
    } 

    return savedExp; 
} 

Jak mogę ponownie użyć wyrażenia, tylko z inną wartością id?

+0

byłby to pomysł, aby użyć [ 'ParameterExpression'] (http://msdn.microsoft.com/en-us/library/system.linq.expressions.parameterexpression.aspx) zamiast" ConstantExpression "? (Przy okazji, jeśli wydajność jest problemem, po co używać LINQ w ogóle? LINQ handluje wydajnością, ponieważ nie musi uczyć się SQL.) – Andomar

+2

@Andomar: drugi 'ParameterExpression' sprawi, że' Func '. Nie może być używany jako predykat dla 'Where'. – Dennis

Odpowiedz

4

Nie można zmienić drzewek wyrażeń - są one niezmienne. Ale można zastąpić stałą ekspresję poprzez tworzyć własne gościa:

class MyVisitor : ExpressionVisitor 
{ 
    private readonly ConstantExpression newIdExpression; 

    public MyVisitor(int newId) 
    { 
     this.newIdExpression = Expression.Constant(newId); 
    } 

    public Expression ReplaceId(Expression sourceExpression) 
    { 
     return Visit(sourceExpression); 
    } 

    protected override Expression VisitConstant(ConstantExpression node) 
    { 
     return newIdExpression; 
    } 
} 

Zastosowanie:

  var expr = MakeWhereForPK(0); // p => p.ID == 0 
      var visitor = new MyVisitor(1); 
      var newExpr = visitor.ReplaceId(expr); p => p.ID == 1 

uwaga, że ​​ta tworzy kopię istniejącego drzewa. i nie przetestowałem tego pod kątem wydajności. Nie jestem pewien, czy to będzie szybsze, czy nie.

ten kod:

  // warming up 
      var visitor = new MyVisitor(1); 
      var expr = MakeWhereForPK(0); 
      visitor.ReplaceId(MakeWhereForPK(0)); 

      var sw = new System.Diagnostics.Stopwatch(); 
      sw.Start(); 

      for (var i = 0; i < 1000000; i++) 
      { 
       MakeWhereForPK(i); 
      } 

      sw.Stop(); 
      Console.WriteLine("Make expression: {0}", sw.Elapsed); 

      sw.Restart(); 

      for (var i = 0; i < 1000000; i++) 
      { 
       visitor.Visit(expr); 
      } 

      sw.Stop(); 
      Console.WriteLine("Replace constant expression: {0}", sw.Elapsed); 

      Console.WriteLine("Done.");  

produkuje te wyniki na moim komputerze:

Dodać wyrażenie: 00: 00: +04,1714254
Wymień stałą ekspresję: 00: 00: +02,3644953
Gotowe .

Wygląda na to, że użytkownik jest szybszy niż tworzenie nowego wyrażenia.

+0

Nice - przyjrzy się przyjęciu tego - może zrobić rozsądną różnicę. Dzięki –

+0

BTW, twój kod jest nieco błędny, ponieważ zawsze zastępuje 0 1 (a nie 'i'). Ale nie sądzę, że to spowoduje jakąkolwiek różnicę w wydajności. – svick

6

Można użyć faktu, że drzewo wyrażeń nie musi zawierać tylko prostych stałych, może również zawierać właściwości dostępne.Tak więc, aby utworzyć pojedyncze wyrażenie, które uzyskuje dostęp do wartości niektórych właściwości i za każdym razem zmienić tylko tę właściwość, a nie drzewo wyrażeń.

Coś jak:

class ExpressionHolder 
{ 
    public int Value { get; set; } 

    public Expression<Func<Brand, bool>> Expr { get; private set; } 

    public ExpressionHolder() 
    { 
     Expr = MakeWhereForPK(); 
    } 

    private Expression<Func<Brand, bool>> MakeWhereForPK() 
    { 
     var paramExp = Expression.Parameter(typeof(Brand), "b"); 

     var leftExp = Expression.Property(paramExp, "ID"); 

     var rightExp = Expression.Property(Expression.Constant(this), "Value"); 

     var whereExp = Expression.Equal(leftExp, rightExp); 

     return Expression.Lambda<Func<Brand, bool>>(whereExp, paramExp); 
    } 
} 

Jest to około 500 x szybciej niż kodzie: stosując kod pomiarowy Dennisa, mam następujące wyniki:

Make expression: 00:00:02.9869921 
Replace constant expression: 00:00:02.3332857 
Set property: 00:00:00.0056485 
+0

To interesująca alternatywa - będzie wymagać przeróbki, ale wygląda na to, że warto. –

+0

Oprócz doskonałej perfekcji, jest to o wiele bardziej wszechstronne, ponieważ pozwala na zmianę stałych nawet wtedy, gdy nie masz możliwości zmiany drzewa ekspresji, z którego korzysta coś. To jest to, czego używam, z klasą 'Proxy ', która ma 'public Expression MakeAccessExpression() => Expression.Property (Expression.Constant (this), nameof (Proxy .Value));' – jnm2

Powiązane problemy