13

直接使用上下文是一个好主意吗?例如,假设我有一个客户数据库,用户可以按名称搜索他们,显示一个列表,选择一个,然后编辑该客户的属性。

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

接下来,他们可以更改名称、类型、网站地址、公司规模等。点击保存按钮后,我打开一个新上下文,使用 IDCustomerViewModel检索该客户记录,并更新其每个属性。最后,我调用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));
}

现在用户可以选中/取消选中 Print、Email、Fax 按钮,因为它们绑定到 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();
        }
    }
}

你觉得这有什么问题吗?

4

3 回答 3

17

可以使用长时间运行的上下文;您只需要了解其中的含义。

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

上下文跟踪大量实体也会减慢 DetectChanges。减轻这种情况的一种方法是使用更改跟踪代理。

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

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

希望这可以为您提供更多信息,以帮助您决定使用哪种方法来提供帮助。

于 2013-02-21T23:45:38.947 回答
3

微软参考:

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

他们说;

限制 ObjectContext 的范围

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

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

但是,当控件绑定到由对象上下文管理的对象时,只要需要绑定并手动处理,就应该维护 ObjectContext 实例。

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

其中说;

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


StackOverflow 参考:

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

业务逻辑中的实体框架最佳实践?

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


我的十便士值:

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

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

因此,我采用了与您提到的方法类似的方法,我在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)

这当然减少了我需要为每个存储库编写的代码量!

于 2013-02-22T00:06:01.347 回答
0

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

打开上下文,获取记录,关闭上下文,然后将修改的属性从全新的上下文复制到对象是冗余代码的缩影。您应该不理会原始上下文并使用它来执行 SaveChanges()。

如果您正在寻找处理并发问题,您应该为您的实体框架版本进行有关“处理并发”的谷歌搜索。

作为一个例子,我发现了这个

编辑以回应评论:

因此,据我了解,您需要用新值覆盖记录的一部分列,而其余部分不受影响?如果是这样,是的,您需要手动更新“新”对象上的这几列。

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

于 2013-02-21T23:50:21.500 回答