2010-10-14 13 views
6

Ich habe eine komplexe LINQ-Abfrage (mit LINQ 2 EF), die doppelte Ergebnisse zurückgeben kann, und ich verwende daher die .Distinct()-Methode, um Duplikate zu vermeiden. Hier ist das Skelett:LINQ Select Distinct beim Ignorieren des XML-Felds

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

Jede der Unterabfragen eine gemeinsame Benutzertabelle mit einer anderen Tabelle und führen Sie eine ‚wo‘ Abfrage werden die Ergebnisse kombiniert später im .Union(...). Das funktionierte gut, bis die Tabelle geändert wurde eine XML-Spalte enthält, das in dieser Ausnahme ergibt:

der XML-Datentyp nicht als verschieden gewählt werden kann, weil sie nicht vergleichbar sind

In diesem Fall I Es ist mir egal, ob die XML-Spalte über die Ergebnisse hinweg äquivalent ist. eigentlich muss ich nur sicher sein, dass der Primärschlüssel UserId in den Ergebnissen eindeutig ist.

Gibt es eine Möglichkeit, Distinct() zu verwenden, aber die XML-Spalte zu ignorieren oder einen einfacheren Weg, um sicherzustellen, dass ich Datensätze aus dem Ergebnis mit der gleichen UserId auf effiziente Weise entfernen? Idealerweise würde dies doppelte Datensätze nicht aus der Datenbank abrufen und würde keine Nachbearbeitung erfordern, um die Duplikate zu entfernen.

Update: Ich habe herausgefunden, dass, wenn ich meine Fragen serialisiert vor der Zeit auf Arrays dann gibt es keine Notwendigkeit, da Linq2Objects für jede Art von Vergleich ist nicht das XML eindeutige Auswahl Problem. Zum Beispiel kann ich dies tun:

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

Also, was ich mich wirklich für einen Weg, um die Zwischenabfragen zu vermeiden, ist die Serialisierung und tut ein Linq2Entities ruft direkt das wird nicht mit doppelten UserId Unterlagen holen. Danke für alle bisherigen Antworten.

+1

Keine Antwort auf genaues Problem, aber im Allgemeinen, wenn Sie 'Distinct' mit einigen Verkettungen wollen, verwenden Sie direkt 'Union'. Setze Operationen wie 'Union', 'Ausgenommen', 'Schneiden' usw. entfernt sowieso Duplikate. Also in Ihrem Fall, nur: 'subQuery1.Union (subQuery2) .ToArray()' – nawfal

Antwort

1

Diese Erweiterung Methode sollte eine Liste der Elemente nur mit dem ersten Element aus jedem Satz von Duplikaten in sie zurückkehren ...

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; 
    } 
} 

es wäre wie diese list.RemoveDuplicates(x => x.UserID) auf einer Liste verwendet werden. Wenn zwei Einträge in Liste mit derselben Benutzer-ID vorhanden wären, würde nur die erste

+0

Gut, besser nennen Sie es "Distinct" oder "DistinctBy"? 'Remove' klingt nicht funktional, sondern nicht-seiteneffektfrei. – nawfal

3

Schreiben Sie eine IEqualityComparer<T> Implementierung für das Objekt, das Ihren XML-Typ enthält, und übergeben Sie es an Distinct. In der Equals Methode können Sie Gleichheits-Semantik implementieren, wie Sie möchten.

Dies ist eine praktische T4 Code Generation Vorlage ich mich für mein Team die Domain-Modelle zur Erzeugung von IEqualityComparer<T> Implementierungen geschrieben:

<#@ 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) 
#> 
} 

Es macht die Annahme, dass jedes Ihrer Modellklassen eine Eigenschaft „ID“ genannt haben Dies ist der Primärschlüssel, gespeichert als etwas, das Equals implementiert. Unsere Konvention zwingt alle unsere Modelle, diese Eigenschaft zu besitzen. Wenn Ihre Modelle alle ID-Eigenschaften mit unterschiedlichen Namen haben, sollten Sie entweder diese T4-Vorlage an Ihre Bedürfnisse anpassen oder noch besser, um Ihnen das Leben zu erleichtern (nicht nur um dieses T4 zu verwenden) und Ihre Modelle zu ändern " Name.

+0

Die T4-Vorlage ist sicher praktisch, aber um den IEqualityComparer zu verwenden, muss ich zuerst meine beiden Abfragen zu Arrays serailisieren (da linq2entities den Vergleich nicht unterstützt), bevor ich Duplikate entfernen kann. Nichtsdestotrotz ist es etwas, das vorerst funktioniert, danke! – TJB

+0

@ TJB: Ah. Ich habe keine Erfahrung mit LINQ-to-Entities. Ich verwende einfach den 'IEqualityComparer ' für LINQ-to-Objekte in In-Memory-Sammlungen für eine schnelle distinct-by-ID. Stehe gerne zur Verfügung! T4 Vorlagen Regel :) –

2

als James Dunne sagte, Sie ein IEqualityComparer

ein schnelles Mock-up etwas wie dies wäre verwenden wollen würden. Sie müssen "ObjectType" durch jeden Typ ersetzen, der in Ihrer subQuery1 und subQuery2 natürlich ist. Bitte beachten Sie, das ist nicht getestet:

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

Huckepack Antwort, was? :) Ich habe darüber nachgedacht zurück zu gehen und meine Antwort zu aktualisieren, um ein Beispiel hinzuzufügen, aber deins reicht genauso gut aus. –

+0

haha, in meiner Verteidigung, ich würde dies schreiben, bevor ich Ihre Antwort sah :) –

1

Sie morelinq ‚s DistinctBy nutzen könnten. Ich vermute (aber habe nicht verifiziert), dass dies sowie die Antworten IEqualityComparer und RemoveDuplicates die doppelten Datensätze von SQL Server abrufen und dann die Duplikate auf dem Client entfernen. Wenn jemand eine serverseitige Lösung anbietet, würde ich empfehlen, seine Antwort zu akzeptieren.

+0

Richtig, ich bin auf der Suche nach etwas, das die 'distinct' in SQL erreichen kann, anstatt in der Nachbearbeitung – TJB

0

Hinweis: Ich verwende Linq2SQL (nicht Linq2Entities) - aber wahrscheinlich funktioniert für beide.

Wenn Sie nicht möchten, dass der XML-Code für jede Abfrage zurückgegeben wird, können Sie die XML-Spalte als "Verzögerung laden" in der DBML-Datei festlegen.

Ich fügte eine AddressBook XML-Spalte zu einer Customer Tabelle hinzu, die plötzlich alle meine Suchen durchbrach. Sobald ich die Spalte zu DelayLoad=true wechselte, dann hat alles wieder funktioniert (weil es diese Spalte in DISTINCT nicht einschloss).

Abhängig von Ihren Daten kann diese Lösung (die eine Lazy-Load-Spalte darstellt) entweder Ihr System erheblich beschleunigen oder verlangsamen - seien Sie also vorsichtig!