LINQ选择Distinct而忽略XML字段

时间:2010-10-14 02:24:37

标签: c# xml linq linq-to-entities distinct

我有一个复杂的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 个答案:

答案 0 :(得分:3)

为包含XML类型的对象编写IEqualityComparer<T>实现,并将其传递给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的东西。我们的会议迫使我们所有的模型都拥有这个属性。如果您的模型都具有不同名称的ID属性,请考虑修改此T4模板以满足您的需求或更好,让自己更轻松(不仅仅是为了使用此T4)并更改模型以使用“ID” “名字。

答案 1 :(得分:2)

正如James Dunne所说,你想要使用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();
    }

}

答案 2 :(得分:1)

此扩展方法应该返回一个项目列表,其中只包含每组重复项中的第一项......

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中有两个具有相同userID的记录,则只返回第一个

答案 3 :(得分:1)

您可以使用morelinq的DistinctBy。我怀疑(但尚未验证),以及IEqualityComparer和RemoveDuplicates答案,将从SQL Server检索重复记录,然后删除客户端上的重复项。如果有人提供服务器端解决方案,我建议接受他们的回答。

答案 4 :(得分:0)

注意:我使用的是Linq2SQL(不是Linq2Entities) - 但可能适用于两者。

如果您不总是希望为每个查询返回XML,则可以在DBML文件中将XML列设置为“延迟加载”。

我在AddressBook表中添加了Customer XML列,这突然破坏了我的所有搜索。一旦我将列切换到DelayLoad=true,那么一切都再次起作用(因为它不包括DISTINCT中的那列)。

根据您的数据,此解决方案(制作延迟加载列)可能会显着加快或降低系统速度 - 所以要小心!