2012-03-17 8 views
10

Ho dato un'occhiata ad altre domande simili a questa, ma non sono riuscito a trovare alcuna risposta praticabile.Conversione di un'espressione lambda in una chiave univoca per la memorizzazione nella cache

Ho utilizzato il seguente codice per generare chiavi univoche per archiviare i risultati delle mie query linq nella cache.

string key = ((LambdaExpression)expression).Body.ToString(); 

    foreach (ParameterExpression param in expression.Parameters) 
    { 
     string name = param.Name; 
     string typeName = param.Type.Name; 

     key = key.Replace(name + ".", typeName + "."); 
    } 

    return key; 

Sembra funzionare bene per le query semplici che contengono numeri interi o booleani, ma quando il mio query contiene nidificate espressioni costanti per esempio

// Get all the crops on a farm where the slug matches the given slug. 
(x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false) 

La chiave restituito è quindi:

(Vero AndAlso (Farm.Crops.Any (y => (valore (OzFarmGuide.Controllers.FarmController + <> c__DisplayClassd) .slug == y.Slug)) AndAlso (Farm.Deleted == False)))

Come si può vedere qualsiasi nome di raccolto che ho passato darà lo stesso risultato chiave. C'è un modo per estrarre il valore del parametro dato in modo che io possa differenziare tra le mie query?

conversione Anche il y di dire il nome del tipo corretto sarebbe bello .....

+0

che cosa è sbagliato con l'utilizzo di il metodo 'GetHashCode()' e un 'HashSet '? Non è univoco, ma un 'HashSet' è in molti casi in grado di ricevere e aggiungere elementi in O (1). –

+0

@CommuSoft, non funzionerebbe, perché anche due espressioni che sembrano esattamente uguali non sarebbero considerate uguali (a meno che tu non abbia fornito il tuo comparatore di uguaglianza). – svick

+0

@CommuSoft - Inoltre, un hashcode non è garantito e quindi un potenziale bug nel codice – Polity

risposta

5

Come detto Polity e Marc nei loro commenti, che cosa avete bisogno è un valutatore parziale dell'espressione LINQ. Puoi leggere come farlo usando ExpressionVisitor in Matt Warren's LINQ: Building an IQueryable Provider - Part III. L'articolo Caching the results of LINQ queries by Pete Montgomery (collegato a Polity) descrive alcune più specifiche relative a questo tipo di memorizzazione nella cache, ad es. come rappresentare le raccolte nella query.

Inoltre, non sono sicuro di fare affidamento su ToString() in questo modo. Penso che sia pensato principalmente per scopi di debug e potrebbe cambiare in futuro. L'alternativa sarebbe creare il tuo IEqualityComparer<Expression> che può creare un codice hash per qualsiasi espressione e può confrontare due espressioni per l'uguaglianza. Probabilmente lo farei usando anche ExpressionVisitor, ma farlo sarebbe piuttosto noioso.

0

Che dire di questo?

public class KeyGeneratorVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     return Expression.Parameter(node.Type, node.Type.Name); 
    } 

    protected override Expression VisitMember(MemberExpression node) 
    { 
     if (CanBeEvaluated(node)) 
     { 
      return Expression.Constant(Evaluate(node)); 
     } 
     else 
     { 
      return base.VisitMember(node); 
     } 
    } 

    private static bool CanBeEvaluated(MemberExpression exp) 
    { 
     while (exp.Expression.NodeType == ExpressionType.MemberAccess) 
     { 
      exp = (MemberExpression) exp.Expression; 
     } 

     return (exp.Expression.NodeType == ExpressionType.Constant); 
    } 

    private static object Evaluate(Expression exp) 
    { 
     if (exp.NodeType == ExpressionType.Constant) 
     { 
      return ((ConstantExpression) exp).Value; 
     } 
     else 
     { 
      MemberExpression mexp = (MemberExpression) exp; 
      object value = Evaluate(mexp.Expression); 

      FieldInfo field = mexp.Member as FieldInfo; 
      if (field != null) 
      { 
       return field.GetValue(value); 
      } 
      else 
      { 
       PropertyInfo property = (PropertyInfo) mexp.Member; 
       return property.GetValue(value, null); 
      } 
     } 
    } 
} 

Questo sostituirà le complesse espressioni costanti ai loro valori originali, nonché i nomi dei parametri ai loro nomi dei tipi. Quindi devi solo creare una nuova istanza KeyGeneratorVisitor e chiamare il suo metodo Visit o VisitAndConvert con la tua espressione.

Si prega di notare che il metodo Expression.ToString verrà richiamato anche sui vostri tipi complessi, quindi o ignorare i loro ToString metodi o di scrivere la logica personalizzata per loro nel metodo Evaluate.

3

Ho cercato di capire uno scenario in cui questo tipo di approccio potrebbe essere utile senza portare a una memoria ingombrante che è faticosamente difficile da mantenere.

So che questo non è direttamente rispondere alla tua domanda, ma vorrei sollevare alcune domande su questo approccio che, in un primo momento, può sembrare allettante:

  • Come hai intenzione di gestire il parametro ordinamento? Vale a dire. (x => x.blah == "slug" & &! x.Cancellato) chiave della cache dovrebbe essere uguale (x =>! X.Deleted & & x.blah == "slug") chiave di cache.
  • Come pensavi di evitare gli oggetti duplicati nella cache? Vale a dire. La stessa farm da più query dovrebbe essere memorizzata in cache separatamente per ogni query. Ad esempio, per ogni lumaca visualizzata nella farm, abbiamo una copia separata della farm.
  • Estendere quanto sopra con più parametri, come pacchi, agricoltori, ecc. Porterebbe a più query corrispondenti con ciascuna una copia separata della farm memorizzata nella cache. Lo stesso vale per ogni tipo che potresti interrogare più i parametri potrebbero non essere nello stesso ordine
  • Ora, cosa succede se aggiorni la fattoria? Senza sapere quali query memorizzate nella cache contengono la tua farm, sarai costretto a uccidere l'intera cache. Che tipo di controproducente è quello che stai cercando di ottenere.

Posso vedere il ragionamento dietro questo approccio. Uno strato di prestazioni di manutenzione 0. Tuttavia, se i punti di cui sopra non vengono presi in considerazione, l'approccio prima ucciderà le prestazioni, quindi porterà a molti tentativi di mantenerlo, quindi si dimostrerà completamente non-mantenibile.

Sono stato su quella strada. Alla fine ho perso un sacco di tempo e ho rinunciato.

Ho trovato un approccio molto migliore mettendo in cache ciascuna entità risultante separatamente quando i risultati provengono dal back-end con un metodo di estensione per ogni tipo separatamente o tramite un'interfaccia comune.

Quindi è possibile creare un metodo di estensione per le espressioni lambda per provare prima la cache prima di colpire il db.

var query = (x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false); 
var results = query.FromCache(); 
if (!results.Any()) { 
    results = query.FromDatabase(); 
    results.ForEach(x = x.ToCache()); 
} 

Naturalmente, sarà ancora bisogno di tenere traccia quali query hanno effettivamente colpito il database per evitare di query Un ritorno 3 allevamenti da DB soddisfacente interrogazione B con una sola azienda agricola corrispondente dalla cache mentre il database sarebbe in realtà avere 20 aziende agricole corrispondenti a disposizione. Quindi, ogni query deve necessariamente premere DB almeno una volta.

E devi tenere traccia delle query che restituiscono 0 risultati per evitare che colpiscano di conseguenza il DB per nulla.

Ma, tutto sommato, si ottiene via con molto meno codice e come bonus, quando si aggiorna una fattoria, è possibile

var farm = (f => f.farmId == farmId).FromCache().First(); 
farm.Name = "My Test Farm"; 
var updatedFarm = farm.ToDatabase(); 
updatedFarm.ToCache(); 
+0

Ho effettivamente fatto un errore [qui] Le chiavi della cache vengono create valutando parzialmente l'espressione lambda in modo che solo il ritorno da IQueryable sia memorizzato nella cache. Al momento dell'aggiornamento, la cache cancellerà tutti gli oggetti memorizzati nella cache con una chiave specifica. È ancora un lavoro in corso, ma sembra molto promettente. Devo fare un po 'di ordine re objectcontext vs dbcontext. (Https://github.com/JimBobSquarePants/EFBootstrap) –

0

ne dite:

var call = expression.Body as MethodCallExpression; 

if (call != null) 
{ 

    List<object> list = new List<object>(); 

    foreach (Expression argument in call.Arguments) 
    { 

     object o = Expression.Lambda(argument, expression.Parameters).Compile().DynamicInvoke(); 

     list.Add(o); 

    } 

    StringBuilder keyValue = new StringBuilder(); 

    keyValue.Append(expression.Body.ToString()); 

    list.ForEach(e => keyValue.Append(String.Format("_{0}", e.ToString()))); 

    string key = keyValue.ToString(); 

} 
Problemi correlati