具有3层体系结构的实体框架,跨域的不同实体

时间:2014-12-18 16:18:29

标签: asp.net wpf entity-framework mvvm n-tier-architecture

我知道这个标题听起来像是一些现有帖子的副本,但我读了很多这些帖子,我的情况实际上是完全不同的。如果任何有实体框架经验的人可以就以下场景的最佳架构提供一些建议,我将非常感激。

我有一个带有3层布局,数据访问层,业务逻辑层和UI表示层的wpf应用程序。 UI使用MVVM。 DAL使用实体框架。 UI和数据访问层都有自己的模型,UIModel和DataModel。

当前设计在整个应用程序中使用全局DbContext for Entity Framework。对于简单的更新操作,从数据库中检索实体作为DataModel,转换为GUIModel,连接到ViewModel和View以获取更新,并转换回DataModel以在数据库中更新。这就是问题:当从转换创建新的DataModel时,它不再与检索到的原始实体相关,并且Entity Framework无法执行更新,因为它现在有两个相同主键的重复模型附加到同一个DbContext

我做了一些研究,发现了几种可能的方法来解决这个问题。一种是在所有层中使用单个模型实体,而不是分离GUIModel和DataModel,并将全局DbContext分解为工作单元。这似乎是一种非常常见的设计,但我对这种方法的关注是GUIModel和DataModel的合并违反了责任分离,并且使用工作单元需要Business Layer来控制DbContext的生命周期,这也模糊了它之间的界限。 BLL和DAL。

第二种方法是对具有using块的每个数据库查询使用本地DbContext。这似乎是最有效的内存。但是这样做会使延迟加载变得不可能,并且在每个查询中急切加载所有导航属性可能会影响性能。短暂的DbContexts也需要完全在断开连接的图形中工作,这在变化跟踪方面变得相当复杂。

第三种可能性是在更新后缓存所有原始DataModel并更新这些实体。

我是Entity Framework的新手,我确信应该有其他方法来解决这个问题。如果有人能就最好的方法提供一些见解,我将非常感激。

3 个答案:

答案 0 :(得分:1)

更好的方法是当您在存储库中进行更新调用时,首先通过主键获取实体,现在您在dbContext中,需要更新所需的实体,然后分配更新的字段并更新上下文。

这是代码:

public void UpdateEntity(Entity updatedEntity)
    {
        using (var db = new DBEntities())
        {
            var entity = db.Entities.Find(updatedEntity.Id);
            if (entity!= null)
            {
                entity.Name = updatedEntity.Name;
                entity.Description = updatedEntity.Description;
                entity.LastModifiedBy = updatedEntity.LastModifiedBy;
                entity.Value = updatedEntity.Value;
                entity.LastModifiedOn = DateTime.Now;
                db.SaveChanges();
            }
        }
     }

答案 1 :(得分:0)

我建议您使用第二种替代方案中描述的单独Business Objects。在多层方案中,您可以从UI角度创建支持用例的可重用对象,对业务域的行为进行建模(因为您将其称为“GUIModel”)。这些模型应该关注系统的行为,并且只包含支持此行为所需的数据。这与专注于数据的实体类形成鲜明对比。

示例:Northwind数据库,客户表。该实体将是一个包含客户所有属性的类,可能具有相关事物的导航属性。当您需要在自动完成搜索框的下拉列表中显示精简客户信息列表时,您真的想要使用此模型吗?您是否希望使用相同的模型在网格中显示客户及其汇总的发票数据?您需要将所有客户信息与相关发票一起加载到您的表示层。你可能不想这样做。

如果您针对不同的用例使用不同的模型,那么从面向对象的角度来看,事情会更有意义:

班级CustomerSearchResult:身份证,姓名。 GetCustomerEdit方法。

班级CustomerInvoiceInfo:身份证,姓名,汇总发票值。 GetCustomerEdit方法。

CustomerEdit:要显示和编辑的所有属性,用于乐观并发检查的时间戳。更改跟踪逻辑,验证逻辑。在编辑客户时模拟所需行为的方法。

CustomerEntity:这是您的数据对象,类似于customers表。您可以将其用作DTO来初始化数据库中的其他对象,或将更改推送回数据库。你不是通过电汇发送它。

这样,当您到达数据访问层时,可以将DbContext放入using块并尊重工作单元格。当然,您需要通过从CustomerEdit实例创建新的CustomerEntity来反映对context.Entry(entity).State = EntityState.Modified; context.SaveChanges(); 实例所做的更改,并将其重新附加到修改后的上下文中:

{{1}}

这一开始看起来很复杂且繁琐,但实际上,实体框架并不包含任何可以在断开连接(n层)场景中帮助你的魔法。如果您尝试使用延迟加载或保持DbContext实例始终打开等事情,事情会变得非常快。

如果您正在寻找有助于创建Business Objects并支持多层体系结构的框架,请查看CSLA.net。免责声明:很多人不喜欢它。如果使用错误会使事情变得更糟。不过,它在一些项目中帮助了我,我很高兴。

答案 2 :(得分:0)

  1. 您可以使用实例将实体附加到现有的dbContext 下面的代码,here也是关于MSDN实体状态的好文章
    var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" }; 
    using (var context = new BloggingContext()) 
    { 
        context.Entry(existingBlog).State = EntityState.Modified; 
        // Do some more work...
    context.SaveChanges(); }
  2. 关于3层,我想首先为每一层提供.net上下文的小描述

    • 演示文稿,这是将结果返回到使用的层,它可以是ASP.Net网站,Windows窗体,Web Api,WCF服务或其他任何形式
    • 业务,这应该包括您的业务的域模型,业务逻辑和跨多个域实体提供业务的服务
    • 数据访问/持久性,该层应该包含用于持久化并将域模型检索到持久性媒体(如DB,文件系统,......)的逻辑。

    一般来说,这里的常见问题是哪个模型进入哪个层,例如X类应该进入演示或业务,我建议一种简单的方法来帮助您做出决定,即引入新的兄弟层,所以问问自己是否愿意喜欢将另一个表示层构建为控制台而不是windows,您是否会将该逻辑复制并粘贴到新图层中?如果是,那么您的课程很可能不在正确的位置。

  3. 最后提出一些具体建议,

    • 保持每个图层都有其模型,因为每个图层都是唯一的 责任,也有好的框架可以帮助你 AutoMapper
    • 等模型之间的映射
    • 不要跨层传输实体框架模型,因为这会破坏关注点的分离,如果启用了延迟加载,它也会遇到越来越多的问题。
    • 除非你知道自己在做什么,否则尽量避免延迟加载,其中一个常见的陷阱是选择N + 1而here是一篇描述它的好文章。
    • 此外,如果您有复杂的业务,请尝试分离查询系统并通过应用CQRS模式进行更新,并且有一些框架可以帮助您,例如Dapper