【问题标题】:Implementing Entity Framework for Simple Data Entry App为简单的数据输入应用程序实现实体框架
【发布时间】:2014-08-17 19:52:30
【问题描述】:

这可能已经被问和回答了 1000 次,但今天早上 Google 还不是我的朋友。

我正在从使用存储过程和业务对象切换到使用实体框架。我喜欢从生成的 EDM 生成 POCO 的简单性(此处为数据库优先方法)。而且我喜欢打字少得多。

我很难为一个非常常见的应用场景(无论如何,在我的世界中)设计一个合适的设计。

基本上,想象一个数据输入应用程序,假设是一个在线商店的管理员(我在 WPF 中做这件事,但它很容易基于 Web)。

管理员希望在数据网格中查看客户列表(管理客户视图)。对于每一行,都有一个按钮来编辑客户或删除它。在网格的底部有一个用于创建新客户的按钮。

如果他们删除客户,则会立即从数据网格以及后端数据存储中将其删除(确认后)。如果他们编辑客户,则会弹出一个窗口(编辑客户视图),显示该客户的当前数据。他们可以编辑数据,然后单击提交或取消。提交将更改保存到数据存储中,取消则放弃更改。两个按钮都会关闭窗口。

如果他们从 Manage Customer 视图中单击 New Customer 按钮,则会创建一个新的 Customer 对象(尚未保存到 DB),并打开相同的 Edit Customer 视图,显示新的空白客户。

这是我到目前为止所得到的:

当构建管理客户视图模型时,它会填充客户的公共列表,例如:

public List<Customer> customers {get; set; }  
using (WebStoreEndities context = new WebStoreEntities())
{
    customers = context.Customers.ToList();
}

然后,如果管理员用户单击 Customer 行中的 Edit 按钮,则绑定的 Customer 对象将传递给 Edit Customer 视图的构造函数。该视图构造其视图模型,该模型具有视图绑定到的客户属性。用户可以在视图中对 Customer 对象进行更改,然后单击保存或取消。

这是我迷路的地方。在我的业务对象/存储过程实现中,我将只有两个客户对象:一个用于正在编辑的客户(绑定到视图),以及该客户的一个副本,称为 backupCustomer,用于在它们取消更改时恢复更改编辑客户视图(因为我使用的是 MVVM,所以客户的属性会立即从 UI 中更改,如果他们开始进行更改,然后单击取消,他们将不会在该客户中看到他们的更改)。

更重要的是,如果他们确实在 Edit Customer 视图中单击了 Submit,则会调用 Customer 业务对象的 Save() 方法,该方法进入 DAL 并触发存储过程以更新数据存储。

好的,现在进入实体框架现实。

问题 #1。无法保存单个实体。因此,即使我将 Customer 实体扩展为具有 Save() 方法,它也必须创建一个新的 WebStoreEntities 上下文并在其上调用 SaveChanges():

using (WebStoreEntities context = new WebStoreEntities())
{
    context.SaveChanges();
}

这对我来说似乎很奇怪。我认为您不希望有一个实体实例来创建实体上下文和东西。

问题 #2。在我的业务对象实现中,我缓存了我的对象,因此我只需要从数据库中获取它们一次。如果他们对客户进行更改,那就太好了。我只是在上面调用 save() 并更新数据存储。与删除和插入相同。但是我永远不必多次获取相同的客户集合(并发性不是这个特定项目的问题)。在我的 EF 实现中,每次他们打开“管理客户”视图时,都会触发上面的代码以获取客户列表。我想我可以只在整个应用程序期间保持一个数据上下文打开,但这似乎也是一个糟糕的设计。为整个用户会话绑定数据存储的连接,因为他们可能会多次打开同一个视图。

请帮我解决以上问题,如果可以的话,不要挂断我要说的话(反正这只是我的初步印象):

似乎 EF 在我的关注点分离中混淆了逻辑边界:

  • 您必须在 UI 项目中保留实体连接字符串的副本(我通常将业务对象和数据对象保存在单独的项目中)。
  • 您不能告诉实体保存或删除自己。您必须从底层上下文中执行此操作,这通常位于 UI 层中。在我的 UI 中,我喜欢能够说 myBusinessObject.Save() 或 myBusinessObject.Delete(),因为我知道对象知道如何保存或删除自己。

无论如何,EF 似乎是未来,所以我会坚持下去。我会喜欢你的建议。

非常感谢!

放克猴子。

【问题讨论】:

标签: c# wpf entity-framework


【解决方案1】:

虽然大多数示例都让您实现了由using 包围的查询,但您实际上不应该这样做。每个 EF 上下文跟踪它自己的实体更改,通过使用多个 usings,您将不知道查找哪个上下文调用了 SaveChanges。因此,只需为每个用户使用一个上下文,并在您完全完成时处理(退出时等)。您可以使用单例或静态类,在桌面应用程序中它似乎与我的经验没有太大区别。在 MVVM 场景中,您也可以使用 ViewModel 处理上下文,因此当您实例化您的 ViewModel 时,实例化您的上下文并在 dispose 上处理您的上下文,这可能更符合逻辑,具体取决于您在内部处理数据的方式.

为了能够还原更改,EF 实际上跟踪对象的原始数据库版本以及对象的更改版本。然而,要获得这些信息有点复杂:

断开连接并查找实体:

((IObjectContextAdapter)myContext).ObjectContext.Detach(dbObject);
var entry = myContext.Entry(dbObject);
var original = entry.OriginalValues;

就我个人而言,我只是在代码中处理复制和保存原始对象,它更干净,似乎更安全。它也可能更快,但我从未运行测试来证明这一点。如果您处于多用户环境中,您可能会受益于简单地从数据库重新加载数据,这样您就不会错误地显示陈旧的数据。

【讨论】:

  • 谢谢。我最终走这条路,这对我的目的最有意义。它工作得非常好!感谢大家的帮助。无论出于何种原因,将此响应标记为答案不起作用。也许 SO 不喜欢 Safari。无论如何,谢谢。
【解决方案2】:

问题 #1:您希望实体拥有 Save 方法,但又想避免在实体和持久层(例如 EF 上下文)之间创建耦合?好吧,如果Save 方法是由实体实现的,那么您就无法避免这种情况。或许更好的是,将 Save 方法移至存储库:

repository.Update(entity);

现在由存储库负责创建 EF 上下文,而不是实体。

问题 #2:EF 上下文是轻量级的,正常的使用模式是你描述的上下文是在哪里临时创建的,然后在保存更改后释放。可以想象,您可以创建一个桌面应用程序,该应用程序在应用程序的生命周期内只有一个上下文,但是如果在应用程序运行时更改了数据库,则上下文的内容将会过时。状态不一致迟早会打击你,我认为如果你坚持瞬态上下文模式,你会得到一个更易于维护的应用程序。如果您正在编写 Web 应用程序,您将无法选择在请求之间保持数据库上下文处于活动状态,并且该模式已被证明在编写业务应用程序时非常成功。

所以我推荐这个:

  • 在存储库或服务类中实现持久性,而不是在实体类中。

  • 在读取或写入实体时,以确保 EF 上下文仅在操作期间(工作单元)存在的方式进行。或者,您可以使用行版本号来确保实体在上次写入后在数据库中发生更改时无法更新。

【讨论】:

    【解决方案3】:

    听上去……您更喜欢 ActiveRecord 模式……但 EF 遵循 UnitOfWork 模式……在您的情况下,您使用的是 POCO 实体……它们是“持久无知”的。

    隐藏“EF 技术”的一种方法是创建一个“存储库”层,在其中隐藏所有 EF 逻辑,即管理“上下文”。但是创建另一个层可能是很多重复的工作。通常,您的存储库将共享相同的上下文。

    如果您保留 EF 上下文,那么它会为您管理已检索对象的更改跟踪和缓存。

    或者,您可以在断开连接的模式下工作...在每次要检索/保留实体时创建上下文...但是,您必须自己进行缓存和状态跟踪并“重新附加”在提交之前将对象添加到上下文中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-19
      • 2016-09-07
      • 1970-01-01
      • 2012-05-15
      • 1970-01-01
      相关资源
      最近更新 更多