拆分长linq查询以提高可维护性

我有很多这些任务都基于LINQ查询。 我正在寻找重构它们的好方法,使它们更容易阅读,并允许我根据语言/区域等更改查询。

var mailTaskOne = CreateTask(() => myService.Mail.Where(p => p.ProjectName == "Delta" && (p.MailLang== (int)MailLanguage.EU || p.MailLang == (int)MailLanguage.RU) && (p.DateEntered >= startDate && p.DateEntered <= endDate) && p.MailPriority == (int)MailPriority.High).Count()); 

我认为方便的一种方法是将查询分成这样的东西。

 var results = myService.Mail.Where(x => x.ProjectName == "Delta"); results = results.Where(p => p.MailLang== (int)MailLanguage.EU); results = results.Where(p => p.DateModified >= startDate && p.DateModified <= endDate); 

这样我就可以在不必为每个区域重复整个查询的情况下执行此操作。

 if (MailLanguage == "English") results = results.Where(p => p.MailLang== (int)MailLanguage.EU); else results = results.Where(p => p.MailLang== (int)MailLanguage.RU); 

有谁知道更好的解决方案吗? 我最终拥有巨大的function,因为我需要做20个这样的查询,具体取决于要求; 如地区,项目名称等


编辑:

由于我不知道后端(web服务/ api)的一些限制,遗憾的是我不能使用这个问题中提到的一些很棒的答案。

例如,这不能正确翻译,但绝不是因为答案不正确,根本不适用于我正在使用的API – 可能是因为它实现得很差。

 public bool IsValid(Type x) { return (xa == b) && (xc ==d) && (xd == e); } 

无论如何,任何寻找类似解决方案的人都是有效的答案,但最后我最终得到的东西类似于提供的解决方案。

您可以创建一个参数类,如:

 public class MailParameters { public DateTime EndTime { get; private set; } public IEnumerable Languages { get; private set; } public int Priority { get; private set; } public string ProjectName { get; private set; } public DateTime StartTime { get; private set; } public MailParameters(string projectName, DateTime startTime, DateTime endTime, MailLang language, Priority priority) : this(projectName, startTime, endTime, new[] { language }, priority) public MailParameters(string projectName, DateTime startTime, DateTime endTime, IEnumerable languages, Priority priority) { ProjectName = projectName; StartTime = startTime; EndTime = endTime; Languages = languages.Cast(); Priority = (int)priority; } } 

然后添加以下扩展方法:

 public static int Count(this IQueryable mails, MailCountParameter p) { return mails.Count(m => m.ProjectName == p.ProjectName && p.Languages.Contains(m.MailLang) && m.EnteredBetween(p.StartTime, p.EndTime) && m.Priority == p.Priority); } public static bool EnteredBetween(this Mail mail, DateTime startTime, DateTime endTime) { return mail.DateEntered >= startTime && mail.DateEntered <= endTime; } 

那么用法是:

 var mailParametersOne = new MailParameters("Delta", startDate, endDate, new[] { MailLang.EU, MailLang.RU }, MailPriority.High); var mailTaskOne = CreateTask(() => myService.Mail.Count(mailParametersOne)); 

我会像你建议的那样将查询拆分到不同的行上,这意味着你可以在每行放置注释来描述它在做什么。 您仍然只进行一次数据库访问,因此您不会在性能方面失去任何东西,但可获得更好的可读性。

为什么不简单地为此目的设一个方法呢?

 public static IQueryable Count(this IQueryable mails, string projectName, MailLanguage mailLanguage, DateTime startDate, DateTime endDate) { return mails.Count(p=> p.ProjectName == projectName && p.MailLang == mailLanguage && p.DateEntered >= startDate && p.DateEntered <= endDate && p.MailPriority == (int)MailPriority.High); } 

然后你可以像这样简单地使用它

 CreateTask(() => myService.Mail.Count("Delta",MailLanguage.EU,startDate,endDate)); 

您可以将项目名称,数据修改,邮件语言和任何其他条件转换为变量,并根据任何条件确定您想要的值。 然后您的查询将使用变量而不是文字值。

 var projectName="Delta"; var mailLanguage=(int)MailLanguage.RU; var results=myService.Mail.Where(x => x.ProjectName == projectName) && (p.MailLang== mailLanguage); 

通过这种方式,您可以将大部分复杂性放在为变量赋值,而linq查询将更容易阅读和保留。

考虑将复杂比较移动到一个函数中。 对于exanple,而不是

 Results.Where(x => (xa == b) && (xc == d) && (xd == e)) 

考虑

 Results.Where(x => IsValid(x)) ... public bool IsValid(Type x) { return (xa == b) && (xc ==d) && (xd == e); } 

代码变得更易读,使用自动化测试框架可以轻松测试IsValid。

我的最终解决方案是基于ScottGu的一篇文章。 http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

我像这样构建LINQ查询。

  var linqStatements = new List(); linqStatements.Add(parser.StringToLinqQuery("ProjectId", report.Project)); linqStatements.Add(parser.StringToLinqQuery("RegionId", report.Region)); linqStatements.Add(parser.StringToLinqQuery("Status", report.Status)); linqStatements.Add(parser.StringToLinqQuery("Priority", report.Priority)); linqStatements.Add(parser.StringToLinqQuery("CategoryId", report.Category)); linqStatements.Add(AccountIdsToLinqQuery(report.PrimaryAssignment)); string baseQuery = String.Join(" AND ", linqStatements.Where(s => !String.IsNullOrWhiteSpace(s))); var linqQuery = service.Mail.Where(baseQuery).Cast(); 

StringToLinqQuery看起来像这样(简化版)。

 public string StringToLinqQuery(string field, string value) where TEnum : struct { if (String.IsNullOrWhiteSpace(value)) return String.Empty; var valueArray = value.Split('|'); var query = new StringBuilder(); for (int i = 0; i < valueArray.Count(); i++) { TEnum result; if (Enum.TryParse(valueArray[i].ToLower(), true, out result)) { if (i > 0) query.Append(" OR "); query.AppendFormat("{0} == {1}", field, Convert.ToInt32(result)); } else { throw new DynoException("Item '" + valueArray[i] + "' not found. (" + type of (TEnum) + ")", query.ToString()); } } // Wrap field == value with parentheses () query.Insert(0, "("); query.Insert(query.Length, ")"); return query.ToString(); } 

最终结果看起来像这样。

 service.Mail.Where("(ProjectId == 5) AND (RegionId == 6 OR RegionId == 7) AND (Status == 5) and (Priority == 5)") 

在我的项目中,我将值存储在XML文件中,然后将它们提供给上面的LINQ查询。 如果字段为空,则将被忽略。 它还使用|支持多个值 符号,例如EU|US将转换为(Region == 5 OR Region == 6)