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

时间:2012-09-03 08:16:16

标签: c# linq refactoring

我有很多这些基于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);

有没有人知道更好的解决方案?我最终拥有巨大的功能,因为我需要做20个这样的查询,具体取决于要求;如Region,Project name等。


修改

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

例如,这不能正确翻译,但由于答案不正确,因此无法正常使用我正在使用的API - 可能是因为它实施得很差。

public bool IsValid(Type x)
{
    return (x.a == b) && (x.c ==d) && (x.d == e);
}

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

6 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

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

public static IQueryable<Mail> Count(this IQueryable<Mail> 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));

答案 2 :(得分:2)

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

var projectName="Delta";
var mailLanguage=(int)MailLanguage.RU;

var results=myService.Mail.Where(x => x.ProjectName == projectName)
            && (p.MailLang== mailLanguage);

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

答案 3 :(得分:1)

考虑将复杂比较移动到函数中。例如,而不是

Results.Where(x => (x.a == b) && (x.c == d) && (x.d == e))

考虑

Results.Where(x => IsValid(x))

...

public bool IsValid(Type x)
{
    return (x.a == b) && (x.c ==d) && (x.d == e);
}

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

答案 4 :(得分:1)

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

public class MailParameters
{
    public DateTime EndTime { get; private set; }
    public IEnumerable<int> 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<MailLang> languages, Priority priority)
    {
        ProjectName = projectName;
        StartTime = startTime;
        EndTime = endTime;
        Languages = languages.Cast<int>();
        Priority = (int)priority;
    }
}

然后添加以下扩展方法:

public static int Count(this IQueryable<Mail> 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));

答案 5 :(得分:0)

我的最终解决方案是基于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<String>();

    linqStatements.Add(parser.StringToLinqQuery<Project>("ProjectId", report.Project));
    linqStatements.Add(parser.StringToLinqQuery<Region>("RegionId", report.Region));
    linqStatements.Add(parser.StringToLinqQuery<Status>("Status", report.Status));
    linqStatements.Add(parser.StringToLinqQuery<Priority>("Priority", report.Priority));
    linqStatements.Add(parser.StringToLinqQuery<Category>("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<Mail>();

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

public string StringToLinqQuery<TEnum>(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<TEnum>(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)

相关问题