将静态参数转换为Hashtable并与lambda搜索一起使用

时间:2019-01-05 00:08:37

标签: c#

这个问题是not a duplicate。我正在寻找一种方法,可以为任意数量的键值参数和相关的where子句传递Hashtable,并且可以将哈希表动态集成到lambda search中。另一篇文章没有回答。

我有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的键值对(或其他具有同等或更好意义的集合)?

谢谢。

6 个答案:

答案 0 :(得分:2)

如果您将它分成几部分,这个问题会简单得多。

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

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

答案 1 :(得分:1)

通过稍微分离关注点,您可以看到composite设计模式将解决此问题。您的方法基本上是...

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);   

免责声明:我不在我的电脑上,所以如果这不能编译或包含任何自动更正,请给我一些放松!

答案 2 :(得分:0)

怎么样呢?

    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;
        }
    }

答案 3 :(得分:0)

类似的东西会有所帮助

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;

}

答案 4 :(得分:0)

您可以传递枚举的搜索项并在它们上循环。对于每个项目,只需在结果中附加一个对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;
    }
}

答案 5 :(得分:0)

这是你的食谱

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是给定数组的长度,因此该解决方案实际上可能很慢。但是必须对其进行测试。