问题描述
我首先提到这个问题开始我的问题 - 我已经完成了其他 SO 问题,但最终遇到了一个我找不到答案的情况/问题。所以如果有的话,请指点我。
我的问题: 我有两个模型对象列表。 考虑一下,我有一个模型类 -
public class Contact
{
public string FirstName {get;set;}
public string LastName {get;set;}
public string MiddleName {get;set;}
public long ContactId {get;set;}
public long? DestKey {get;set;}
}
我有两个数据源,可能有一些联系人数据。想象一下,从 Db 来源 1,我有 2 个联系人,从 Db 来源 2,我有 10 个联系人。
我正在尝试从 Db1 列表中查找不在 Db2 列表中的唯一联系人。我确实使用自定义 Equality 比较器通过检查 FirstName 和 Lastname 字段来比较数据。我也覆盖了 GetHashCode()。
所以,我的自定义平等比较器如下所示:
public class MyContactComparer : IEqualityComparer<Contact>
{
public bool Equals(Contact src,Contact dest)
{
// compare LastName
if (!src.LastName.Equals(dest.LastName,StringComparison.CurrentCultureIgnoreCase)) return false;
// if LastName matches,compare FirstName
if (!src.FirstName.Equals(dest.FirstName,StringComparison.CurrentCultureIgnoreCase))
if (!(src.FirstName.Contains(dest.FirstName,StringComparison.CurrentCultureIgnoreCase) ||
dest.FirstName.Contains(src.FirstName,StringComparison.CurrentCultureIgnoreCase)))
return false;
// do other needful comparisons
//Todo: check for other comparison
return true;
}
public int GetHashCode(MmdContact obj)
{
return obj.FirstName.GetHashCode() ^ obj.LastName.GetHashCode();
}
}
我称之为,
var nonMatchingContactsList = db2srcModelleddb1Data
.Except(db2ContactsData.ToArray(),new MyContactComparer())
.ToList()
.Select(person => person.ContactId);
现在,我将 Db1 上的数据设置为
- {FirstName = "Studo Mid",LastName = "Tar",MiddleName = null,ContactId = 1}
- {FirstName = "Foo",LastName = "Bar",MiddleName = "H",ContactId = 2}
Db2 上的数据设置为,
- {FirstName = "Studo",MiddleName = "Mid",DestKey = 10001}
- {FirstName = "Studo",DestKey = 10002}
- {FirstName = "Studo",DestKey = 10003}
- {FirstName = "Studo",DestKey = 10004}
- {FirstName = "Studo",DestKey = 10005}
- {FirstName = "Studo",DestKey = 10006} ... 等等, 通过名称重复记录但具有唯一的 DestKey。假设它们是由我在下面解释的逻辑创建的,结果是重复的。无论数据质量如何,我都希望将 Db1 集中的 2 个联系人与 Db2 集中的 10 个联系人进行比较。
但是当我调试它时,Equals() 方法只是在 Db2 集的 10 个联系人之间进行迭代和检查,因为我可以看到“src”和“Dest”之间的 DestKey 值。在我看来,它在 Db2 集中进行比较,然后将 Db1 上的 2 个联系人识别为不存在。所以我的逻辑是创建它们,在此基础上,“Studo Mid Tar”记录被一次又一次地创建。
当我再次重新运行时,它不会检测到该联系人是否匹配,并且不会执行 except() 部分。我想说,Db1 上的第二个联系人(Foo Bar)是我希望看到的作为要创建的输出的东西。 GetHashCode() 仅针对 db2 集发生。
那么,出了什么问题,为什么会出现这种行为? 需要什么来针对适当的列表运行它,即 2 对 10 条记录
更新: 我的主要问题是为什么 Equals() 与它自己的列表进行比较?看看这个小提琴 - https://dotnetfiddle.net/upCgbb
我看到了所需的输出,但我不明白的是,为什么 Equals() 方法比较相同模型类型(在本例中为 DataB)的数据进行几次迭代,而不是比较 A 与 B?它确实将 1001 与 1002 进行比较,然后将 1001 与 1003 进行比较,然后再与实际的 A ContactId 1 进行比较。这就是我为什么要比较自己的列表的问题?
解决方法
如果您需要比较两个列表并且您有某种一对多关系(一对无也是一对多)或左连接,您应该使用 .GroupJoin()
。
所以如果你用这个替换你的工作方法:
public void DoMyWork()
{
// expecting 1 record from A which is ContactId = 2 {Foo Bar}
var nonMatchingContactsList = dataFromA
.GroupJoin(
dataFromB,a => $"{a.FirstName}|{a.LastName}",b => $"{b.FirstName} {b.MiddleName}|{b.LastName}",(a,matchingBs) => matchingBs.Any() ? null : a)
.Where(a => a != null)
.ToList();
Console.WriteLine($"Total contacts specific to DbA: {nonMatchingContactsList.Count()}\r\n{string.Join("\n",nonMatchingContactsList)}");
}
您无需使用任何自行编写的比较器即可获得所需的答案。 魔法发生在这些行中:
a => $"{a.FirstName}|{a.LastName}"
这将从您的类中创建所需的密钥。在我的心智模型中,这类似于 Dictionary<string,ModelA>
。
b => $"{b.FirstName} {b.MiddleName}|{b.LastName}"
这对您的第二个类执行相同的操作,并使用所需的属性生成类似的键以进行比较。根据您的数据,如果中间名为空或为空,您可能需要改进此方法以删除空格。
(a,matchingBs) => matchingBs.Any() ? null : a
这是组加入方法的结果选择器。第一个参数是一个 modelA 对象,第二个参数是所有匹配的 modelB 对象的列表。因为您想获取 ModelB 列表中不存在匹配对象的所有 ModelA 对象,所以我们检查 matchingBs.Any()
,如果有匹配项,则返回 null
,否则返回 a
。之后,您只需扔掉所有空值,即可从 ModelA 列表中获得所需的项目。
如果中间名为空,可能是避免空格的更好方法:
b => string.isNullOrEmpty(b.MiddleName)
? $"{b.FirstName}|{b.LastName}"
: $"{b.FirstName} {b.MiddleName}|{b.LastName}"
关于编写自己的比较器类的最后一个技巧。在实现方法 GetHashCode()
和 Equals()
时,需要遵守很多规则。要将它们全部匹配,您应该使用此蓝图作为起点,并采用应比较的属性。通过使用此蓝图,您可以避免很多问题:
public bool Equals(MyObject x,MyObject y)
{
if (ReferenceEquals(x,y))
return true;
if (ReferenceEquals(x,null))
return false;
if (ReferenceEquals(y,null))
return false;
if (x.MostDifferentialProperty != y.MostDifferentialProperty)
return false;
if (x.DifferentialProperty != y.DifferentialProperty)
return false;
// Other properties to compare...
return true;
}
public int GetHashCode(MyObject obj)
{
if (ReferenceEquals(obj,null))
return -1;
// Add all properties from Equals() method here.
return HashCode.Combine(obj.MostDifferentialProperty,obj.DifferentialProperty);
}