2009-08-12 10 views
9

J'ai commencé avec this question, ce que j'ai en quelque sorte répondu there, et maintenant je pose la question plus fondamentale ici. J'ai simplifié la requête à ceci:Linq "Impossible de traduire l'expression ... en SQL et ne pouvait pas la traiter comme une expression locale."

var q = from ent in LinqUtils.GetTable<Entity>() 
     from tel in ent.Telephones.DefaultIfEmpty() 
     select new { 
      Name = ent.FormattedName, 
      Tel = tel != null ? tel.FormattedNumber : "" // this is what causes the error 
     }; 

tel.FormattedNumber est une propriété qui combine les Number et Extension champs dans une chaîne parfaitement formatée. Et voici l'erreur qui en résulte:

System.InvalidOperationException: Could not translate expression 'Table(Entity).SelectMany(ent => ent.Telephones.DefaultIfEmpty(), (ent, tel) => new <>f__AnonymousType0`2(Name = ent.FormattedName, Tel = IIF((tel != null), tel.FormattedNumber, "")))' into SQL and could not treat it as a local expression. 

Si je change la référence ci-dessus de FormattedNumber à tout simplement Number, tout fonctionne très bien. Mais je souhaite que le numéro formaté s'affiche correctement dans ma liste. Que recommandez-vous comme la façon la plus propre et la plus propre de le faire?

Répondre

11

Vous pouvez utiliser AsEnumerable sur l'entité, mais cela l'obligerait à ramener toutes les colonnes (même si elles ne sont pas utilisées); peut-être plutôt quelque chose comme:

var q1 = from ent in LinqUtils.GetTable<Entity>() 
     from tel in ent.Telephones.DefaultIfEmpty() 
     select new { 
      Name = ent.FormattedName, 
      Number = (tel == null ? null : ent.Number), 
      Extension = (tel == null ? null : ent.Extension) 
     }; 

var q2 = from row in q1.AsEnumerable() 
     select new { 
      row.Name, 
      FormattedNumber = FormatNumber(row.Number, row.Extension) 
     }; 

FormatNumber est une méthode qui prend les deux et les fusionne, réutilisés probablement de votre autre code (propriété). Avec LINQ-to-SQL, une autre option consiste à exposer une fonction définie par l'utilisateur sur le contexte de données qui effectue le formatage dans la base de données; un exemple légèrement différent:

var qry = from cust in ctx.Customers // and tel 
      select new { 
       cust.Name, 
       FormattedNumber = ctx.FormatNumber(tel.Number, tel.Extension) 
      }; 

(qui fera le travail à la base de données, que ce soit ou non qui est une bonne idée ;-p)

1

@Marc Gravell m'a battu à la réponse, le crédit aussi les différents répondeurs à this question qui m'ont mis sur la bonne voie.

Je l'ai fait un peu comme la première suggestion de Marc, comme ceci:

var q1 = from ent in LinqUtils.GetTable<Entity>() 
     from tel in ent.Telephones.DefaultIfEmpty() 
     select new { ent, tel }; 
var q2 = from q in q1.AsEnumerable() 
     select new { 
      Name = q.ent.FormattedName, 
      Tel = q.tel != null ? q.tel.FormattedNumber : "" 
     }; 

Et qui l'a fait! Merci, tous!

3

La manière la plus simple est d'indiquer les champs que vous voulez réellement dans l'expression, de les placer dans vos objets de niveau intermédiaire, puis d'utiliser les fonctions auxiliaires pour les modifier. Je ne suis pas sûr si vous réalisez qu'une classe représentant la table SQL pour LINQ est une classe DTO - elle définit la grammaire utilisée par le traducteur LINQ-SQL. L'injection d'une propriété dans un DTO qui n'est pas mappé à la table SQL n'est même pas prise en charge, ce qui signifie que le traducteur peut se déclencher à volonté. Les attributs définissent la grammaire et tout ce qui n'est pas défini par eux n'existe pas pour le traducteur d'expression.

Les entités nommées dans la clause from ne sont pas des objets - ce sont juste des symboles utilisés pour aider à épeler les champs de table réels qui seront récupérés. Un champ non nommé explicitement dans select est un champ non récupéré - du moins c'est l'objectif du traducteur, il peut en laisser passer quelques-uns. Par exemple si ent.FormattedName n'est pas déclaré, c'est un glissement et peut exploser dernier. Par conséquent, cette propriété FormattedNumber injectée dans la classe DTO n'existe même pas dans la grammaire. Ce n'est pas un "champ calculé" - ce terme est strictement pour les définitions de tables SQL et si vous en aviez un, ce serait dans la grammaire de DTO.Notez que l'erreur dit très précisément "expression locale" - portée très limitée.

Vous pourriez essayer de tricher avec une expression lambda imbriquée en invoquant une fonction statique sur tout le "tel" qui pourrait triger la récupération de tout l'enregistrement - ou en lançant une autre exception.

D'autres LINQ-s, qui ne sont pas des traducteurs, peuvent avoir des règles assouplies. LINQ-SQL doit être très strict ou très lent et c'est déjà assez lent :-)

Questions connexes