实体框架和WPF最佳实践

13

是否有必要直接使用上下文来进行工作?例如,我有一个客户数据库,用户可以通过姓名搜索客户,显示列表,选择一个客户,然后编辑该客户的属性。

似乎我应该使用上下文来获取客户列表(映射到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管理客户页面的用户界面将在客户沟通方式(打印、电子邮件、传真)下方包含三个复选框。我不能将每个复选框绑定到该枚举,这没有意义。而且,如果用户单击了该客户,然后起身去吃午饭……上下文会停留数小时,这很糟糕。相反,这是我的思考过程。
最终用户从列表中选择一个客户。我新建一个上下文,找到该客户并返回一个CustomerViewModel,然后关闭上下文(为简单起见,我在这里省略了存储库)。
using(MyContext ctx = new MyContext())
{
    CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId));
}

现在用户可以勾选/取消勾选打印、电子邮件、传真按钮,因为它们绑定到CustomerViewModel中的三个bool属性,该属性还具有Save()方法。就是这样。
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();
        }
    }
}

你看到这个有什么问题吗?
3个回答

17

使用长时间运行的上下文是可以的;您只需要了解其影响。

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

由于一个上下文跟踪的实体很多,可能会减慢DetectChanges的速度。缓解这种情况的一种方法是使用更改跟踪代理。

由于加载实体和保存实体之间的时间可能相当长,因此遇到乐观并发异常的机会比短期上下文更大。当加载和保存实体之间在外部更改实体时,就会发生这些异常。处理这些异常非常简单,但仍需注意。

在WPF中,您可以使用长时间运行的上下文绑定到DbSet.Local属性(例如,context.Customers.Local)。这是一个ObservableCollection,其中包含所有未标记为删除的跟踪实体。

希望这些内容能为你提供更多信息,以帮助你决定应该采取哪种方法来帮助你。

好的答案。感谢您的回复:EF 团队成员的答案在这里总是有价值的。 - JYL

3

Microsoft参考:

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

他们说:

限制ObjectContext的范围

在大多数情况下,您应该在using语句(Visual Basic中的Using…End Using)中创建一个ObjectContext实例。

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

但是,当控件绑定到由对象上下文管理的对象时,应将ObjectContext实例保持为所需的绑定时间,并手动处理它。

有关详细信息,请参阅Object Services(Entity Framework)中的资源管理。 http://msdn.microsoft.com/en-gb/library/bb896325.aspx

这意味着:

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


StackOverflow参考:

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

Entity Framework Best Practices In Business Logic?

一些人建议将上下文提升到更高的级别并从此处引用它,从而保持单个上下文。


我的建议:

将上下文包装在Using语句中,可以使垃圾收集器清理资源,并防止内存泄漏。

显然,在简单的应用程序中,这不是什么问题,但是,如果您有多个屏幕,所有屏幕都使用大量数据,则可能会遇到麻烦,除非您确保正确处理上下文的Dispose。

因此,我采用了与您提到的类似的方法,其中我为每个存储库添加了一个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)

这样做显著减少了每个代码库需要编写的代码量!

感谢您的详细评论。您是否发现我在水平线下更新的内容有任何问题?再次感谢。 - BBauer42

0

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

打开上下文,获取记录,关闭上下文,然后从全新的上下文中复制修改后的属性到对象中,这是多余代码的典范。您应该保留原始上下文,并使用它来执行SaveChanges()。

如果您想处理并发问题,您应该在Google上搜索有关您的实体框架版本的“处理并发”的内容。

例如,我找到了this

针对评论的编辑:

所以,从我的理解来看,您需要覆盖记录的子集列的新值,而其余部分则不受影响?如果是这样,是的,您需要手动更新这些少数列的“新”对象。

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

请看我的帖子更新,解释为什么我使用了CustomerViewModel而不是上下文。 - BBauer42

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接