2010-10-14 15 views
6

Tengo una consulta LINQ compleja (utilizando LINQ 2 EF) que puede devolver resultados duplicados y estoy utilizando el método .Distinct() para evitar duplicados. Aquí está el esqueleto:LINQ Seleccione Distinct al ignorar el campo XML

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

Cada una de las sub consultas se unen a una tabla de usuario común con otra mesa y realizar una 'dónde' consulta, los resultados se combinaron más adelante en el .Union(...). Esto funcionó bien hasta que la tabla se modificó para incluir una columna XML, lo que resulta en esta excepción:

el tipo de datos xml no se puede seleccionar como distinta porque no es comparable

En este caso I no importa si la columna XML es equivalente en todos los resultados. de hecho, solo necesito estar seguro de que la clave principal UserId es distinta en los resultados.

¿Hay alguna manera de usar Distinct() pero ignore la columna XML o una forma más simple de asegurar que elimine los registros del resultado con el mismo UserId de una manera eficiente? Idealmente, esto no recuperaría registros duplicados de la base de datos y no requeriría un procesamiento posterior para eliminar los duplicados.

Actualización: he descubierto que si serializar mis consultas a las matrices antes de tiempo, entonces no hay necesidad de ningún tipo de comparador ya Linq2Objects no tiene el problema de selección distinta XML. Por ejemplo, yo puedo hacer esto:

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

Así que lo que realmente estoy buscando es una manera de evitar la serialización de las consultas intermedias y hacer un Linq2Entities llamar directamente que no se devolverán los registros duplicados con UserId s. Gracias por todas las respuestas hasta el momento.

método
+1

No es una respuesta al problema exacto, pero en el caso de que en general, si desea 'Distinct' junto con alguna concatenación, use directamente' Union'. Establecer operaciones como 'Unión',' Excepto', 'Intersecar' etc. elimina duplicados de todos modos. Entonces en su caso, simplemente: 'subQuery1.Union (subQuery2) .ToArray()' – nawfal

Respuesta

1

Esta extensión debe devolver una lista de artículos con sólo el primer elemento de cada conjunto de duplicados que está dentro ...

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

que sería utilizado en una lista como esta list.RemoveDuplicates(x => x.UserID). Si hubiera dos registros en List con el mismo ID de usuario, solo devolvería el primer

+0

Bueno, mejor llámelo 'Distinct' o' DistinctBy'? 'Quitar' no suena funcional, sino que no es libre de efectos secundarios. – nawfal

3

Escriba una implementación IEqualityComparer<T> para el objeto que contiene su tipo XML y páselo a Distinct. En el método Equals puede implementar la semántica de igualdad como desee.

Ésta es una práctica plantilla T4 de generación de código que escribí yo mismo para generar IEqualityComparer<T> implementaciones de modelos de dominio de mi equipo:

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

Se hace la suposición de que cada una de las clases del modelo tienen una propiedad denominada "ID" que es la clave principal, almacenada como algo que implementa Equals. Nuestra convención obliga a todos nuestros modelos a tener esta propiedad. Si todos sus modelos tienen propiedades de identificación con nombres diferentes, considere modificar esta plantilla T4 para adaptarla a sus necesidades o, mejor aún, facilitarle la vida (no solo por el uso de esta T4) y cambiar sus modelos para usar la "ID". " nombre.

+0

La plantilla T4 es muy útil, pero para usar IEqualityComparer tengo que primero alinear ambas consultas en matrices (ya que linq2entities no es compatible con el comparador) antes de poder eliminar duplicados. Sin embargo, es algo que funciona por el momento, ¡gracias! – TJB

+0

@TJB: Ah. No tengo ninguna experiencia con LINQ-to-entities. Solo uso el 'IEqualityComparer ' para LINQ-to-objects en colecciones en memoria para una rápida identificación separada por ID. ¡Contento de estar en servicio! Regla de plantillas T4 :) –

2

como dijo James Dunne, usted desea utilizar una IEqualityComparer

una maqueta rápida sería algo como esto. Tendrá que reemplazar "ObjectType" con cualquier tipo que esté en su subQuery1 y subQuery2, por supuesto. tenga en cuenta que esto es no probado:

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

respuesta Piggyback, ¿eh? :) Estaba considerando regresar y actualizar mi respuesta para incluir un ejemplo, pero el suyo es suficiente. –

+0

jaja, en mi defensa, iba a escribir esto antes de ver tu respuesta :) –

1

Usted podría utilizar morelinq 's DistinctBy. Sospecho (pero no he verificado) que esto, así como las respuestas de IEqualityComparer y RemoveDuplicates, recuperarán los registros duplicados de SQL Server y luego eliminarán los duplicados en el cliente. Si alguien proporciona una solución del lado del servidor, recomendaría aceptar su respuesta.

+0

Correcto, estoy buscando algo que pueda lograr lo "distinto" en sql en lugar de en post-procesamiento – TJB

0

Nota: Estoy utilizando Linq2SQL (no Linq2Entities) - pero probablemente funcione para ambos.

Si no siempre quiere que se devuelva el XML para cada consulta, puede establecer que la columna XML sea 'Delay Load' en el archivo DBML.

Agregué una columna AddressBook XML a una tabla Customer que de repente rompió todas mis búsquedas. Una vez que cambié la columna a DelayLoad=true, todo volvió a funcionar (porque no incluía esa columna en DISTINCT).

Dependiendo de sus datos, esta solución (que crea una columna de carga diferida) puede acelerar o ralentizar significativamente su sistema, ¡así que tenga cuidado!

Cuestiones relacionadas