代码之家  ›  专栏  ›  技术社区  ›  FunkMonkey33

实现简单数据输入应用程序的实体框架

  •  0
  • FunkMonkey33  · 技术社区  · 11 年前

    这个问题可能已经被问了1000次,但今天早上谷歌并不是我的朋友。

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

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

    基本上,想象一个数据输入应用程序,比如说一个在线商店的管理员(我是在WPF中做的,但它很容易是基于web的)。

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

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

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

    以下是我目前所获得的:

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

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

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

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

    更重要的是,如果他们在Edit Customer视图中单击Submit,就会调用Customer业务对象的Save()方法,该方法会到达DAL并启动存储过程以更新数据存储。

    好了,现在来谈谈实体框架的现实。

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

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

    这对我来说似乎很奇怪。我不认为你会想要一个实体实例来创建实体上下文之类的东西。

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

    请帮我解决以上问题,如果可以的话,不要被我要说的话所困扰(这只是我最初的印象):

    EF似乎模糊了我关注点分离的逻辑界限:

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

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

    非常感谢!

    疯猴子。

    3 回复  |  直到 11 年前
        1
  •  0
  •   siva.k    11 年前

    虽然大多数示例都实现了由 using 你不应该这样做。每个EF上下文通过使用多个 使用 你不知道查找的是哪个上下文 SaveChanges 因此,只需为每个用户使用一个上下文,并在完成后(退出时等)进行处理。你可以使用单例类或静态类,在桌面应用程序中,它似乎与我的经验没有太大区别。在MVVM场景中,您可能也可以使用ViewModel来处理上下文,因此当您实例化ViewModel时,实例化您的上下文并在dispose时处理上下文,这可能更符合逻辑,这取决于您如何在内部处理数据。

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

    断开并查找实体:

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

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

        2
  •  0
  •   Martin Liversage    11 年前

    问题1:您希望您的实体拥有 Save 方法,但您希望避免在实体和持久层(例如EF上下文)之间创建耦合?嗯,如果 拯救 方法由实体实现,则无法避免这种情况。也许更好的办法是 拯救 方法添加到存储库:

    repository.Update(entity);
    

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

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

    因此,我建议:

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

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

        3
  •  0
  •   Community Mohan Dere    9 年前

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

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

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

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