使用L2E向数据模型中的对象添加行为时,首选方法是什么?
拥有一个包装器类,只需要您需要的数据就可以实现所需的行为
using (var dbh = new ffEntities())
{
var query = from feed in dbh.feeds select
new FFFeed(feed.name, new Uri(feed.uri), feed.refresh);
return query.ToList();
}
//Later in a separate place, not even in the same class
foreach (FFeed feed in feedList) { feed.doX(); }
直接使用数据模型实例,并拥有一个在这些实例的IEnumerable上运行的方法
using (var dbh = new ffEntities())
{
var query = from feed in dbh.feeds select feed;
return query.ToList();
}
//Later in a separate place, not even in the same class
foreach (feeds feed in feedList) { doX(feed); }
在数据模型类上使用扩展方法,因此它最终会拥有包装器所具有的额外方法。
public static class dataModelExtensions {
public static void doX(this feeds source) {
//do X
}
}
//Later in a separate place, not even in the same class
foreach (feeds feed in feedList) { feed.doX(); }
哪一个最好?我倾向于支持最后一种方法,因为它很干净,不会干扰CRUD设施(我可以直接使用它来插入/更新/删除,不需要包装回来),但我想知道是否有一个缺点我没有没见过。
有第四种方法吗?我没有把握LINQ的哲学,特别是关于LINQ to Entities。
答案 0 :(得分:1)
据我所知,Entity类是部分类,因此您可以使用partial
关键字直接添加另一个扩展它们的文件。
否则,我通常有一个包装类,即我的ViewModel(我正在使用带MVVM的WPF)。我还有一些带有流畅接口的通用Helper类,用于向ViewModel添加特定的查询过滤器。
答案 1 :(得分:1)
我认为将行为放在实体类型上是错误的。
实体框架基于实体数据模型,由其架构师之一描述为“非常接近.NET的对象数据模型,模拟行为”。换句话说,您的实体模型旨在将关系数据映射到对象空间,但不应使用方法进行扩展。保存业务类型的方法。
与其他一些ORM不同,您不会遇到任何来自黑匣子的对象类型。您可以使用LINQ投影到几乎任何类型,即使它的形状与您的实体类型不同。因此,仅使用实体类型进行映射,而不是使用业务代码,数据传输或表示模型。
生成代码时,实体类型被声明为部分。这导致一些开发人员尝试将它们扩展到业务类型。这是个错误。实际上,扩展实体类型并不是一个好主意。可以在LINQ to Entities中查询在实体模型中创建的属性;您添加到分部类的属性或方法不能包含在查询中。
考虑这些商业方法的例子:
public Decimal CalculateEarnings(Guid id)
{
var timeRecord = (from tr in Context.TimeRecords
.Include(“Employee.Person”)
.Include(“Job.Steps”)
.Include(“TheWorld.And.ItsDog”)
where tr.Id = id
select tr).First();
// Calculate has deep knowledge of entity model
return EarningsHelpers.Calculate(timeRecord);
}
这种方法有什么问题?生成的SQL将变得非常复杂,因为我们已经要求实体框架实现整个对象的实例,仅仅是为了获得Calculate方法所需的少数属性。代码也很脆弱。更改模型不仅会破坏急切加载(通过Include调用),还会破坏Calculate方法。
单一责任原则规定一个班级应该只有一个改变的理由。在屏幕上显示的示例中,EarningsHelpers类型负责实际计算收益和保持与实体模型的更新保持同步。第一个责任似乎是正确的,第二个责任听起来不对。让我们看看我们是否可以解决这个问题。
public Decimal CalculateEarnings(Guid id)
{
var timeData = from tr in Context.TimeRecords
where tr.Id = id
select new EarningsCalculationContext
{
Salary = tr.Employee.Salary,
StepRates = from s in tr.Job.Steps
select s.Rate,
TotalHours = tr.Stop – tr.Start
}.First();
// Calculate has no knowledge of entity model
return EarningsHelpers.Calculate(timeData);
}
在下一个示例中,我重写了LINQ查询,仅选取Calculate方法所需的信息位,并将该信息投影到汇总Calculate方法参数的类型上。如果只是为了向方法传递参数而编写一个新类型似乎太多了,我也可以投射到一个匿名类型,并将Salary,StepRates和TotalHours作为单独的参数传递。但不管怎样,我们已经修复了EarningsHelpers对实体模型的依赖性,并且作为免费奖励,我们也获得了更高效的SQL。
您可能会查看此代码,并想知道如果TimeRecord的Job属性可以为空,会发生什么。我不会得到一个空引用异常吗?
不,我不会。此代码不会作为IL编译和执行;它将被翻译为SQL。 LINQ to Entities合并空引用。在屏幕上显示的示例查询中,如果Job为null,则StepRates将返回null。您可以将此视为与延迟加载相同,除非没有额外的数据库查询。代码说,“如果有工作,那么从其步骤加载费率。”
这种架构的另一个好处是它可以非常容易地对Web组件进行单元测试。一般来说,单元测试不应该访问数据库(换句话说,访问数据库的测试是集成测试而不是单元测试)。编写一个模拟存储库非常容易,它将对象数组作为Queryables返回,而不是实际进入实体框架。