2015-11-19 38 views
5

sto ottenendo il seguente erroreCombinando molteplici espressioni alberi

Il parametro 'p' non era legato in LINQ to Entities specificato interrogazione espressione.

ho capito il problema (stessa istanza di ParameterExpression deve essere utilizzato con tutte le espressioni nella struttura) e hanno tentato di utilizzare le soluzioni che ho trovato online, ma senza fortuna.

Questo è il mio metodo

private void SeedEntity<TEntity>(DatabaseContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions) where TEntity : class 
{ 
    Expression<Func<TEntity, bool>> allExpresions = null; 

    var parameters = identifierExpressions.SelectMany(x => x.Parameters).GroupBy(x => x.Name).Select(p => p.First()).ToList(); 

    foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions) 
    { 
     Func<TEntity, object> vv = identifierExpression.Compile(); 
     object constant = vv(entity); 

     ConstantExpression constExp = Expression.Constant(constant, typeof(object)); 
     BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp); 
     Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, parameters); 

     if (allExpresions == null) 
     { 
      allExpresions = equalExpression2; 
     } 
     else 
     { 
      BinaryExpression bin = Expression.And(allExpresions.Body, equalExpression2.Body); 
      allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, parameters); 
     } 
    } 

    TEntity existingEntity = null; 
    if (allExpresions != null) 
    { 
     existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions); 
    } 

    if (existingEntity == null) 
    { 
     context.Set<TEntity>().Add(entity); 
    } 
    else 
    { 
     entity = existingEntity; 
    } 
} 

Esso genera un'espressione per la ricerca di un soggetto sulla base di un certo numero di proprietà.

Funziona bene per una singola espressione, l'errore si verifica solo quando si passa in più.

Chiamato in questo modo:

SeedEntity(context, ref e, p=> p.Name);//Works 
SeedEntity(context, ref e, p=> p.Name, p=> p.Age);//Fails 

genera qualcosa di simile a me di eseguire la seguente:

context.Set<TEntity>().FirstOrDefault(p=>p.Name == e.Name && p.Age == e.Age); 

Sostituzione e.Name & & I.età con un ConstantExpression

Puoi vedi nel metodo sopra, prendo tutti i parametri univoci e li memorizzo in parameters al p, quindi utilizzare la stessa variabile in tutto.Questo è l'inizio, ma poi ho bisogno di sostituire le istanze del parametro in ciascuno dei Expression<Func<TEntity, bool>> passato come l'array params, questo è dove sto fallendo.

Ho provato enumerare le espressioni e utilizzare il metodo di .Update() passaggio nelle params

Ho anche provato una soluzione che utilizza il ExpressionVisitor

public class ExpressionSubstitute : ExpressionVisitor 
{ 
    public readonly Expression from, to; 
    public ExpressionSubstitute(Expression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 
    public override Expression Visit(Expression node) 
    { 
     if (node == from) return to; 
     return base.Visit(node); 
    } 
} 

public static class ExpressionSubstituteExtentions 
{ 
    public static Expression<Func<TEntity, TReturnType>> RewireLambdaExpression<TEntity, TReturnType>(Expression<Func<TEntity, TReturnType>> expression, ParameterExpression newLambdaParameter) 
    { 
     var newExp = new ExpressionSubstitute(expression.Parameters.Single(), newLambdaParameter).Visit(expression); 
     return (Expression<Func<TEntity, TReturnType>>)newExp; 
    } 
} 
+0

Solo un pensiero veloce, hai provato a utilizzare una lettera diversa per il secondo parametro? (cioè p => p.Name, f => f.ge) –

+0

Grazie per l'input, questo non funzionerebbe mai, in quanto hai solo un parametro ma il tuo passaggio in due. Viene generato un numero errato di parametri forniti per lambda –

+2

Invece di combinare le query, perché non applicarle successivamente? 'risultati =/* set completo * /; foreach (espressione) {results = results.Where (espressione)} '? Poiché EF utilizza 'IQueryable', il framework rinvierà l'esecuzione fino a quando non sarà necessario, quindi combinerà tutti i predicati in una singola query per SQL. – Basic

risposta

4

Sei davvero vicino. Non vedo il punto della tua variabile parameters. Raggrupparli per nome è un errore. Perché non passare semplicemente i parametri dall'espressione? Quindi visitare se necessario. Il tuo codice visitatore è a posto.

private static void SeedEntity<TEntity>(DbContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions) 
     where TEntity : class 
    { 
     Expression<Func<TEntity, bool>> allExpresions = null; 

     foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions) 
     { 
      Func<TEntity, object> vv = identifierExpression.Compile(); 
      object constant = vv(entity); 

      ConstantExpression constExp = Expression.Constant(constant, typeof(object)); 
      BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp); 
      Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, identifierExpression.Parameters); 

      if (allExpresions == null) 
      { 
       allExpresions = equalExpression2; 
      } 
      else 
      { 
       var visitor = new ExpressionSubstitute(allExpresions.Parameters[0], identifierExpression.Parameters[0]); 
       var modifiedAll = (Expression<Func<TEntity,bool>>)visitor.Visit(allExpresions); 
       BinaryExpression bin = Expression.And(modifiedAll.Body, equalExpression2.Body); 
       allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, identifierExpression.Parameters); 
      } 
     } 

     TEntity existingEntity = null; 
     if (allExpresions != null) 
     { 
      existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions); 
     } 

     if (existingEntity == null) 
     { 
      context.Set<TEntity>().Add(entity); 
     } 
     else 
     { 
      entity = existingEntity; 
     } 
    } 
+1

Ho acquisito le autorizzazioni univoche pensando di poter riutilizzare quell'istanza sostituendo i parametri (non nell'esempio precedente). La soluzione funziona, grazie mille. Vedo dove ho sbagliato ora –