2013-05-13 72 views
9

我正在开发一个项目,我的任务是添加一个高级搜索和筛选选项,允许用户通过指定尽可能多的条件从一系列Windows事件列表中查询所需结果如他们想要的。嵌套开关语句:建筑设计问题

的想法是每Windows事件日志具有几个特性,例如LogNameSourceCreatedDateMessageNumber,等等(FieldItem枚举的一部分)。总共有四个 possbile数据类型:StringDateTimeIntegral (Int/Long)EventEntryType。这四种数据类型中的每一种都有自己的选择器操作数集合(SelectorOperator enum的一部分)。这里是一个图片给你的整体结构看起来像一个更好的主意:

我的初步实施这个想法是这样的:

public static class SearchProvider 
{ 
    public static List<EventLogItem> SearchInLogs(List<EventLogItem> currentLogs, SearchQuery query) 
    { 
     switch (query.JoinType) 
     { 
      case ConditionJoinType.All: 
       return SearchAll(currentLogs, query); 
      case ConditionJoinType.Any: 
       return SearchAny(currentLogs, query); 
      default: 
       return null; 
     } 
    } 

    private static List<EventLogItem> SearchAll(List<EventLogItem> currentLogs, SearchQuery query) 
    { 
     foreach (SearchCondition condition in query.Conditions) 
     { 
      switch (condition.FieldName) 
      { 
       case FieldItem.Category: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Contains: 
          currentLogs = currentLogs.Where(item => item.Category.ToLower().Contains(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.EndsWith: 
          currentLogs = currentLogs.Where(item => item.Category.ToLower().EndsWith(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.Is: 
          currentLogs = currentLogs.Where(item => string.Equals(item.Category, condition.FieldValue as string, StringComparison.OrdinalIgnoreCase)).ToList(); 
          break; 
         case SelectorOperator.StartsWith: 
          currentLogs = currentLogs.Where(item => item.Category.ToLower().StartsWith(condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
       case FieldItem.InstanceID: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Equals: 
          currentLogs = currentLogs.Where(item => item.InstanceID == long.Parse(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.IsGreaterThan: 
          currentLogs = currentLogs.Where(item => item.InstanceID > long.Parse(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.IsLessThan: 
          currentLogs = currentLogs.Where(item => item.InstanceID < long.Parse(condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
       case FieldItem.LogName: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Contains: 
          currentLogs = currentLogs.Where(item => item.LogName.ToLower().Contains(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.EndsWith: 
          currentLogs = currentLogs.Where(item => item.LogName.ToLower().EndsWith(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.Is: 
          currentLogs = currentLogs.Where(item => string.Equals(item.LogName, condition.FieldValue as string, StringComparison.OrdinalIgnoreCase)).ToList(); 
          break; 
         case SelectorOperator.StartsWith: 
          currentLogs = currentLogs.Where(item => item.LogName.ToLower().StartsWith(condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
       case FieldItem.Message: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Contains: 
          currentLogs = currentLogs.Where(item => item.Message.ToLower().Contains(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.EndsWith: 
          currentLogs = currentLogs.Where(item => item.Message.ToLower().EndsWith(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.Is: 
          currentLogs = currentLogs.Where(item => string.Equals(item.Message, condition.FieldValue as string, StringComparison.OrdinalIgnoreCase)).ToList(); 
          break; 
         case SelectorOperator.StartsWith: 
          currentLogs = currentLogs.Where(item => item.Message.ToLower().StartsWith(condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
       case FieldItem.Number: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Equals: 
          currentLogs = currentLogs.Where(item => item.Number == int.Parse(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.IsGreaterThan: 
          currentLogs = currentLogs.Where(item => item.Number > int.Parse(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.IsLessThan: 
          currentLogs = currentLogs.Where(item => item.Number < int.Parse(condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
       case FieldItem.Source: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Contains: 
          currentLogs = currentLogs.Where(item => item.Source.ToLower().Contains(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.EndsWith: 
          currentLogs = currentLogs.Where(item => item.Source.ToLower().EndsWith(condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.Is: 
          currentLogs = currentLogs.Where(item => string.Equals(item.Source, condition.FieldValue as string, StringComparison.OrdinalIgnoreCase)).ToList(); 
          break; 
         case SelectorOperator.StartsWith: 
          currentLogs = currentLogs.Where(item => item.Source.ToLower().StartsWith(condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
       case FieldItem.Type: 
        switch (condition.SelectorOperator) 
        { 
         case SelectorOperator.Is: 
          currentLogs = currentLogs.Where(item => item.Type == (EventLogEntryType)Enum.Parse(typeof(EventLogEntryType), condition.FieldValue as string)).ToList(); 
          break; 
         case SelectorOperator.IsNot: 
          currentLogs = currentLogs.Where(item => item.Type != (EventLogEntryType)Enum.Parse(typeof(EventLogEntryType), condition.FieldValue as string)).ToList(); 
          break; 
        } 
        break; 
      } 
     } 

     return currentLogs; 
    } 

示例查询可能是这样的:

条件选择:

All of the conditions are met 

条件

LogName Is "Application" 
Message Contains "error" 
Type IsNot "Information" 
InstanceID IsLessThan 1934 

正如你所看到的,SearchAll()方法是很长,不是很维护,由于嵌套switch语句。但是,代码起作用,我觉得这不是实现这种设计的最优雅的方式。有没有更好的方法来解决这个问题?也许通过找出一种方法来降低switch层级的复杂性,或者通过使代码更通用?任何帮助/建议表示赞赏。

+0

IMO,看起来像多派遣(访客模式)的候选人。我会给人比我聪明的答案:) – 2013-05-13 21:25:28

+1

什么是您的数据访问策略?如果您使用的是LINQ to SQL或LINQ to Entities,那么通过使用'IQueryable ''接口,这可以变得非常简单(也很优雅)。 – Yuck 2013-05-13 21:30:41

+0

@Yuck事件日志由'System.Diagnostics.EventLog.GetEventLogs()'返回,并从这些列表中创建'EventLogItem'(自定义类型)列表,并最终绑定到'ListView'控件。 'IQueryable'就是我对它的一种预感,但不知道如何实现/使用它。 – PoweredByOrange 2013-05-13 21:37:11

回答

1

我认为你确实需要两个switch语句,但它们不需要嵌套。您可以将操作分离出来,以便在任何类型的对象上一般地工作,然后在运行时传入要搜索的对象。

public static class SearchProvider 
{ 
    static Func<object, bool> GetSearchMethod(SelectorOperator selectorOperator, string conditionFieldValue) 
    { 
     switch (selectorOperator) 
     { 
      //strings 
      case SelectorOperator.Contains: 
       return new Func<object, bool>(s => s.ToString().ToLower().Contains(conditionFieldValue)); 
      case SelectorOperator.StartsWith: 
       return new Func<object, bool>(s => s.ToString().ToLower().StartsWith(conditionFieldValue)); 
      case SelectorOperator.EndsWith: 
       return new Func<object, bool>(s => s.ToString().ToLower().EndsWith(conditionFieldValue)); 
      case SelectorOperator.Is: 
       return new Func<object, bool>(s => string.Equals(s.ToString(), conditionFieldValue, StringComparison.OrdinalIgnoreCase)); 

      //numbers 
      case SelectorOperator.Equals: 
       return new Func<object, bool>(n => (long)n == long.Parse(conditionFieldValue)); 
      case SelectorOperator.IsGreaterThan: 
       return new Func<object, bool>(n => (long)n > long.Parse(conditionFieldValue)); 
      case SelectorOperator.IsLessThan: 
       return new Func<object, bool>(n => (long)n < long.Parse(conditionFieldValue)); 

      //type 
      case SelectorOperator.TypeIs: 
       return new Func<object, bool>(t => (EventLogEntryType)t == (EventLogEntryType)Enum.Parse(typeof(EventLogEntryType), conditionFieldValue)); 
      case SelectorOperator.TypeIsNot: 
       return new Func<object, bool>(t => (EventLogEntryType)t != (EventLogEntryType)Enum.Parse(typeof(EventLogEntryType), conditionFieldValue)); 

      default: 
       throw new Exception("Unknown selector operator"); 
     } 
    } 

    private static List<EventLogItem> SearchAll(List<EventLogItem> currentLogs, SearchQuery query) 
    { 
     foreach (SearchCondition condition in query.Conditions) 
     { 
      var search = GetSearchMethod(condition.SelectorOperator, condition.FieldValue as string); 
      switch (condition.FieldName) 
      { 
       case FieldItem.Category: 
        currentLogs = currentLogs.Where(item => search(item.Category)).ToList(); 
        break; 
       case FieldItem.InstanceID: 
        currentLogs = currentLogs.Where(item => search(item.InstanceID)).ToList(); 
        break; 
       case FieldItem.LogName: 
        currentLogs = currentLogs.Where(item => search(item.LogName)).ToList(); 
        break; 
       case FieldItem.Message: 
        currentLogs = currentLogs.Where(item => search(item.Message)).ToList(); 
        break; 
       case FieldItem.Number: 
        currentLogs = currentLogs.Where(item => search(item.Number)).ToList(); 
        break; 
       case FieldItem.Source: 
        currentLogs = currentLogs.Where(item => search(item.Source)).ToList(); 
        break; 
       case FieldItem.Type: 
        currentLogs = currentLogs.Where(item => search(item.Type)).ToList(); 
        break; 
      } 
     } 
     return currentLogs; 
    } 
} 

注我张贴这么晚,因为SO服务器崩溃,然后我就去睡觉:(
因此它类似于@ dasblinkenlight的答案。

2

处理这种任务的标准方法是创建一个自定义的IQueryable提供程序,并使用LINQ。从字面上看,您正在寻找的每个操作都通过LINQ表达式具有标准的可扩展机制。基本的想法是,你将有ExpressionVisitor实现应用每个重写规则,而不是有一个巨大的switch语句。由于您可以根据需要使用尽可能多的表达式访问者,因此您的维护和可扩展性成本会下降。

如果您想采取这种方法,我强烈建议您查看IQToolkit和Matt Warren的Building an IQueryable博客系列。

+0

该教程是全面的,但对我而言有点太复杂,你知道是否有更简单的(但同时实用)教程可用吗?我在Google上找不到任何内容。谢谢。 – PoweredByOrange 2013-05-13 22:31:35

2

避免嵌套和相关重复的一种方法是将提取值的代码部分与执行操作的代码部分分开。这里是一个应该说明的技术中的一个小例子:

Func<EventLogEntry,string> getString = null; 
Func<EventLogEntry,int> getInt32 = null; 
... 
switch (condition.FieldName) { 
    case FieldItem.Category: getString = e => e.Category; break; 
    case FieldItem.Message: getString = e => e.Message; break; 
    case FieldItem.Number: getInt32 = e => e.Number; break; 
    default:     throw new ApplicationException("Unsupported field"); 
} 
switch (condition.SelectorOperator) { 
    case SelectorOperator.Contains: 
     currentLogs = currentLogs.Where(item => getString(item).ToLower().Contains(condition.FieldValue as string)).ToList(); 
    break; 
    case SelectorOperator.EndsWith: 
     currentLogs = currentLogs.Where(item => getString(item).ToLower().EndsWith(condition.FieldValue as string)).ToList(); 
    break; 
    case SelectorOperator.Is: 
     currentLogs = currentLogs.Where(item => string.Equals(getString(item), condition.FieldValue as string, StringComparison.OrdinalIgnoreCase)).ToList(); 
    break; 
    case SelectorOperator.StartsWith: 
     currentLogs = currentLogs.Where(item => getString(item).ToLower().StartsWith(condition.FieldValue as string)).ToList(); 
    break; 
    case SelectorOperator.Equals: 
     currentLogs = currentLogs.Where(item => getInt32(item) == int.Parse(condition.FieldValue as string)).ToList(); 
    break; 
    case SelectorOperator.IsGreaterThan: 
     currentLogs = currentLogs.Where(item => getInt32(item) > int.Parse(condition.FieldValue as string)).ToList(); 
    break; 
    case SelectorOperator.IsLessThan: 
     currentLogs = currentLogs.Where(item => getInt32(item) < int.Parse(condition.FieldValue as string)).ToList(); 
    break; 
} 

添加新的源字段现在需要添加另一case到第一switch;在一个类型中增加一个新操作只需要第二个switch中的一个新的case,从而减少代码中“维护点”的数量。

+0

这绝对是比我最初的实现更好的解决方案,并显着增加了程序的可维护性。然而,在看到其他人建议使用'IQueryable'和'ExpressionVisitor'后,我试图看看如何将这些应用到我的问题。虽然你的答案绝对是一个很好的起点。谢谢! – PoweredByOrange 2013-05-13 22:58:02

+2

@ programmer93基于['IQueryProvider'](http://msdn.microsoft.com/en-us/library/system.linq.iqueryprovider.aspx)的方法将提供与.NET环境更好的集成,但它显得更加复杂(我已经实现了其中的一些,并且花费了大量的学习 - 即使在掌握了LINQ表达式的知识之外)。 – dasblinkenlight 2013-05-13 23:46:44

+0

嗯...我不介意学习它,当谈到LINQ时,我不是很有知识,但我觉得他们可以在编写更好的程序时非常有用,所以我需要在某些时候学习它们。但另一方面,你可能是对的,考虑到Windows EventLog的属性不会被定期更改,我不需要使它比目前更复杂。感谢您的建议,我会考虑它。 – PoweredByOrange 2013-05-13 23:52:23

1

我不明白为什么人们建议IQueryable的方法。我一直认为IQueryable用于将C#查询转换为SQL(SELECT语句)或XML(XQuery)等其他技术中的查询,以便它可以在适当的位置执行,而无需知道任何具体的技术细节查询被转换为(由您作为开发人员/程序员或您的代码 - 与该te没有紧密耦合chnology)。

由于您的查询在C#/ .NET代码中执行,因此不需要IQueryable。

例如,如果您将使用EventLog服务的本地查询功能,那么实现IQueryable将C#LINQ转换为查询字符串或其他EventLog服务可以理解并执行的其他窗体将非常有用。

对我来说,这个问题看起来像通过链接谓词创建复合谓词的问题,所以复合谓词可以用在LINQ Where语句中。

这取决于你想要怎么通用的解决方案是,但这里是一个可能实现一个大量使用类型推断和lambda闭包来创建复合谓语:

class Predicate<T> 
{ 
    public static Func<T, bool> Or(params Func<T, bool>[] predicates) 
    { 
     return item => predicates.Any(p => p(item)); 
    } 

    public static Func<T, bool> And(params Func<T, bool>[] predicates) 
    { 
     return item => predicates.All(p => p(item)); 
    } 

    #region Generic predicates 

    public static Func<T, bool> Is<TValue>(Func<T, TValue> selector, string value) where TValue : IEquatable<TValue> 
    { 
     return item => GetEqualityComparer<TValue>().Equals(selector(item), Parse<TValue>(value)); 
    } 

    public static Func<T, bool> IsNot<TValue>(Func<T, TValue> selector, string value) where TValue : IEquatable<TValue> 
    { 
     return item => !Is(selector, value)(item); 
    } 

    public static Func<T, bool> IsLessThan<TValue>(Func<T, TValue> selector, string value) where TValue : IComparable<TValue> 
    { 
     return item => GetComparer<TValue>().Compare(selector(item), Parse<TValue>(value)) < 0; 
    } 

    public static Func<T, bool> IsLessThanOrEqualTo<TValue>(Func<T, TValue> selector, string value) where TValue : IComparable<TValue> 
    { 
     return item => GetComparer<TValue>().Compare(selector(item), Parse<TValue>(value)) <= 0; 
    } 

    public static Func<T, bool> IsGreaterThan<TValue>(Func<T, TValue> selector, string value) where TValue : IComparable<TValue> 
    { 
     return item => !IsLessThanOrEqualTo(selector, value)(item); 
    } 

    public static Func<T, bool> IsGreaterThanOrEqualTo<TValue>(Func<T, TValue> selector, string value) where TValue : IComparable<TValue> 
    { 
     return item => !IsLessThan(selector, value)(item); 
    } 

    public static Func<T, bool> IsBetween<TValue>(Func<T, TValue> selector, string lower, string higher) where TValue : IComparable<TValue> 
    { 
     return item => IsGreaterThan(selector, lower)(item) && IsLessThan(selector, higher)(item); 
    } 

    #endregion 

    #region String specialized predicates 

    public static Func<T, bool> Contains(Func<T, string> selector, string value) 
    { 
     return item => selector(item).IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0; 
    } 

    public static Func<T, bool> StartsWith(Func<T, string> selector, string value) 
    { 
     return item => selector(item).StartsWith(value, StringComparison.OrdinalIgnoreCase); 
    } 

    public static Func<T, bool> EndsWith(Func<T, string> selector, string value) 
    { 
     return item => selector(item).EndsWith(value, StringComparison.OrdinalIgnoreCase); 
    } 

    #endregion 

    private static IEqualityComparer<TValue> GetEqualityComparer<TValue>() 
    { 
     // If value type is string, use OrdinalIgnoreCase equality comparer. 
     return typeof(TValue) == typeof(string) ? (IEqualityComparer<TValue>)StringComparer.OrdinalIgnoreCase : EqualityComparer<TValue>.Default; 
    } 

    private static IComparer<TValue> GetComparer<TValue>() 
    { 
     // If value type is string, use OrdinalIgnoreCase comparer. 
     return typeof(TValue) == typeof(string) ? (IComparer<TValue>)StringComparer.OrdinalIgnoreCase : Comparer<TValue>.Default; 
    } 

    private static TValue Parse<TValue>(string value) 
    { 
     // We need special handling for Enum type since, unfortunately, System.String doesn't handle conversion to Enum type in its IConvertible.ToType implementation. 
     // All other used types (string, DateTime, int, long) are supported by Convert class. 
     return (TValue)(typeof(TValue).IsEnum ? Enum.Parse(typeof(TValue), value) : Convert.ChangeType(value, typeof(TValue), CultureInfo.InvariantCulture)); 
    } 
} 

// For easier typing, no need to explicitly specify type. 
class EventLogPredicate : Predicate<EventLogItem> 
{ 
} 

这里是你如何使用它:

var items = new List<EventLogItem>() 
{ 
    new EventLogItem() { LogName = "First" }, 
    new EventLogItem() { LogName = "Second bla", Number = 100 }, 
    new EventLogItem() { LogName = "Third bla", Number = 25 }, 
    new EventLogItem() { LogName = "Fourth", Number = 25 } 
}; 

var predicate = EventLogPredicate.And(EventLogPredicate.Contains(item => item.LogName, "bla"), EventLogPredicate.IsLessThan(item => item.Number, "50")); 

var filteredItems = items.Where(predicate).ToArray(); 
+0

我喜欢这种方法,但条件是自定义类型,我需要迭代它们,所以我不知道如何使用var var predicate = EventLogPredicate.And(EventLogPredicate.Contains(item => item.LogName ,“bla”),EventLogPredicate.IsLessThan(item => item.Number,“50”));'因为我不能先确定条件是什么, – PoweredByOrange 2013-05-14 16:13:18