Clause de sélection contenant des appels de méthode non EF

c# entity-framework linq linq-to-entities postgresql

Question

Je ne parviens pas à créer une requête LINQ Entity Framework dont la clause select contient des appels de méthode à des objets non-EF.

Le code ci-dessous fait partie d'une application utilisée pour transformer les données d'un SGBD en un schéma différent sur un autre SGBD. Dans le code ci-dessous, le rôle est ma classe personnalisée non liée au SGBD, et les autres classes sont toutes générées par Entity Framework à partir de mon schéma de base de données:

// set up ObjectContext's for Old and new DB schemas
var New = new NewModel.NewEntities();
var Old = new OldModel.OldEntities();

// cache all Role names and IDs in the new-schema roles table into a dictionary
var newRoles = New.roles.ToDictionary(row => row.rolename, row => row.roleid);

// create a list or Role objects where Name is name in the old DB, while
// ID is the ID corresponding to that name in the new DB
var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] };
var list = roles.ToList();

Mais appeler ToList me donne cette exception NotSupportedException:

LINQ to Entities ne reconnaît pas la méthode 'Int32 get_Item (System.String)', et cette méthode ne peut pas être traduite en expression de magasin.

On dirait que LINQ-to-Entities m'interroge sur mon appel pour extraire la valeur du dictionnaire en prenant le nom comme clé. Certes, je ne comprends pas assez EF pour savoir pourquoi il s’agit d’un problème.

J'utilise le fournisseur d'infrastructure d'entité dotConnect for PostgreSQL de devart, bien que je suppose à ce stade qu'il ne s'agit pas d'un problème spécifique au SGBD.

Je sais que je peux le faire fonctionner en divisant ma requête en deux requêtes, comme celle-ci:

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r;
var roles2 = from r in roles.AsEnumerable()
            select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] };
var list = roles2.ToList();

Mais je me demandais s’il existait un moyen plus élégant et / ou plus efficace de résoudre ce problème, idéalement sans le scinder en deux requêtes.

Quoi qu'il en soit, ma question est en deux parties:

Premièrement, puis-je transformer cette requête LINQ en quelque chose qu'Entity Framework acceptera, idéalement sans se scinder en deux?

Deuxièmement, j'aimerais aussi comprendre un peu EF et comprendre pourquoi EF ne peut pas superposer mon code .NET personnalisé au-dessus de l'accès à la base de données. Mon SGBD n'a aucune idée de la façon d'appeler une méthode sur une classe de dictionnaire, mais pourquoi EF ne peut-il pas simplement faire ces appels à une méthode de dictionnaire après qu'il ait déjà extrait des données de la base de données? Bien sûr, si je voulais composer plusieurs requêtes EF ensemble et mettre du code .NET personnalisé au milieu, je m'attendrais à ce que cela échoue, mais dans ce cas, le code .NET n'est qu'à la fin, alors pourquoi est-ce un problème pour EF? Je suppose que la réponse est quelque chose comme "cette fonctionnalité n'a pas été intégrée à EF 1.0", mais je cherche un peu plus d'explications sur la raison pour laquelle cette opération est suffisamment difficile pour justifier son exclusion de EF 1.0.

Réponse acceptée

Le problème est qu’en utilisant l’exécution retardée de Linq, vous devez vraiment décider où vous voulez que le traitement soit effectué et quelles données vous voulez transmettre par le canal à votre application cliente. Dans un premier temps, Linq résout l'expression et extrait toutes les données du rôle en tant que précurseur de

New.roles.ToDictionary(row => row.rolename, row => row.roleid);

À ce stade, les données sont transférées de la base de données vers le client et sont transformées en dictionnaire. Jusqu'ici tout va bien.

Le problème est que votre deuxième expression Linq demande à Linq d'effectuer la transformation sur la deuxième base de données à l' aide du dictionnaire de la base de données . En d'autres termes, il essaie de trouver un moyen de transmettre la structure de dictionnaire entière à la base de données afin qu'elle puisse sélectionner la valeur d'ID correcte dans le cadre de l'exécution différée de la requête. Je soupçonne que cela résoudrait très bien si vous changiez la seconde moitié en

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r.RoleName;
var list = roles.ToDictionary(roleName => roleName, newRoles[roleName]);

De cette manière, votre sélection sur la base de données (en sélectionnant uniquement le nom de famille) sera résolue comme un précurseur du traitement de l'appel ToDictionary (ce qui devrait être fait sur le client comme vous le souhaitiez). C'est essentiellement ce que vous faites dans votre deuxième exemple, car AsEnumerable extrait les données vers le client avant de les utiliser dans l'appel ToList. Vous pouvez aussi facilement le changer en quelque chose comme

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r;
var list = roles.AsEnumerable().Select(r => new Role { Name = r.RoleName, ID = newRoles[r.RoleName] });

et ça marcherait pareil. L'appel à AsEnumerable () résout la requête en extrayant les données vers le client pour les utiliser dans la sélection qui les suit.

Notez que je n’ai pas testé cela, mais pour autant que je comprenne Entity Framework, c’est ma meilleure explication de ce qui se passe sous le capot.


Réponse populaire

Jacob a totalement raison. Vous ne pouvez pas transformer la requête souhaitée sans la scinder en deux parties, car Entity Framework ne peut pas traduire l'appel get_Item dans la requête SQL.
Le seul moyen est d'écrire la requête LINQ to Entities puis d'écrire une requête LINQ to Objects dans son résultat, comme l'a conseillé Jacob.
Le problème est spécifique à Entity-Framework, il ne découle pas de notre implémentation du support Entity Framework.



Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow