提问人:mrAtari 提问时间:12/13/2022 最后编辑:mrAtari 更新时间:12/29/2022 访问量:74
Linq 匹配可变数量的 AND 条件
Linq matching variable number of AND conditions
问:
我有一个数据库,其中包含一组规则,应查询这些规则以查找匹配的规则。
简化表包含 5 列,其中包含 4 列和模式字符串(我们称它们为 And1..And4) 和一个用于操作 (ToDo):
A B C D Todo1
A - - - Todo2
A C - - Todo3
C B - - Todo4
现在我有一个最多有 4 个条件的输入模式,这些条件应该是 AND 条件。
预期结果应为 s.th。喜欢:
(A) -> Todo2
(A,C) -> Todo3, Todo2 // Both rules match the input
(A,B,C) -> Todo3, Todo2
(B,C) -> Todo4 //order of the input should be irrelevant
(A,B,C,D) -> Todo1, Todo2
2022年12月29日更新:
这里有一些示例代码,它说明了旧方法(第一个代码片段不完整且不起作用),这个示例代码确实如此:
public List<CombinationRule> GetCombinationRules(string firstElement, List<string> andConditions)
{
using (var context = _dbContextProvider.GetDbContext())
{
var result = new List<CombinationRule>();
var query = context.CombinationTable
.Where(x => Match(firstElement, x.ColumnA))
result.AddRange(SelectItems(query, new List<string> { andConditions[0] }));
if (andConditions.Count > 1)
{
result.AddRange(SelectItems(query, new List<string> { andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[0] }));
}
if (andConditions.Count > 2)
{
result.AddRange(SelectItems(query, new List<string> { andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[1], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[2], andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[0], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[2], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[0], andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[1], andConditions[0] }));
}
if (andConditions.Count > 3)
{
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[1], andConditions[3] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[3], andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[2], andConditions[3] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[0], andConditions[3], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[0], andConditions[3] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[3], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[2], andConditions[3] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[1], andConditions[3], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[0], andConditions[3] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[3], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[1], andConditions[3] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[2], andConditions[3], andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[3], andConditions[0], andConditions[1] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[3], andConditions[1], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[3], andConditions[0], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[3], andConditions[2], andConditions[0] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[3], andConditions[1], andConditions[2] }));
result.AddRange(SelectItems(query, new List<string> { andConditions[3], andConditions[2], andConditions[1] }));
}
return result.Distinct().ToList();
}
}
该方法使用 2 个帮助程序:Match 和 SelectItems。Match 是一种特殊的字符串匹配,基于特殊的业务逻辑规则。SelectItems 为 1、2 或 3 个条件选择规则。SelectItems 的实现方式如下:
private List<CombinationRule> SelectItems(IQueryable<CombinationTable> query,
List<string> andOrderNumbers)
{
switch(andOrderNumbers.Count)
{
case 1:
query = query.Where(x => string.IsNullOrEmpty(x.And2OrderNumber) &&
string.IsNullOrEmpty(x.And3OrderNumber))
.Where(x => Match(andOrderNumbers[0], x.And1OrderNumber));
break;
case 2:
query = query.Where(x => string.IsNullOrEmpty(x.And3OrderNumber))
.Where(x => Match(andOrderNumbers[0], x.And1OrderNumber) &&
Match(andOrderNumbers[1], x.And2OrderNumber));
break;
case 3:
query = query.Where(x => Match(andOrderNumbers[0], x.And1OrderNumber) &&
Match(andOrderNumbers[1], x.And2OrderNumber) &&
Match(andOrderNumbers[2], x.And3OrderNumber));
break;
default:
throw new NotImplementedException();
}
var result = query.Select(x => new CombinationRule(x.PrimaryOrderNumber,
new List<string> { x.And1OrderNumber, x.And2OrderNumber, x.And3OrderNumber },
x.ToDo))
.Distinct()
.ToList();
return result;
}
该方法依赖于手动输入 4 个条件元素的所有可能变体。它可以归结为构建所有可能的变化。我在 CodeProject 上找到了一篇很棒的文章,它解释了变体、排列和组合(Variations、Combinations、Permutations)
{A B C} 的变体选择 2,预期结果:
{A B}, {A C}, {B A}, {B C}, {C A}, {C B}
在进入问题所在之后,如何摆脱丑陋的代码这个问题就可以轻松回答了。通过方法创建变体,然后将它们扔到 SelectItems 方法中是一件容易的事情。
答:
所以这里是函数,它可以构建选择 n 个元素的 k 个元素的 VARIATIONS:
static void GenerateVariationsNoRepetitions(int[] kArray, int[] nArray, int index, List<List<int>> result)
{
if (index >= kArray.Length)
{
result.Add(kArray.ToList());
return;
}
for (int i = index; i < nArray.Length; i++)
{
kArray[index] = nArray[i];
Swap(ref nArray[i], ref nArray[index]);
GenerateVariationsNoRepetitions(kArray, nArray, index + 1, result);
Swap(ref nArray[i], ref nArray[index]);
}
}
private static void Swap<T>(ref T v1, ref T v2)
{
T old = v1;
v1 = v2;
v2 = old;
}
考虑到这一点,GetCombinationRules 方法可以重写得更小、更短:
public List<CombinationRule> GetCombinationRules(string firstElement, List<string> andConditions)
{
using (var context = _dbContextProvider.GetDbContext())
{
var result = new List<CombinationRule>();
var query = context.CombinationTable.AsNoTracking()
.Where(x => Match(firstElement, x.ColumnA));
for (int i=0; i < 3; i++)
{
int[] kArr = new int[i+1];
int[] nArr = Enumerable.Range(0, andConditions.Count).ToArray();
var combinations = new List<List<int>>();
GenerateVariationsNoRepetitions(kArr, nArr, 0, combinations);
foreach (var combi in combinations)
{
var list = new List<string>();
foreach (int c in combi)
{
list.Add(andConditions[c]);
}
result.AddRange(SelectItems(query, list));
}
}
return result.Distinct().ToList();
}
}
我敢肯定,这不是最短和最优雅的实现,但它具有良好的可读性并且易于扩展。
顺便说一句,即使在第一个实现中,我也进行了小的重新排列(包括对数据格式的轻微重新定义),以提高性能。您还记得,我需要 4 个元素中的变体,但实现包含一个主要元素和 3 个元素中的变体。这大大提高了性能,因为对主要元素(必须始终为类型 A)的第一个查询将初始结果从大约 10k 个元素减少到 10 个以下,并且还减少了其余变体的数量。这样,性能大致可以接受。
评论