实体框架和WPF最佳做法 [英] Entity Framework and WPF best practices

查看:72
本文介绍了实体框架和WPF最佳做法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



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



我的问题是,为什么不直接在上下文中直接使用它呢?我读过使用具有较长生命周期范围的相同上下文非常糟糕,并且不可避免地会引起问题。我的假设是,如果该应用程序仅由一个人使用,则我可以保持上下文开放并执行所有操作。但是,如果会有很多用户,我想保持一个简洁的工作单元,从而根据每个请求打开和关闭上下文。



有什么建议吗?






@PGallagher-感谢您提供详尽的答案。

@Brice-您的输入是



但是,@ Manos D.冗余代码的epitome注释使我有些担心。让我来看一个例子。可以说我将客户存储在数据库中,而我的客户属性之一是CommunicationMethod。

  [Flags] 
公共枚举CommunicationMethod
{
None = 0,
打印= 1,
电子邮件= 2,
传真= 4
}

WPF中我的客户管理页面的UI将在客户交流方法(打印,电子邮件,传真)下包含三个复选框。我无法将每个复选框都绑定到该枚举,这没有任何意义。另外,如果用户单击该客户,起身去吃午餐...上下文在那儿坐了好几个小时,那是不好的。相反,这是我的思考过程。



最终用户从列表中选择一个客户。我新建了一个上下文,找到该客户并返回一个CustomerViewModel,然后关闭了该上下文(为简化起见,我将存储库省略了。)。

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

现在,用户可以在选中,取消选中打印,电子邮件,传真按钮时绑定到CustomerViewModel中的三个bool属性,该属性也具有Save()方法。

 公共类CustomerViewModel:ViewModelBase 
{
Customer _customer;

public CustomerViewModel(客户客户)
{
_customer =客户;
}


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

如果(值)
_customer.CommunicationMethod | = CommunicationMethod.Email;
else
_customer.CommunicationMethod& =〜CommunicationMethod.Email;
}
}
公共布尔CommunicateViaFax
{
get {return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax); }
set
{
if(value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax))return;

如果(值)
_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))返回;

如果(值)
_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();
}
}
}

您是否发现任何错误这个?

解决方案

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



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

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



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

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



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


Is it ever a good idea to work directly with the context? For example, say I have a database of customers and a user can search them by name, display a list, choose one, then edit that customer's properties.

It seems I should use the context to get a list of customers (mapped to POCOs or CustomerViewModels) and then immediately close the context. Then, when the user selects one of the CustomerViewModels in the list the customer properties section of the UI populates.

Next they can change the name, type, website address, company size, etc. Upon hitting a save button, I then open a new context, use the ID from the CustomerViewModel to retrieve that customer record, and update each of its properties. Finally, I call SaveChanges() and close the context. This is a LOT OF WORK.

My question is why not just work directly with the context leaving it open throughout? I have read using the same context with a long lifetime scope is very bad and will inevitably cause problems. My assumption is if the application will only be used by ONE person I can leave the context open and do everything. However, if there will be many users, I want to maintain a concise unit of work and thus open and close the context on a per request basis.

Any suggestions? Thanks.


@PGallagher - Thanks for the thorough answer.
@Brice - your input is helpful as well

However, @Manos D. the 'epitome of redundant code' comment concerns me a bit. Let me go through an example. Lets say I'm storing customers in a database and one of my customer properties is CommunicationMethod.

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

The UI for my manage customers page in WPF will contain three check boxes under the customer communication method (Print, Email, Fax). I can't bind each checkbox to that enum, it doesn't make sense. Also, what if the user clicked that customer, gets up and goes to lunch... the context sits there for hours which is bad. Instead, this is my thought process.

End user chooses a customer from the list. I new up a context, find that customer and return a CustomerViewModel, then the context is closed (I've left repositories out for simplicity here).

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

Now the user can check/uncheck the Print, Email, Fax buttons as they are bound to three bool properties in the CustomerViewModel, which also has a Save() method. Here goes.

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();
        }
    }
}

Do you see anything wrong with this?

解决方案

It is OK to use a long-running context; you just need to be aware of the implications.

A context represents a unit of work. Whenever you call SaveChanges, all the pending changes to the entities being tracked will be saved to the database. Because of this, you'll need to scope each context to what makes sense. For example, if you have a tab to manage customers and another to manage products, you might use one context for each so that when a users clicks save on the customer tab, all of the changes they made to products are not also saved.

Having a lot of entities tracked by a context could also slow down DetectChanges. One way to mitigate this is by using change tracking proxies.

Since the time between loading an entity and saving that entity could be quite long, the chance of hitting an optimistic concurrency exception is greater than with short-lived contexts. These exceptions occur when an entity is changed externally between loading and saving it. Handling these exceptions is pretty straightforward, but it's still something to be aware of.

One cool thing you can do with long-lived contexts in WPF is bind to the DbSet.Local property (e.g. context.Customers.Local). this is an ObservableCollection that contains all of the tracked entities that are not marked for deletion.

Hopefully this gives you a bit more information to help you decide which approach to help.

这篇关于实体框架和WPF最佳做法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆