好的,所以我一直在构建我的第一个大型(ish)EF 4.1 POCO + MVC应用程序。它是遗留系统的替代品,因此我使用的是现有数据库。
我使用DbContext T4生成生成了我的POCO类。我有一些非常好的表格,我的MVC课程中有很多性感的泛型,以减少锅炉板代码......一切都很好。
突然间,我意识到(对我而言)最明智的事情是某些业务逻辑在我的POCO对象的某些属性的“集合”中。
E.g。假设T4生成了以下类;
public partial class SalesOrderLine
{
public int ID { get; set; }
public int SalesOrderID { get; set; }
public int ProductID { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal ExtendedPrice { get; set; }
public virtual Product Product { get; set; }
public virtual SalesOrder SalesOrder { get; set; }
}
暂时忽略计算字段“ExtendedPrice”甚至不应该存储在数据库中的明显论点,并且只是跟我一起骑车...
...然后,在我看来,从逻辑上讲,如果这个对象真的应该代表一个销售订单行,那么我应该能够构建我的对象,以便下面的单元测试可以工作:
SalesOrderLine sol = new SalesOrderLine();
sol.UnitPrice = 100;
sol.Quantity = 5;
Assert.IsEqual(sol.ExtendedPrice, 500);
......显然我不能那样做,只要我想要由T4生成基础POCO。在我看来,我有几个选择:
设置生成的代码文件的属性“不编译”,将生成的代码复制并粘贴到另一个文件中并修改“set”,以便在设置UnitPrice或Quantity时执行设置扩展价格的业务逻辑。这里的缺点是每当从数据库加载一个对象时就会运行逻辑(因为EF将设置公共属性而不是我的私有字段)。此外,当数据库发生更改时,需要在项目的剩余使用期内手动维护此对象。
创建一个UpdateTotals函数,该函数在我对我的对象的Validate例程中调用,该函数由DbContext上的SaveChanges()调用。显然,上述单元测试在这种情况下不起作用。然而,系统和我的集成测试都可以工作,只有在对对象进行了更改时才会调用代码。
决定我问的是错误的问题,并且我应该向名为“SetPrice”和“SetQuantity”的对象添加方法,然后将UnitPrice和Quantity的set访问器限定为“internal ”。这里的缺点是MVC将尝试从表单更新模型,并且无法设置这些属性。
一些解决方案涉及下载两个或三个以上的框架,这些框架创建的抽象级别比我已经拥有的更多...一个存储库模式,或者“使用NHibernate”或类似的东西......你可以建议这个但是我越来越厌倦了以“学术上正确”的方式设置它的工作量。对于这个项目,我宁愿在长期可维护性与开发速度之间达成一致,并且不会因为大量额外的工具和dll而使我的项目过于复杂......但我会试着保持开放的心态:)
---编辑:另一个想法---
[5。]另一个想法,因为字段总是简单地计算,所以应该真的没有必要设置它们 - 无论是从数据库还是其他方面。因此,这样的事情可能有用:
public decimal ExtendedAmount
{
get { return UnitPrice * Quantity; }
internal set { }
}
...我的想法是EF实例化会尝试调用“set”,但是set无效,然后,当对象被保存或检查更改时,它会调用'get',那就是返回计算值,该值将存储在DB中。这里唯一的缺点是,当数据库存储在ExtendedAmount字段中的值不正确时,您尝试使用对象模型来验证数据库。我知道,这是一个小小的新手,但我认为这将是一个有趣的技巧......实际上,如果(值!= UnitPrice * Quantity)
,“set”可能会抛出异常---结束编辑---
我很想知道其他人在这类案件中所做的事情,因为我确信这很常见。好像很多教程都会把你带到“从数据库中生成POCO类”,然后将剩下的项目开发留给你。
干杯, 克里斯
答案 0 :(得分:1)
一些想法:
为什么不使用Code First?这样,您可以将业务逻辑(例如,计算的属性)放在您的实体类中。
实施例
public partial class SalesOrderLine
{
public int ID { get; set; }
public int SalesOrderID { get; set; }
public int ProductID { get; set; }
private decimal _unitPrice;
public decimal UnitPrice
{
get { return _unitPrice; }
set
{
if (value == _unitPrice) return;
_unitPrice = value;
CalculateExtendedPrice();
}
}
private decimal _quantity;
public decimal Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity= value;
CalculateExtendedPrice();
}
}
public decimal ExtendedPrice { get; set; }
public virtual Product Product { get; set; }
public virtual SalesOrder SalesOrder { get; set; }
private void CalculateExtendedPrice()
{
ExtendedPrice = UnitPrice * Quantity;
}
}
如果Code First不是一个选项,那么如何将您的实体设为partial class(如果尚未实施)并将您的业务逻辑放在单独的代码文件中(但具有相同的类名) 。这样,生成时您的主代码文件将被覆盖,但您的辅助代码文件将保留。这是处理生成代码的常用方法。