2013-02-21 128 views
13

直接使用上下文是否是个好主意?例如,假设我有一个客户数据库,用户可以按名称搜索它们,显示一个列表,选择一个,然后编辑该客户的属性。实体框架和WPF最佳实践

看来我应该使用上下文来获取客户列表(映射到POCO或CustomerViewModels),然后立即关闭上下文。然后,当用户选择列表中的CustomerViewModels中的一个时,UI的客户属性部分填充。

接下来他们可以更改名称,类型,网站地址,公司大小等。点击保存按钮后,我打开一个新的上下文,使用CustomerViewModel中的ID检索该客户记录,并更新其属性。最后,我打电话SaveChanges()并关闭上下文。这是很多工作。

我的问题是为什么不直接与整个环境打交道?我已阅读使用相同的长寿命范围的背景是非常糟糕的,并且不可避免地会导致问题。我的假设是,如果应用程序只能由一个人使用,我可以保持上下文畅通无阻。但是,如果将有许多用户,我希望保持一个简洁的工作单位,从而根据请求打开和关闭上下文。

有什么建议吗?谢谢。


@PGallagher - 感谢您的完整的答案。
@Brice - 您的输入也很有帮助

但是,@Manos D.'冗余代码的缩影'评论让我有点担心。让我通过一个例子。假设我将客户存储在数据库中,并且我的一个客户属性是CommunicationMethod。

[Flags] 
public enum CommunicationMethod 
{ 
    None = 0, 
    Print = 1, 
    Email = 2, 
    Fax = 4 
} 

我在WPF管理客户页面的UI将包含客户通信方法(打印,电子邮件,传真)下的三个复选框。我无法将每个复选框绑定到该枚举,这是没有意义的。此外,如果用户点击该客户,起床并去吃午餐......情景会在那里呆上几个小时,这很糟糕。相反,这是我的思维过程。

最终用户从列表中选择一个客户。我新建一个上下文,找到这个客户并返回一个CustomerViewModel,然后关闭这个上下文(为了简单起见,我已经把这个存储库留下了)。

using(MyContext ctx = new MyContext()) 
{ 
    CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId)); 
} 

此时用户可以选择/不选择打印,电子邮件,传真按钮,因为它们绑定到CustomerViewModel,其中也有一个Save()方法3个布尔属性。开始。

public class CustomerViewModel : ViewModelBase 
{ 
    Customer _customer; 

    public CustomerViewModel(Customer customer) 
    { 
     _customer = customer; 
    } 


    public bool CommunicateViaEmail 
    { 
     get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email); } 
     set 
     { 
      if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email)) return; 

      if (value) 
       _customer.CommunicationMethod |= CommunicationMethod.Email; 
      else 
       _customer.CommunicationMethod &= ~CommunicationMethod.Email; 
     } 
    } 
    public bool CommunicateViaFax 
    { 
     get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax); } 
     set 
     { 
      if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax)) return; 

      if (value) 
       _customer.CommunicationMethod |= CommunicationMethod.Fax; 
      else 
       _customer.CommunicationMethod &= ~CommunicationMethod.Fax; 
     } 
    } 
    public bool CommunicateViaPrint 
    { 
     get { return _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print); } 
     set 
     { 
      if (value == _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print)) return; 

      if (value) 
       _customer.CommunicateViaPrint |= CommunicationMethod.Print; 
      else 
       _customer.CommunicateViaPrint &= ~CommunicationMethod.Print; 
     } 
    } 

    public void Save() 
    { 
     using (MyContext ctx = new MyContext()) 
     { 
      var toUpdate = ctx.Customers.Find(_customer.Id); 
      toUpdate.CommunicateViaEmail = _customer.CommunicateViaEmail; 
      toUpdate.CommunicateViaFax = _customer.CommunicateViaFax; 
      toUpdate.CommunicateViaPrint = _customer.CommunicateViaPrint; 

      ctx.SaveChanges(); 
     } 
    } 
} 

你看到有什么问题吗?

回答

17

可以使用长时间运行的上下文;你只需要意识到其中的含义。

上下文表示一个工作单元。每次调用SaveChanges时,所有正在跟踪的实体的所有未决更改都将保存到数据库中。正因为如此,你需要将每个上下文的范围扩大到有意义的范围。例如,如果您有一个用于管理客户的选项卡,另一个用于管理产品,则每个用户都可以使用一个上下文,以便当用户单击保存在客户选项卡上时,他们对产品所做的所有更改都不会保存。

拥有大量的上下文也可能DetectChanges减缓跟踪实体。减轻这一点的一种方法是使用更改跟踪代理。

由于加载一个实体,并保存该实体可能是相当长的时间,打一个乐观并发异常的几率比用短暂的背景下越大。当一个实体在加载和保存之间在外部进行更改时会发生这些异常。 Handling these exceptions非常简单,但仍然需要注意。您可以在WPF长寿命的背景下做

一个很酷的事情是绑定到DbSet.Local特性(例如context.Customers.Local)。这是一个ObservableCollection,它包含所有未标记为删除的跟踪实体。

但愿这给你多一点信息,以帮助您决定帮助哪种方法。

+0

很好的答案。感谢您的回复:英孚团队成员的答案在这里总是很有价值。 – JYL 2014-03-05 15:20:01

0

上下文不是永久连接到数据库。它本质上是从磁盘加载的记录的内存缓存。如果您强制刷新或者将更改保存到磁盘时,它只会在您请求先前未加载的记录时才会从数据库请求记录。

打开上下文,抓取记录,关闭上下文,然后将修改后的属性复制到来自全新上下文的对象是冗余代码的缩写。您应该单独保留原始上下文,并使用它来执行SaveChanges()。

如果你正在寻找解决并发问题,你应该做的“处理并发性”为你的实体框架的版本谷歌搜索。

例如,我发现this

编辑回应评论:

所以从我明白你需要记录的列的子集与新值所覆盖,而其余不受影响?如果是这样,是的,您需要手动更新这些“新”对象上的这几列。

我的印象是,您所谈论的是反映客户对象所有字段的表单,并且意在提供对整个客户记录的编辑访问权。在这种情况下,使用新的上下文并且辛苦地逐个复制所有属性是没有意义的,因为最终结果(所有数据都会覆盖表单值而不考虑年龄)将是相同的。

+0

请参阅我的博文更新,解释为什么我使用CustomerViewModel而不是上下文 – BBauer42 2013-02-22 13:55:44

3

微软参考:

http://msdn.microsoft.com/en-gb/library/cc853327.aspx

他们说;

限制的ObjectContext的

的范围在大多数情况下,你应该创建 using语句内的ObjectContext的实例(使用...最终使用在 Visual Basic中)。

这可以通过确保在代码退出语句块时自动处理与对象上下文关联的 资源 来提高性能。

然而,当 控件绑定到对象上下文管理对象,所述 ObjectContext的实例应被只要所述结合是 需要和设置的手动维护。

有关更多信息,请参阅管理对象服务(实体框架)中的资源。 http://msdn.microsoft.com/en-gb/library/bb896325.aspx

说的是;

在长时间运行的对象上下文中,您必须确保在不再需要时处置上下文 。


StackOverflow的参考:

这个StackOverflow的问题,也有一些有用的答案...

Entity Framework Best Practices In Business Logic?

其中一些人士建议,您推广方面的更高层次并参考它,从而只保留一个单一的上下文。


我十便士的价值:

结束语在using语句的背景下,使垃圾收集清理资源,并防止内存泄漏。

显然,在简单的应用程序中,这并不是什么大问题,但是,如果您有多个屏幕,所有使用大量数据,最终都会遇到麻烦,除非您确定正确配置Context。

因此,我已经采用了类似的方法,你已经提到过,我已经添加了一个AddOrUpdate方法到我的每个存储库,我通过我的新或修改实体,并更新或添加它取决于是否它存在。


更新实体属性:

关于然而更新属性,我使用它使用反射来复制从一个实体向另一个的所有属性的简单功能;

Public Shared Function CopyProperties(Of sourceType As {Class, New}, targetType As {Class, New})(ByVal source As sourceType, ByVal target As targetType) As targetType 
    Dim sourceProperties() As PropertyInfo = source.GetType().GetProperties() 
    Dim targetProperties() As PropertyInfo = GetType(targetType).GetProperties() 

    For Each sourceProp As PropertyInfo In sourceProperties 
     For Each targetProp As PropertyInfo In targetProperties 
      If sourceProp.Name <> targetProp.Name Then Continue For 

      ' Only try to set property when able to read the source and write the target 
      ' 
      ' *** Note: We are checking for Entity Types by Checking for the PropertyType to Start with either a Collection or a Member of the Context Namespace! 
      ' 
      If sourceProp.CanRead And _ 
        targetProp.CanWrite Then 
       ' We want to leave System types alone 
       If sourceProp.PropertyType.FullName.StartsWith("System.Collections") Or (sourceProp.PropertyType.IsClass And _ 
         sourceProp.PropertyType.FullName.StartsWith("System.Collections")) Or sourceProp.PropertyType.FullName.StartsWith("MyContextNameSpace.") Then 
        ' 
        ' Do Not Store 
        ' 
       Else 

        Try 

         targetProp.SetValue(target, sourceProp.GetValue(source, Nothing), Nothing) 

        Catch ex As Exception 

        End Try 

       End If 
      End If 

      Exit For 
     Next 
    Next 

    Return target 
End Function 

我在哪里做类似的事情;

dbColour = Classes.clsHelpers.CopyProperties(Of Colour, Colour)(RecordToSave, dbColour) 

这减少了我需要为每个Repository编写的代码量!

+0

感谢您的全面评论。对于我在水平线下做出的OP更新,您是否看到任何问题?再次感谢。 – BBauer42 2013-02-22 13:59:42