LINQ Выберите Distinct, игнорируя поле XML - PullRequest
7 голосов
/ 14 октября 2010

У меня сложный запрос LINQ (с использованием LINQ 2 EF), который может возвращать дублированные результаты, и поэтому я использую метод .Distinct(), чтобы избежать дублирования. Вот скелет:

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

Каждый из подзапросов объединяет общую пользовательскую таблицу с другой таблицей и выполняет запрос 'where', результаты затем объединяются в .Union(...). Это работало нормально, пока таблица не была изменена, чтобы включить столбец XML, что приводит к этому исключению:

тип данных xml нельзя выбрать как отдельный, потому что он несопоставим

В этом случае мне все равно, является ли столбец XML эквивалентным по результатам. на самом деле мне нужно только быть уверенным, что первичный ключ UserId отличается в результатах.

Есть ли способ использовать Distinct(), но игнорировать столбец XML или более простой способ убедиться, что я удаляю записи из результата с тем же UserId эффективным способом? В идеале это не будет извлекать дубликаты записей из базы данных и не потребует последующей обработки для удаления дубликатов.

Обновление: Я обнаружил, что если я сериализирую свои запросы к массивам раньше времени, то нет необходимости в каком-либо виде компаратора, поскольку у Linq2Objects нет отдельной проблемы выбора XML. Например, я могу сделать это:

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

Итак, что я действительно ищу, так это способ избежать сериализации промежуточных запросов и прямого вызова Linq2Entities, который не будет извлекать записи с дублирующимися UserId с. Спасибо за ответы на все вопросы.

Ответы [ 5 ]

3 голосов
/ 14 октября 2010

Напишите реализацию IEqualityComparer<T> для объекта, содержащего ваш тип XML, и передайте его Distinct.В методе Equals вы можете реализовать семантику равенства по своему усмотрению.

Это удобный шаблон генерации кода T4, который я написал для генерации IEqualityComparer<T> реализаций для моделей предметной области моей команды:

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

Предполагается, что у каждого из ваших классов моделей есть свойство с именем "ID", которое является первичным ключом и хранится как нечто, реализующее Equals.Наше соглашение заставляет все наши модели иметь это свойство.Если все ваши модели имеют свойства идентификаторов с разными именами, рассмотрите возможность изменения этого шаблона T4 в соответствии с вашими потребностями или еще лучше, упростите себе жизнь (не только ради использования этого T4) и измените свои модели на использование идентификатора"имя.

2 голосов
/ 14 октября 2010

как сказал Джеймс Данн, вы бы хотели использовать IEqualityComparer

быстрый макет был бы примерно таким. Вам нужно будет заменить «ObjectType» любым типом, который есть в вашем subQuery1 и subQuery2. обратите внимание, что это не проверено:

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

}
1 голос
/ 14 октября 2010

Вы можете использовать morelinq 's DistinctBy.Я подозреваю (но не проверял), что это, так же как и ответы IEqualityComparer и RemoveDuplicates, будет извлекать дубликаты записей из SQL Server, а затем удалит дубликаты на клиенте.Если кто-то предоставляет решение на стороне сервера, я бы рекомендовал принять его ответ.

1 голос
/ 14 октября 2010

Этот метод расширения должен возвращать список элементов, содержащих только первый элемент из каждого набора дубликатов ...

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

будет использоваться в списке, подобном этому list.RemoveDuplicates(x => x.UserID). Если бы в List было две записи с одним и тем же идентификатором пользователя, он вернул бы только первую

0 голосов
/ 20 апреля 2011

Примечание: я использую Linq2SQL (не Linq2Entities) - но, вероятно, работает для обоих.

Если вы не всегда хотите, чтобы XML возвращался для каждого запроса, вы можете установить для столбца XML значение «Задержка загрузки».'в файле DBML.

Я добавил AddressBook столбец XML в таблицу Customer, которая внезапно прервала все мои поиски.Как только я переключил столбец на DelayLoad=true, все снова заработало (потому что он не включал этот столбец в DISTINCT).

В зависимости от ваших данных это решение (создание столбца с отложенной загрузкой) может либо значительно ускориться, либозамедляй свою систему - будь осторожен!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...