代码之家  ›  专栏  ›  技术社区  ›  TJB

LINQ Select Distinct,同时忽略XML字段

  •  7
  • TJB  · 技术社区  · 14 年前

    我有一个复杂的LINQ查询(使用linq2ef),它可以返回重复的结果,因此我使用 .Distinct() 方法避免重复。骨架如下:

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

    每个子查询将一个公共用户表与另一个表联接起来并执行“where”查询,结果稍后将合并到 .Union(...)

    无法将xml数据类型选择为distinct,因为它不具有可比性

    在本例中,我不关心XML列在结果中是否相等。实际上我只需要确定主键 UserId 结果截然不同。

    有办法用吗 Distinct() 但请忽略XML列,或者用一种更简单的方法来确保我从结果中删除相同的记录 用户ID 以一种有效的方式?理想情况下,这不会从数据库中检索重复记录,也不需要进行后处理来删除重复记录。

    更新: 我发现,如果我提前将查询序列化到数组中,那么就不需要任何类型的比较器,因为Linq2Objects没有XML不同选择问题。例如,我可以这样做:

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

    用户ID s、 谢谢你到目前为止的所有答案。

    5 回复  |  直到 14 年前
        1
  •  3
  •   James Dunne    14 年前

    写一篇 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的东西。我们的惯例强制我们所有的模型都有这个属性。如果您的模型都具有不同的命名ID属性,请考虑修改此T4模板以满足您的需要或更好,使您的生活更轻松(不仅仅是为了使用此T4),并将模型更改为使用“ID”名称。

        2
  •  2
  •   jb.    14 年前

    正如jamesdunne所说,你应该使用IEqualityComparer

    一个快速的模型应该是这样的。当然,您需要用subQuery1和subQuery2中的任何类型替换“ObjectType”。请注意,这是未经测试的:

    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();
        }
    
    }
    
        3
  •  1
  •   TerrorAustralis    14 年前

    这个扩展方法应该返回一个项目列表,其中每个重复项集中只有第一个项目。。。

    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)

        4
  •  1
  •   TrueWill    14 年前

    你可以利用 morelinq 很明显。我怀疑(但尚未验证)这一点以及IEqualityComparer和removedupplicates答案将从SQL Server检索重复记录,然后在客户端上删除重复记录。如果有人提供服务器端解决方案,我建议接受他们的答案。

        5
  •  0
  •   Simon_Weaver    14 年前

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

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

    我加了一个 AddressBook XML列到 Customer DelayLoad=true 然后一切又恢复了(因为它没有在DISTINCT中包含该列)。

    根据您的数据,这个解决方案(生成一个延迟加载列)可以显著地加快或减慢您的系统,所以要小心!