我最近从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子句,以避免大量重复的代码。
经过大量搜索,我尝试使用建议的here解决方案,但这已在Entity Framework 6中取代。
如何避免不必要地重复代码?
答案 0 :(得分:0)
The nice thing about LINQ is that methods like Where()
return an IEnumerable<T>
that you can feed into the next method.
You could refactor the where
clauses into extension methods akin to:
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);
}
}
And then compose your queries using these building blocks:
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)
}
);
Now that you have composed the query, execute it by enumerating. No DB access has happened until now.
var output = result.ToArray(); // <- DB access happens here
Edit After the suggestion of Ivan, I looked at our codebase. As he mentioned, the Extension methods should work on IQueryable
instead of IEnumerable
. Just take care that you only use expressions that can be translated to SQL, i.e. do not call any custom code like an overriden ToString()
method.
Edit 2 If Payment
and other model classes inherit BaseTable
, the filter methods can be written as generic methods that accept any child type of BaseTable
. Also added example for grouping method.
答案 1 :(得分:0)
我确实尝试使用你建议的扩展子句,但是我生成的类没有扩展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
由于此查询是我将要应用的最简单的查询,因此将来的查询将变得更加复杂。