【问题标题】:Entity Framework and WPF best practices实体框架和 WPF 最佳实践
【发布时间】:2013-02-21 21:53:10
【问题描述】:

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

看来我应该使用上下文来获取客户列表(映射到 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 中的三个 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();
        }
    }
}

你觉得这有什么问题吗?

【问题讨论】:

    标签: wpf entity-framework


    【解决方案1】:

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

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

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

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

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

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

    【讨论】:

    • 很好的答案。感谢您的回复:EF 团队成员的回答在这里总是很有价值。
    【解决方案2】:

    微软参考:

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

    他们说;

    限制ObjectContext的范围

    在大多数情况下,您应该创建 using 语句中的 ObjectContext 实例(Using...End Using in 视觉基本)。

    这可以通过确保 与对象上下文关联的资源被释放 当代码退出语句块时自动。

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

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

    说什么;

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


    StackOverflow 参考:

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

    Entity Framework Best Practices In Business Logic?

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


    我的十便士值:

    将上下文包装在 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)
    

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

    【讨论】:

    • 感谢完整的 cmets。您认为我在水平规则下对 OP 所做的更新有什么问题吗?再次感谢。
    【解决方案3】:

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

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

    如果您希望处理并发问题,您应该在 Google 上搜索您的实体框架版本的“处理并发”。

    作为一个例子,我找到了this。

    根据评论进行编辑:

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

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

    【讨论】:

    • 查看我的帖子更新,解释为什么我使用 CustomerViewModel 而不是上下文
    猜你喜欢
    • 1970-01-01
    • 2010-09-14
    • 2023-03-28
    • 2010-11-11
    • 2014-04-01
    • 2022-10-15
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多