1

我有一堆这些任务都基于 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 个这样的查询;例如地区、项目名称等。


编辑:

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

例如,这没有得到正确翻译,但绝不是因为答案不正确,根本无法与我正在使用的 API 一起工作——可能是因为它实现得很差。

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

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

4

6 回答 6

2

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

于 2012-09-03T08:19:43.810 回答
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));
于 2012-09-03T08:28:00.787 回答
2

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

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

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

这样,您可以将大部分复杂性放在为变量提供值的过程中,并且 linq 查询将更易于阅读和维护。

于 2012-09-03T08:37:36.290 回答
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 易于使用自动化测试框架进行测试。

于 2012-09-03T08:27:31.067 回答
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));
于 2012-09-03T09:08:37.333 回答
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).

于 2012-09-06T10:39:15.430 回答