2012-12-06 2 views
6

J'ai lu environ 30 questions Stack Overflow, et environ 20 blogs et ne trouve pas ma réponse. Cela dit, je suis sûr que la réponse que je demande est là, donc si vous le savez, veuillez le signaler (veuillez noter le dernier paragraphe/phrase sur les réponses qui ne correspondent pas à mes exigences). Merci!Expression <Func <TEntity, bool >> imbriquant qui fonctionne avec Entity Framework

Je suis cas persistant de classes qui ont une forme similaire à:

public class Data { 
    public int Id { get; set; } 
} 

public class Container { 
    public int Id { get; set; } 
    public Data Data { get; set; } 
} 

Actuellement les requêtes sont écrites pour trouver des conteneurs via une couche d'abstraction qui nécessite une expression Lambda accepter un récipient et retour bool (prédicat). Il n'acceptera pas IQueryable même si Entity Framework 5 est l'ORM de son choix.

Ma tâche consiste à présenter une surface d'API basée sur des expressions lambda acceptant le type de données. Je ne peux pas changer la couche d'abstraction (je dois passer un prédicat qui accepte un conteneur), donc je suis en train de convertir une expression que je reçois comme:

Expression<Func<Data , bool>> 
to: 
Expression<Func<Container , bool>> 

J'ai ajouté une méthode supplémentaire dans ma classe référentiel comme celles-ci:

public Container Find(Expression<Func<Data , bool>> predicate) { 

    IEnumerable<Container> result = QueryStrategy.Fetch(c => predicate.Compile().Invoke(c.Data)); 

    return result.FirstOrDefault(); 

} 

Ce complimente la méthode Find existant:

public Container Find(Expression<Func<Container , bool>> predicate) { 

    IEnumerable<Container> result = QueryStrategy.Fetch(predicate); 

    return result.FirstOrDefault(); 

} 

Lorsque la première méthode est utilisée, elle produit l'exception suivante:

LINQ to Entities ne reconnaît pas la méthode « booléenne Invoke (Container.Data) » méthode, et cette méthode ne peut pas être traduit dans une expression de magasin.

J'ai essayé toutes sortes de choses avec les classes d'expression je ne peux pas voir un moyen de la carte:

Expression<Func<Data , bool>> 
to: 
Expression<Func<Container , bool>> 

Sans utiliser Invoke qui ne sont pas pris en charge par Entity Framework (mais fonctionne très bien avec en mémoire des données énumérables).

Quelqu'un peut-il m'aider à faire fonctionner le scénario ci-dessus en utilisant Expressions? Je comprends que je peux utiliser la librairie LinqKit pour résoudre ce problème, mais je veux vraiment résoudre ce problème sans avoir recours à une bibliothèque tierce. MISE À JOUR: Dans mon effort pour présenter un problème simplifié, j'ai impliqué (en utilisant initialement DbContext dans l'exemple de code) que le code aurait accès à IQueryable et/ou serait approprié pour utiliser AsExpandable() de LinqKit. En réalité, ce n'est pas le cas - la classe repository n'est pas autorisée à utiliser IQueryable ou toute autre extension spécifique au fournisseur. J'ai modifié les exemples ci-dessus et j'espère que cela rend les choses plus claires.

Ce problème est résolu

+0

Est-ce une exigence que le filtrage des conteneurs par votre expression Func a lieu dans la base de données? –

+0

Bonne question - Oui c'est. – FantasticJamieBurns

+0

Une raison particulière pour laquelle vous ne voulez pas apporter une bibliothèque tierce? Comme vous l'avez mentionné ci-dessus, LinqKit peut gérer la composition des expressions comme vous le souhaitez; le faire sans LinqKit nécessiterait probablement de dupliquer beaucoup de ses fonctionnalités. –

Répondre

2

j'ai réussi à résoudre le problème au bout de 4 heures de lecture/bricoler avec Expressions (technologie étonnante).

J'ai mis cette classe ensemble (il est poli ou finale, mais montre comment il a été atteint):

class ParameterRewriter<TTarget , TSource> : ExpressionVisitor { 

    private ParameterExpression Source; 
    private MemberExpression Target; 

    public Expression<Func<TTarget , bool>> Rewrite(Expression<Func<TSource , bool>> predicate , Expression<Func<TTarget , TSource>> propertyNameExpression) { 

     var parameter = Expression.Parameter(typeof(TTarget)); 

     var propertyName = (propertyNameExpression.Body as MemberExpression).Member.Name; 

     Source = predicate.Parameters.Single(); 
     Target = Expression.PropertyOrField(parameter , propertyName); 

     var body = Visit(predicate.Body); 

     return Expression.Lambda<Func<TTarget , bool>>(
      body , 
      parameter 
     ); 

    } 

    protected override Expression VisitParameter(ParameterExpression node) { 

     if (node == Source) { 
      return Target; 
     } 

     return base.VisitParameter(node); 

    } 

} 

Et il peut être utilisé comme ceci:

var parameterRewriter = new ParameterRewriter<Container , Data>(); 
Expression<Func<Data , bool>> dataPredicate = d => (d.Id == 1); 
var containerPredicate = parameterRewriter.Rewrite(dataPredicate , c => c.Data); 

En théorie un devrait être capable de traverser des relations plus profondes en examinant propertyNameExpression mais j'en ai assez pour aujourd'hui.

Je peux maintenant voir que le Entity Framework SQL a généré pour chaque saveur de requête est identique:

======================= =

Expression<Func<Container , bool>> p = c => c.Data.Id == 1 

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Data_Id] AS [Data_Id] 
FROM [dbo].[Container] AS [Extent1] 
WHERE 1 = [Extent1].[Data_Id] 

========================

Expression<Func<Data , bool>> p = d => d.Id == 1 

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Data_Id] AS [Data_Id] 
FROM [dbo].[Container] AS [Extent1] 
WHERE 1 = [Extent1].[Data_Id] 

=========== =============

+1

@SeanHill Maintenant, dites-moi! Haha - il est 05h30 ici et j'aurais pu dormir;) – FantasticJamieBurns

1

Rejoignez clients filtrés par des données reposeraient, puis sélectionnez le client à partir du résultat

public Container Find(Expression<Func<Data, bool>> predicate) 
{ 
    return MyDbContext.Containers 
         .Join(MyDbContext.Containers.Select(c => c.Data) 
                .Where(predicate), 
          c => c.Data.Id, 
          d => d.Id, 
          (c, d) => c) 
         .FirstOrDefault(); 
} 
+0

Il n'y a pas MyDbContext.Datas exposée. – FantasticJamieBurns

+0

@Jamie 'Data' n'est pas une entité? Quoi qu'il en soit, vous pouvez vous joindre à la table. Je mettrai à jour la réponse –

+0

Ce n'est pas une racine agrégée. – FantasticJamieBurns

Questions connexes