繁体   English   中英

如何为EF6创建可重用的where子句

[英]How to create a reusable where clause for EF6

我最近从Java编码转到c#,我仍在学习c#的各种元素。

为了访问我无法重新设计的现有数据库,我使用实体框架6和“数据库中的代码优先”来生成表示数据库表的上下文和类型。 我正在使用Ling-To-SQL从数据库中检索严重非规范化的数据。

我当前的任务是创建一个报告,其中从各个表中读取每个部分,这些表都与一个基表有关系。

这是我的工作示例:

using(var db = new PaymentContext()) 
{
    var out = from pay in db.Payment
              join typ in db.Type on new { pay.ID, pay.TypeID } equals 
                                     new { typ.ID, typ.TypeID }
              join base in db.BaseTable on 
                  new { pay.Key1, pay.Key2, pay.Key3, pay.Key4, pay.Key5 } equals 
                  new { base.Key1, base.Key2, base.Key3, base.Key4, base.Key5 }
              where 
              base.Cancelled.Equals("0") &&
              base.TimeStamp.CompareTo(startTime) > 0 &&
              base.TimeStamp.CompareTo(endTime) < 1 &&
              .
              (other conditions)
              .
              group new { pay, typ } by new { typ.PaymentType } into grp
              select new
              {
                  name = grp.Key,
                  count = grp.Count(),
                  total = grp.Sum(x => x.pay.Amount)
              };
}

报告中将有大量部分,每个部分将生成一个where子句,其中包含显示的条件。 在某些部分中,所需数据将从表格中提取,最多可在BaseTable下面的五个级别中提取。

我想要做的是为每个报告部分创建一个resuable where子句,以避免大量重复的代码。

经过大量搜索后,我尝试使用此处建议的解决方案,但这已在Entity Framework 6中取代。

如何避免不必要地重复代码?

关于LINQ的好处是像Where()这样的方法返回一个可以提供给下一个方法的IEnumerable<T>

您可以将where子句重构为扩展方法,类似于:

public static class PaymentQueryExtensions {

    public static IQueryable<T> ApplyNotCancelledFilter(
        this IQueryable<T> payments) 
        where T : BaseTable {

        // no explicit 'join' needed to access properties of base class in EF Model
        return payments.Where(p => p.Cancelled.Equals("0"));
    }

    public static IQueryable<T> ApplyTimeFilter(
        this IQueryable<T> payments, DateTime startTime, DateTime endTime) 
        where T: BaseTable {

        return payments.Where(p => p.TimeStamp.CompareTo(startTime) > 0 
                                && p.TimeStamp.CompareTo(endTime) < 1);
    }

    public static IGrouping<Typ, T> GroupByType(
         this IQueryable<T> payments) 
         where T: BaseTable {

        // assuming the relationship Payment -> Typ has been set up with a backlink property Payment.Typ
        // e.g. for EF fluent API: 
        //  ModelBuilder.Entity<Typ>().HasMany(t => t.Payment).WithRequired(p => p.Typ);
        return payments.GroupBy(p => p.Typ);
    }
}

然后使用这些构建块编写查询:

IEnumerable<Payment> payments = db.Payment
    .ApplyNotCancelledFilter()
    .ApplyTimeFilter(startTime, endTime);

if (renderSectionOne) {
    payments = payments.ApplySectionOneFilter();
}

var paymentsByType = payments.GroupByType();

var result = paymentsByType.Select(new
          {
              name = grp.Key,
              count = grp.Count(),
              total = grp.Sum(x => x.pay.Amount)
          }
);

现在您已经编写了查询,请通过枚举来执行它。 直到现在才发生数据库访问。

var output = result.ToArray(); // <- DB access happens here

编辑在Ivan的建议之后,我查看了我们的代码库。 正如他所提到的,Extension方法应该适用于IQueryable而不是IEnumerable 请注意,您只使用可以转换为SQL的表达式,即不要调用任何自定义代码,如重写ToString()方法。

编辑2如果Payment和其他模型类继承BaseTable ,则可以将过滤器方法编写为接受任何子类型BaseTable 通用方法 还添加了分组方法的示例。

我确实尝试使用你建议的扩展子句,但我生成的类没有扩展BaseTable,所以我必须通过navigation属性显式定义链接​​。 由于查询中只有少量表是常见的,因此我决定根据需要将过滤器直接应用于每个表。 我会根据需要定义这些。

krillgar建议转向直接LINQ语法,这似乎是一个很好的建议。 我们打算在不久的将来重新设计我们的数据库,这将删除一些SQL依赖项。 我合并了建议的过滤器和完整的LINQ语法来访问我的数据。

// A class to hold all the possible conditions applied for the report
// Can be applied at various levels within the select
public class WhereConditions
{
    public string CancelledFlag { get; set; } = "0";  // <= default setting
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
}

// Class to define all the filters to be applied to any level of table
public static class QueryExtensions
{
    public static IQueryable<BaseTable> ApplyCancellationFilter(this IQueryable<BaseTable> base, WhereConditions clause)
    {
        return base.Where(bse => bse.CancelFlag.Equals(clause.CancelledFlag));
    }

    public static IQueryable<BaseTable> ApplyTimeFilter(this IQueryable<BaseTable> base, WhereConditions clause)
    {
        return base.Where(bse => bse.TimeStamp.CompareTo(clause.StartTime) > 0 &&
                                 bse.TimeStamp.CompareTo(clause.EndTime) < 1);
    }
}

查询的组成如下:

using (var db = new PaymentContext())
{
    IEnumerable<BaseTable> filter = db.BaseTable.ApplyCancellationFilter(clause).ApplyTimeFilter(clause);

    var result = db.Payment.
                Join(
                    filter,
                    pay => new { pay.Key1, pay.Key2, pay.Key3, pay.Key4, pay.Key5 },
                    bse => new { bse.Key1, bse.Key2, bse.Key3, bse.Key4, bse.Key5 },
                    (pay, bse) => new { Payment = pay, BaseTable = bse }).
                Join(
                    db.Type,
                    pay => new { pay.Payment.TypeKey1, pay.Payment.TypeKey2 },
                    typ => new { typ.TypeKey1, typ.TypeKey2 },
                    (pay, typ) => new { name = typ.Description, amount = pay.Amount }).
                GroupBy(x => x.name).
                Select(y => new { name = y.Key, 
                                  count = y.Count(), 
                                  amount = y.Sum(z => z.amount)});
}

然后最终执行组合查询。

var reportDetail = result.ToArray(); // <= Access database here

由于此查询是我将要应用的最简单的查询,因此将来的查询将变得更加复杂。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM