2010-10-14 5 views
6

J'ai une requête LINQ complexe (en utilisant LINQ 2 EF) qui peut renvoyer des résultats en double et j'utilise donc la méthode .Distinct() pour éviter les doublons. Voici le squelette:LINQ Sélectionnez Distinct en ignorant le champ XML

var subQuery1 = // one query... 
var subQuery2 = // another query... 
var result = subQuery1.Distinct().Union(subQuery2.Distinct()).ToArray(); 

Chacune des requêtes sous joindre à une table utilisateur commune avec une autre table et effectuer une « où » la requête, les résultats sont ensuite combinés dans le .Union(...). Cela a bien fonctionné jusqu'à ce que la table a été modifiée pour inclure une colonne XML, ce qui conduit à cette exception:

le type de données XML ne peut pas être sélectionné comme distinct car il n'est pas comparable

Dans ce cas, je ne se soucie pas si la colonne XML est équivalente à travers les résultats. En fait, j'ai seulement besoin d'être assuré que la clé primaire UserId est distincte dans les résultats.

Existe-t-il un moyen d'utiliser Distinct() mais ignorer la colonne XML ou un moyen plus simple d'assurer que je supprime des enregistrements du résultat avec le même UserId d'une manière efficace? Idéalement, cela ne permettrait pas de récupérer les enregistrements en double de la base de données et ne nécessiterait pas de post-traitement pour supprimer les doublons.

Mise à jour: J'ai découvert que si je sérialiser mes requêtes aux tableaux à l'avance alors il n'y a pas besoin de tout type de comparateur depuis Linq2Objects n'a pas le problème de sélection distincte XML. Par exemple, je peux le faire:

var subQuery1 = // one query... 
var subQuery2 = // another query... 
var result = 
    subQuery1.Distinct().ToArray().Union( 
     subQuery2.Distinct().ToArray()) 
    .ToArray(); 

Ce que je cherche vraiment est un moyen d'éviter sérialisation des requêtes intermédiaires et faire un Linq2Entities appeler directement qui ne sera pas chercher les enregistrements en double avec UserId s. Merci pour toutes les réponses jusqu'à présent.

+1

Pas une réponse à un problème exact, mais dans le cas en général si vous voulez 'Distinct' avec une concaténation, utilisez directement' Union'. Définir des opérations comme 'Union',' Except', 'Intersect' etc supprime les doublons de toute façon. Donc dans votre cas, juste: 'subQuery1.Union (subQuery2) .ToArray()' – nawfal

Répondre

1

Cette méthode d'extension doit retourner une liste d'articles avec seulement le premier élément de chaque série de doublons dans ce ...

public static IEnumerable<Tsource> RemoveDuplicates<Tkey, Tsource>(this IEnumerable<Tsource> source, Func<Tsource, Tkey> keySelector) 
{ 
    var hashset = new HashSet<Tkey>(); 
    foreach (var item in source) 
    { 
     var key = keySelector(item); 
     if (hashset.Add(key)) 
      yield return item; 
    } 
} 

serait utilisé sur une telle liste list.RemoveDuplicates(x => x.UserID). S'il y avait deux enregistrements dans List avec le même ID utilisateur, il retournait uniquement le premier

+0

Bon, mieux l'appeler 'Distinct' ou' DistinctBy'? 'Remove' n'a pas l'air fonctionnel, plutôt que sans effets secondaires. – nawfal

3

Écrivez une implémentation IEqualityComparer<T> pour l'objet qui contient votre type XML et passez-le à Distinct. Dans la méthode Equals, vous pouvez implémenter la sémantique d'égalité comme vous le souhaitez.

Ceci est un modèle pratique de génération de code T4 je me suis écrit pour générer IEqualityComparer<T> implémentations pour les modèles de domaine de mon équipe:

<#@ template language="C#v3.5" debug="True" #> 
<#@ output extension=".generated.cs" #> 
<# 
    var modelNames = new string[] { 
     "ClassName1", 
     "ClassName2", 
     "ClassName3", 
    }; 

    var namespaceName = "MyNamespace"; 
#> 
using System; 
using System.Collections.Generic; 

namespace <#= namespaceName #> 
{ 
<# 
    for (int i = 0; i < modelNames.Length; ++i) 
    { 
     string modelName = modelNames[i]; 
     string eqcmpClassName = modelName + "ByIDEqualityComparer"; 
#> 
    #region <#= eqcmpClassName #> 

    /// <summary> 
    /// Use this EqualityComparer class to determine uniqueness among <#= modelName #> instances 
    /// by using only checking the ID property. 
    /// </summary> 
    [System.Diagnostics.DebuggerNonUserCode] 
    public sealed partial class <#= eqcmpClassName #> : IEqualityComparer<<#= modelName #>> 
    { 
     public bool Equals(<#= modelName #> x, <#= modelName #> y) 
     { 
      if ((x == null) && (y == null)) return true; 
      if ((x == null) || (y == null)) return false; 

      return x.ID.Equals(y.ID); 
     } 

     public int GetHashCode(<#= modelName #> obj) 
     { 
      if (obj == null) return 0; 

      return obj.ID.GetHashCode(); 
     } 
    } 

    #endregion 
<# 
     if (i < modelNames.Length - 1) WriteLine(String.Empty); 
    } // for (int i = 0; i < modelNames.Length; ++i) 
#> 
} 

Il fait l'hypothèse que chacune de vos classes de modèle ont une propriété nommée « ID » qui est la clé primaire, stockée comme quelque chose qui implémente Equals. Notre convention oblige tous nos modèles à avoir cette propriété. Si vos modèles ont tous des propriétés d'ID nommées différemment, envisagez de modifier ce modèle T4 en fonction de vos besoins ou, mieux encore, simplifiez-vous la vie (pas seulement pour utiliser ce T4) et changez vos modèles pour utiliser le "ID". " prénom.

+0

Le template T4 est très pratique, mais pour utiliser IEqualityComparer, je dois d'abord sérialiser mes requêtes vers les tableaux (car linq2entities ne supporte pas le comparateur) avant de pouvoir supprimer les doublons. Néanmoins, c'est quelque chose qui fonctionne pour le moment, merci! – TJB

+0

@TJB: Ah. Je n'ai aucune expérience avec LINQ-to-entities. J'utilise juste le 'IEqualityComparer ' pour LINQ-to-objects sur les collections en mémoire pour un rapide distinct-by-ID. Ravi d'être utile! Règle des templates T4 :) –

2

comme James Dunne dit, vous voudriez utiliser un IEqualityComparer

une place de simulation rapide serait quelque chose comme ça. Vous devrez remplacer "ObjectType" par n'importe quel type dans votre sous-requête1 et subQuery2 bien sûr. S'il vous plaît noter que ce n'est pas testé:

List<ObjectType> listQueries = new List<ObjectType>(); 

ObjectTypeEqualityComparer objectTypeComparer = new ObjectTypeEqualityComparer(); 

listQueries.AddRange(subQuery1);// your first query 
listQueries.AddRange(subQuery2); // your second query 
ObjectType[] result = listQueries.Distinct(objectTypeComparer).ToArray(); 


class ObjectTypeEqualityComparer : IEqualityComparer<ObjectType> 
{ 
    public bool Equals(ObjectType obj1, ObjectType obj2) 
    { 
     return obj1.UserId == obj2.UserId ? true : false; 
    } 

    public int GetHashCode(ObjectType obj) 
    { 
     return obj.UserId.GetHashCode(); 
    } 

} 
+0

Piggyback answer, hein? :) J'envisageais de revenir en arrière et de mettre à jour ma réponse pour inclure un exemple, mais le tien suffit aussi bien. –

+0

haha, pour ma défense, j'allais écrire ceci avant que j'ai vu votre réponse :) –

1

Vous pouvez utiliser morelinq « s DistinctBy. Je soupçonne (mais je n'ai pas vérifié) que ceci, aussi bien que les réponses IEqualityComparer et RemoveDuplicates, récupèrera les enregistrements dupliqués de SQL Server et enlèvera alors les doublons sur le client. Si quelqu'un fournit une solution côté serveur, je recommanderais d'accepter leur réponse.

+0

À droite, je cherche quelque chose qui peut atteindre le 'distinct' en sql plutôt qu'en post-traitement – TJB

0

Note: J'utilise Linq2SQL (pas Linq2Entities) - mais fonctionne probablement pour les deux.

Si vous ne voulez pas toujours que le XML renvoyé pour chaque requête, vous pouvez définir la colonne XML comme «Chargement différé» dans le fichier DBML.

J'ai ajouté une colonne XML AddressBook à une table Customer qui a soudainement brisé toutes mes recherches. Une fois que j'ai changé la colonne à DelayLoad=true alors tout a fonctionné à nouveau (parce qu'il n'incluait pas cette colonne dans DISTINCT). En fonction de vos données, cette solution (créant une colonne de chargement paresseux) peut accélérer ou ralentir votre système de manière significative - alors faites attention!

Questions connexes