这可能已经被问过并回答了1000次,但谷歌今天早上并不是我的朋友。
我正在使用存储过程和业务对象切换到使用Entity Framework。我喜欢从生成的EDM(这里是Database-First方法)生成POCO的简单性。我喜欢打字的次数少得多。
我很难将一个合适的设计包裹在一个非常常见的应用场景中(无论如何,在我的世界里)。
基本上,想象一下数据录入应用程序,假设是在线商店的管理员(我在WPF中这样做,但它可以很容易地基于网络)。
管理员希望在数据网格中查看客户列表(管理客户视图)。对于每一行,都有按钮来编辑客户或删除它。在网格的底部有一个按钮来创建一个新客户。
如果他们删除了客户,则会立即从数据网格以及后端数据存储中删除(在确认之后)。如果他们编辑客户,则会弹出一个窗口(编辑客户视图),显示该客户的当前数据。他们可以编辑数据,然后单击“提交”或“取消”。提交将更改保存到数据存储,取消将丢弃更改。两个按钮关闭窗口。
如果,他们从Manage Customer视图中单击New Customer按钮,然后创建一个新的Customer对象(尚未保存到DB),并打开相同的Edit Customer视图,显示新的空白Customer。
这是我到目前为止所得到的:
构建管理客户视图模型时,它会填充客户的公共列表,例如:
public List<Customer> customers {get; set; }
using (WebStoreEndities context = new WebStoreEntities())
{
customers = context.Customers.ToList();
}
然后,如果admin用户单击Customer行中的Edit按钮,则绑定的Customer对象将传递给Edit Customer视图的构造函数。该视图构造其视图模型,该模型具有视图绑定的customer属性。用户可以在视图中对Customer对象进行更改,然后单击“保存”或“取消”。
这是我迷路的地方。在我的业务对象/存储过程实现中,我只有两个Customer对象:一个用于正在编辑的客户(绑定到视图),以及一个名为backupCustomer的客户副本,如果他们取消了更改,则用于还原更改。编辑客户视图(因为我使用MVVM,客户的属性会立即从UI更改,如果他们开始进行更改,然后单击取消,他们将不会在该客户中看到他们的更改)。
更重要的是,如果他们在“编辑客户”视图中单击“提交”,则会调用Customer业务对象的Save()方法,该方法将进入DAL并触发存储过程以更新数据存储。
好的,现在进入实体框架现实。
问题#1。无法保存单个实体。因此,即使我将Customer实体扩展为具有Save()方法,也必须创建一个新的WebStoreEntities上下文并在其上调用SaveChanges():
using (WebStoreEntities context = new WebStoreEntities())
{
context.SaveChanges();
}
这对我来说似乎很奇怪。我不认为你想要一个实体实例创建实体上下文和东西。
问题#2。在我的业务对象实现中,我缓存了我的对象,所以我只需要从DB中获取一次。如果他们对客户进行更改,那就太好了。我只是在它上面调用save()并更新数据存储。与删除和插入相同。但我不必多次获取同一个客户集合(并发性不是这个特定项目的问题)。在我的EF实现中,每次打开Manage Customers视图时,它都会触发上面的代码来获取Customers列表。我想我可以在整个应用程序中保持一个数据上下文打开,但这似乎也是一个糟糕的设计。为整个用户会话绑定数据存储的连接,只是因为他们可能会多次打开同一个视图。
请帮助我解决上述问题,如果可以的话,不要挂断我要说的话(这只是我最初的印象):
似乎EF混淆了我关注点分离的逻辑边界:
无论如何,好像EF是未来,所以我会坚持下去。我很想你的建议。
非常感谢!
Funk Monkey。
答案 0 :(得分:0)
虽然大多数示例都让您实现由using
包围的查询,但在您的情况下,您实际上不应该这样做。每个EF上下文跟踪它自己的实体更改,通过使用多个using
,您将不知道查找哪个上下文来调用SaveChanges
。因此,每个用户只需使用一个上下文,并在完成后(在退出时等)处理。您可以在桌面应用程序中使用单例或静态类,这似乎与我的经验没有多大区别。在MVVM场景中,您也可以使用处理上下文的ViewModel,因此当您实例化ViewModel实例化上下文并在dispose上处理上下文时,这可能更具逻辑意义,具体取决于您在内部处理数据的方式
为了能够还原更改,EF实际上会跟踪对象的原始数据库版本以及对象的更改版本。然而,要获得这些信息有点令人费解:
断开并查找实体:
((IObjectContextAdapter)myContext).ObjectContext.Detach(dbObject);
var entry = myContext.Entry(dbObject);
var original = entry.OriginalValues;
就个人而言,我只是处理复制并将原始对象保存在代码中,它更干净,似乎更安全。它也可能更快,但我从未运行测试来证明这一点。如果您处于多用户环境中,您可以从数据库重新加载数据中受益,这样您就不会错误地显示陈旧数据。
答案 1 :(得分:0)
问题#1:您希望实体拥有Save
方法,但是您希望避免在实体和持久层之间创建耦合(例如EF上下文)?好吧,如果Save
方法由实体实现,那么你无法避免这种情况。或许更好的是将Save
方法移动到存储库:
repository.Update(entity);
现在,存储库负责创建EF上下文,而不是实体。
问题#2:EF上下文是轻量级的,正常使用模式与描述瞬态创建上下文的位置一样,然后在保存更改后进行处理。可以想象,您可以创建一个桌面应用程序,该应用程序在应用程序的生命周期中具有一个上下文,但如果在应用程序运行时更改了数据库,则上下文的内容将过时。状态迟早会对你产生影响,如果你坚持使用瞬态上下文模式,我会认为你会得到一个更易于维护的应用程序。如果您正在编写Web应用程序,则无法在请求之间保持数据库上下文处于活动状态,并且在编写业务应用程序时该模式已证明非常成功。
所以我推荐这个:
在存储库或服务类中实现持久性,而不是在实体类中实现持久性。
当读取或写入实体时,以确保EF上下文仅在操作期间(工作单元)存在的方式执行。 (可选)您可以使用行版本号来确保如果实体在上次写入后在数据库中已更改,则无法更新该实体。
答案 2 :(得分:0)
通过它的声音......你更喜欢ActiveRecord模式......但EF遵循UnitOfWork模式...在你使用POCO实体的情况下......它是“持久无知的”。
隐藏“EF技术”的一种方法是创建一个“存储库”层,在其中隐藏所有EF逻辑,即“上下文”的管理。但是创建另一个层可能需要大量的重复工作。通常,您的存储库将共享相同的上下文。
如果您保留EF上下文,则它会为您管理已检索对象的更改跟踪和缓存。
或者,您可以在断开连接模式下工作...每次要检索/保存实体时都会创建上下文...但是,您必须自己进行缓存和状态跟踪并“重新连接”在提交之前,上下文的对象。