Linq 通过复合键过滤列表(包含和不包含)
Linq filtering a list by a composite key (contains and not contains)
我需要了解如何通过条件为“包含”和“不包含”的复合键过滤列表。
我有一个列表
List<Entity>();
其中实体是 class
public class Entity
{
int id;
string docNum;
int docVersion;
}
有一个类似的第二个列表。我需要获取列表中符合以下条件的那些元素:
"获取第一个列表的元素,与第二个列表的文档编号相同,相同文档编号的文档版本不同"。
我尝试将 Contains()
与 Select()
结合使用,例如:
firstList
.Where(x => secondList.Select(y => y.docNum).Contains(x.docNum))
.Where(x => !secondList.Select(y => y.docVersion).Contains(x.docVersion))
但恐怕没有考虑到我也需要比较文件编号。
在我看来,这应该通过 GroupBy()
或 ToDictionary()
来解决,但我找不到解决方案。
如果可能的话,请展示一个关于标准 linq 元素的解决方案示例,因为我使用 NHibernate
如果我明白你想要什么,应该这样做:
var results = list1
.Where(l1 => list2.Select(l2 => l2.DocNum).Contains(l1.DocNum))
.GroupJoin(list2, l1 => l1.DocNum, l2 => l2.DocNum, (e, c) => new KeyValuePair<string, IEnumerable<Entity>>(e.DocNum, c.Prepend(e)))
.ToArray();
或
var results = list1
.GroupJoin(list2, l1 => l1.DocNum, l2 => l2.DocNum, (e, c) => new KeyValuePair<string, IEnumerable<Entity>>(e.DocNum, c.Prepend(e)))
.Where(x => x.Value.Count() > 1)
.ToArray();
然后您可以进一步 select 从结果组中获取您需要的实体,也许是获取版本最大的实体,或者所有实体...
不幸的是我从未使用过 NHibernate 所以我不确定是否支持它,也许你需要先 select 来自数据库,枚举它们,然后在软件端执行此操作...
根据您的要求,使用 LINQ 查询语法可能更容易做到这一点,您所做的方式和我发布的解决方案仍然是 LINQ,只是实现方式不同。
List<Entity> firstList = new List<Entity>
{
new Entity
{
id = 1,
docNum = "two",
docVersion = 3
},
new Entity
{
id = 2,
docNum = "four",
docVersion = 4
},
new Entity
{
id = 3,
docNum = "ten",
docVersion = 6
}
};
List<Entity> ListTwo = new List<Entity>
{
new Entity
{
id = 1,
docNum = "two",
docVersion = 5
},
new Entity
{
id = 2,
docNum = "eight",
docVersion = 7
},
new Entity
{
id = 3,
docNum = "ten",
docVersion = 5
}
};
var resultList = from list in firstList
from list2 in ListTwo
where list.docNum == list2.docNum
where list.docVersion != list2.docVersion
select list;
首先,我们创建了两个列表,我们的结果列表将从中创建。这些是 firstList
和 ListTwo
然后声明一个 var
并将我们的 LINQ 查询结果赋值给它
from list in firstList from list2 in ListTwo
用于 select 我们的两个数据存储,我们给一个名称来引用这些数据存储中的单个元素 list
和 list2
where list.docNum == list2.docNum where list.docVersion != list2.docVersion
用来满足我们的条件。第一个 where 子句检查 list
和 list2
中的 docNum
是否匹配。然后,第二个 where 子句检查以确保 list1
和 `list2``` 中的 docVersion
不相等。
select list
用于 select 匹配我们 where 子句的项目。编辑 如果您需要从两个来源匹配的实体 将 select list
更改为 select new {list,list2}
如果您要调试此代码,您会看到resultList
有两个值(见图)。
您可以使用 Any()
而不是 Select().Contains()
进行更复杂的比较。例如,如果组合键存在于 secondList
中,则过滤如下:
.Where(x => !secondList.Any(y => y.docNum == x.docNum && y.docVersion == x.docVersion)
在这种情况下,这比 Select().Contains()
稍微更有效,因为没有中间对象构造。在中间对象更复杂的某些情况下,节省会增加。
我建议您也使用 Any()
作为第一个条件,例如:
firstList
.Where(x => x.Any(y => y.docNum == x.docNum))
.Where(x => !secondList.Any(y => y.docNum == x.docNum && y.docVersion == x.docVersion)
第一个 Where()
将过滤掉列表中没有匹配 docNum
的任何项目,第二个将过滤剩余的项目以删除具有完全复合匹配的项目第二个列表。
当然这是非常低效的,因为你必须扫描 secondList
两次 - 部分扫描,但仍然是两次扫描。最好查找以文档编号为键的版本号,并使用它来进行更有效的过滤:
var lu = secondList.ToLookup(y => y.docNum, y => y.docVersion);
firstList.Where
(
x =>
{
var l = lu[x.docNum];
return l.Any() && !l.Any(v => v == x.docVersion);
}
)
由于 ILookup<>
得到了相当好的优化,因此大大减少了执行过滤器所需的实际时间。使用一些非常简单的(主要是)随机列表生成和相当高的冲突,我测试了对 1,000,000 个项目的列表过滤 10,000 个项目,看看有什么不同。第一个选项 49 秒,查找 1.8 秒。
也就是说,这实际上只适用于 IEnumerable<>
。如果您在 IQueryable<>
上操作,则坚持使用 Any()
和良好的指数。
我需要了解如何通过条件为“包含”和“不包含”的复合键过滤列表。
我有一个列表
List<Entity>();
其中实体是 class
public class Entity
{
int id;
string docNum;
int docVersion;
}
有一个类似的第二个列表。我需要获取列表中符合以下条件的那些元素: "获取第一个列表的元素,与第二个列表的文档编号相同,相同文档编号的文档版本不同"。
我尝试将 Contains()
与 Select()
结合使用,例如:
firstList
.Where(x => secondList.Select(y => y.docNum).Contains(x.docNum))
.Where(x => !secondList.Select(y => y.docVersion).Contains(x.docVersion))
但恐怕没有考虑到我也需要比较文件编号。
在我看来,这应该通过 GroupBy()
或 ToDictionary()
来解决,但我找不到解决方案。
如果可能的话,请展示一个关于标准 linq 元素的解决方案示例,因为我使用 NHibernate
如果我明白你想要什么,应该这样做:
var results = list1
.Where(l1 => list2.Select(l2 => l2.DocNum).Contains(l1.DocNum))
.GroupJoin(list2, l1 => l1.DocNum, l2 => l2.DocNum, (e, c) => new KeyValuePair<string, IEnumerable<Entity>>(e.DocNum, c.Prepend(e)))
.ToArray();
或
var results = list1
.GroupJoin(list2, l1 => l1.DocNum, l2 => l2.DocNum, (e, c) => new KeyValuePair<string, IEnumerable<Entity>>(e.DocNum, c.Prepend(e)))
.Where(x => x.Value.Count() > 1)
.ToArray();
然后您可以进一步 select 从结果组中获取您需要的实体,也许是获取版本最大的实体,或者所有实体...
不幸的是我从未使用过 NHibernate 所以我不确定是否支持它,也许你需要先 select 来自数据库,枚举它们,然后在软件端执行此操作...
根据您的要求,使用 LINQ 查询语法可能更容易做到这一点,您所做的方式和我发布的解决方案仍然是 LINQ,只是实现方式不同。
List<Entity> firstList = new List<Entity>
{
new Entity
{
id = 1,
docNum = "two",
docVersion = 3
},
new Entity
{
id = 2,
docNum = "four",
docVersion = 4
},
new Entity
{
id = 3,
docNum = "ten",
docVersion = 6
}
};
List<Entity> ListTwo = new List<Entity>
{
new Entity
{
id = 1,
docNum = "two",
docVersion = 5
},
new Entity
{
id = 2,
docNum = "eight",
docVersion = 7
},
new Entity
{
id = 3,
docNum = "ten",
docVersion = 5
}
};
var resultList = from list in firstList
from list2 in ListTwo
where list.docNum == list2.docNum
where list.docVersion != list2.docVersion
select list;
首先,我们创建了两个列表,我们的结果列表将从中创建。这些是
firstList
和ListTwo
然后声明一个
var
并将我们的 LINQ 查询结果赋值给它from list in firstList from list2 in ListTwo
用于 select 我们的两个数据存储,我们给一个名称来引用这些数据存储中的单个元素list
和list2
where list.docNum == list2.docNum where list.docVersion != list2.docVersion
用来满足我们的条件。第一个 where 子句检查list
和list2
中的docNum
是否匹配。然后,第二个 where 子句检查以确保list1
和 `list2``` 中的docVersion
不相等。select list
用于 select 匹配我们 where 子句的项目。编辑 如果您需要从两个来源匹配的实体 将select list
更改为select new {list,list2}
如果您要调试此代码,您会看到resultList
有两个值(见图)。
您可以使用 Any()
而不是 Select().Contains()
进行更复杂的比较。例如,如果组合键存在于 secondList
中,则过滤如下:
.Where(x => !secondList.Any(y => y.docNum == x.docNum && y.docVersion == x.docVersion)
在这种情况下,这比 Select().Contains()
稍微更有效,因为没有中间对象构造。在中间对象更复杂的某些情况下,节省会增加。
我建议您也使用 Any()
作为第一个条件,例如:
firstList
.Where(x => x.Any(y => y.docNum == x.docNum))
.Where(x => !secondList.Any(y => y.docNum == x.docNum && y.docVersion == x.docVersion)
第一个 Where()
将过滤掉列表中没有匹配 docNum
的任何项目,第二个将过滤剩余的项目以删除具有完全复合匹配的项目第二个列表。
当然这是非常低效的,因为你必须扫描 secondList
两次 - 部分扫描,但仍然是两次扫描。最好查找以文档编号为键的版本号,并使用它来进行更有效的过滤:
var lu = secondList.ToLookup(y => y.docNum, y => y.docVersion);
firstList.Where
(
x =>
{
var l = lu[x.docNum];
return l.Any() && !l.Any(v => v == x.docVersion);
}
)
由于 ILookup<>
得到了相当好的优化,因此大大减少了执行过滤器所需的实际时间。使用一些非常简单的(主要是)随机列表生成和相当高的冲突,我测试了对 1,000,000 个项目的列表过滤 10,000 个项目,看看有什么不同。第一个选项 49 秒,查找 1.8 秒。
也就是说,这实际上只适用于 IEnumerable<>
。如果您在 IQueryable<>
上操作,则坚持使用 Any()
和良好的指数。