将静态参数转换为哈希表,并与lambda搜索一起使用



这个问题不是重复的。我正在寻找一种方法,在这种方法中,我可以为任何数量的键值参数和相关的where子句传递Hashtable,并且可以将该哈希表动态集成到lambda搜索中。另一篇帖子没有回答这个问题。

我有3种过载方法:

public static bool DoesRecordExist(string keyColumn, string keyValue, DataTable dt) {
if (dt != null && dt.Rows.Count > 0) {
bool exists = dt.AsEnumerable().Where(r => string.Equals(SafeTrim(r[keyColumn]), keyValue, StringComparison.CurrentCultureIgnoreCase)).Any();
return exists;
} else {
return false;
}
}
public static bool DoesRecordExist(string keyColumn1, string keyColumn2, string keyValue1, string keyValue2, DataTable dt) {
if (dt != null && dt.Rows.Count > 0) {
bool exists = dt.AsEnumerable().Where(r => string.Equals(SafeTrim(r[keyColumn1]), keyValue1, StringComparison.CurrentCultureIgnoreCase) && string.Equals(SafeTrim(r[keyColumn2]), keyValue2, StringComparison.CurrentCultureIgnoreCase)).Any();
return exists;
} else {
return false;
}
}
public static bool DoesRecordExist(string keyColumn1, string keyColumn2, string keyColumn3, string keyValue1, string keyValue2, string keyValue3, DataTable dt) {
if (dt != null && dt.Rows.Count > 0) {
bool exists = dt.AsEnumerable().Where(r => string.Equals(SafeTrim(r[keyColumn1]), keyValue1, StringComparison.CurrentCultureIgnoreCase) && string.Equals(SafeTrim(r[keyColumn2]), keyValue2, StringComparison.CurrentCultureIgnoreCase) && string.Equals(SafeTrim(r[keyColumn3]), keyValue3, StringComparison.CurrentCultureIgnoreCase)).Any();
return exists;
} else {
return false;
}
}

这些都按预期工作,正如您所看到的,所有3个都是相同的,只是参数和相应的where子句参数的数量在增加。现在,我需要添加另一个重载,其中包含5个键/值对。很明显,这越来越愚蠢了。

如何将所有这些重载转换为一个函数,在该函数中,我只需传入键值对的Hashtable(或其他意义相同或更好的集合)?

如果你把它分解成几个部分,这个问题会简单得多。

首先编写一个函数来检查单个记录是否匹配:

private static bool IsMatch(DataRow row, Dictionary<string,object> filters)
{
return filters.All( pair => row[SafeTrim(pair.Key)].Equals(pair.Value) );
}

然后将其作为DoesRecordExist逻辑中的代理进行传递:

public static bool DoesRecordExist(Dictionary<string,object> filters, DataTable dt)
{
if (dt == null || dt.Rows.Count == 0) return false;
return dt.AsEnumerable().Any(r => IsMatch(r, filters));
}

以上就是你所需要的。

下面是一个使用示例:

public static DataTable CreateTestData()
{
var data = new []
{
new { ID = 1, Name = "John",  DOB = new DateTime(2018,1,1) },
new { ID = 2, Name = "Paul",  DOB = new DateTime(2018,1,2) },
new { ID = 3, Name = "Ringo", DOB = new DateTime(2018,1,3) },
new { ID = 4, Name = "George",DOB = new DateTime(2018,1,4) }
};
var table = new DataTable();
table.Columns.Add("ID", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("DOB", typeof(DateTime));
foreach (var d in data)
{
var row = table.NewRow();
row[0] = d.ID;
row[1] = d.Name;
row[2] = d.DOB;
table.Rows.Add(row);
}
return table;
}
public static void Main()
{
var table = CreateTestData();
var filter1 = new Dictionary<string,object> { {"ID", 1 } };
Console.WriteLine("Filter1 exists? {0}", DoesRecordExist(filter1, table));  //Should be true
var filter2 = new Dictionary<string,object> { { "ID", 1 }, {"Name", "John" } };
Console.WriteLine("Filter2 exists? {0}", DoesRecordExist(filter2, table));  //Should be true
var filter3 = new Dictionary<string,object> { { "ID", 1 }, {"Name", "John" }, {"DOB", new DateTime(2018,1,31)} };
Console.WriteLine("Filter3 exists? {0}", DoesRecordExist(filter3, table));  //Should be false
var filter4 = new Dictionary<string,object> { { "ID", 1 }, {"Name", "Paul" }, {"DOB", new DateTime(2018,1,2)} };
Console.WriteLine("Filter4 exists? {0}", DoesRecordExist(filter4, table));  //Should be false
}

输出:

Filter1 exists? True
Filter2 exists? True
Filter3 exists? False
Filter4 exists? False

DotNetFiddle

通过将关注点稍微分开,您可以看到复合设计模式将解决这个问题。你的方法基本上是。。。

public static bool DoesRecordExist(IPredicate<DataRow> condition, DataTable dt) {
if (dt != null && dt.Rows.Count > 0) {
bool exists = dt.AsEnumerable().Any(r => predicate.Condition(r));
return exists;
} else {
return false;
}
}

其中IPredicate<T>是一个具有一个方法的接口:bool Condition(T t)

现在,您可以为DataRow定义该接口的实现,以表示与单个列匹配的列/值:

public class DataRowPredicate
: IPredicate<DataRow>
{
private readonly string _keyColumn;
private readonly string _keyValue;
public DataRowPredicate(string keyColumn, string keyValue)
{
_keyColumn=keyColumn;
_keyValue=keyValue;
}
public bool Condition(DataRow r)
{
return string.Equals(SafeTrim(r[_keyColumn]), _keyValue, StringComparison.CurrentCultureIgnoreCase); 
}
}

现在,您所需要的只是将这些条件堆叠在一起的能力——您需要复合模式:一个实现与其所包含内容相同接口的集合。类似。。。

public class PredicateCollection<T> : List<IPredicate<T>>, IPredicate<T>
{
public bool Condition(T t)
{
return this.All(x => x.Condition(t));
}
}

因此,现在您可以通过创建一个新集合并添加所需数量的谓词来堆叠任意数量的谓词。然后将该集合作为参数传递给DoesRecordExist。如果您需要大量重用同一谓词集合,只需保留该集合即可。类似。。。

var conditionA=new PredicateCollection<DataRow>
{
new DataRowPredicate(keyColumn1, keyValue1),
new DataRowPredicate(keyColumn2, keyValue2),
new DataRowPredicate(keyColumn3, keyValue3),
//... etc, as required
};

这样使用。。。

bool result = DoesRecordExist(conditionA, dt);   

免责声明:我不在电脑上,所以如果这篇文章没有编译,或者包含任何自动更正,请放松我!

您可以传入一个可枚举的搜索项并在其上循环。对于每个项目,只需在您的结果上添加一个对Where的额外调用。

我创建了一些示例代码,希望能说明我的意思。我还没有完全测试过,但希望如果它不能正常工作,它会给你正确的想法。它的工作原理基本上是.Where(A&B).Where(A).Where(B)基本相同

public static bool DoesRecordExist(DataTable dt, Dictionary<string,string> searchDetails) 
{
if (dt != null && dt.Rows.Count > 0) {
var items = dt.AsEnumerable();
foreach(var searchItem in searchDetails)
{
items = items.Where(r=>string.Equals(SafeTrim(r[searchItem.Key]), searchItem.Value, StringComparison.CurrentCultureIgnoreCase)
}
return items.Any();
} 
else 
{
return false;
}
}

这样的东西会帮助

public static bool DoesRecordExist(DataTable dt, params string[] parameters)
{
bool exists = false;
if (parameters.Length % 2 != 0 )
throw new ArgumentException();
if (dt == null || dt.Rows.Count == 0)
return false;
var query = dt.AsEnumerable()
for (int i = 0; i < parameters.Length; i += 2)
query = query.Where(r => string.Equals(SafeTrim(r[parameters[i]]),
parameters[i + 1], StringComparison.CurrentCultureIgnoreCase)) ; 
return exists;
}

这是您的配方

public static bool DoesRecordExist(string[] keyColumns, string[] keyValues, DataTable dt)
{
if (dt != null && dt.Rows.Count > 0)
{       
bool exists = dt.AsEnumerable()
.Where(r => keyColumns.Zip(keyValues, 
(col, val) => string.Equals(SafeTrim(r[col]), val, StringComparison.CurrentCultureIgnoreCase))
.All()).Any();
return exists;
}
else return false;
}

  • Zip简单地将两个序列配对在一起
  • All就像在所有布尔上执行&&一样

旁注:

  • 我认为您不需要这个dt.Rows.Count > 0检查,因为Any在空集合上返回false。

  • 如果可能的话,使用IEnumerable<string>而不是string[]

  • 这个解决方案实际上可能很慢,因为嵌套循环,O(N*Rows)搜索速度,其中N是给定数组的长度。但必须对其进行测试。

这样的东西怎么样:

public static bool DoesRecordExist(DataTable dt, List<KeyValuePair<string, string>> keyValuePairs)
{
if (dt != null && dt.Rows.Count > 0)
{
bool exists = dt.AsEnumerable().Where(r =>
{
foreach (var kvp in keyValuePairs)
{
if (string.Equals(SafeTrim(r[kvp.Key]), kvp.Value, StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
}
return false;
}).Any();
return exists;
}
else
{
return false;
}
}

最新更新