保存更改后属性未更新(EF数据库优先)

13

首先,我想说我已经阅读了相关文章(特别是EF 4.1 SaveChanges未更新导航或引用属性Entity Framework Code First - 为什么不能以这种方式更新复杂属性?Entity Framework 4.1 RC(Code First)-实体不通过关联更新)。

然而,我无法解决我的问题。我对Entity Framework非常陌生,所以我想我一定误解了那些文章中的答案。无论如何,如果有人能帮助我理解,我会非常感激,因为我很困扰。

我有两个表:

  • Person
  • Item具有可为空的PersonIdType

一个物品可以有一个所有者,也可以没有。因此,Person具有一个Items属性,它是Item的IEnumerable。

一个人只能有一个与类型相同的Item。如果这个人想要更改,他可以用他的物品中相同类型的任何其他物品替换他当前的物品:

public class MyService
{
    private PersonRepo personRepo = new PersonRepo();
    private ItemRepo itemRepo = new ItemRepo();

    public void SwitchItems(Person person, Guid newItemId)
    {
        using (var uof = new UnitOfWork())
        {
            // Get the entities
            Item newItem = itemRepo.Get(newItemId);
            Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)

            // Update the values
            newItem.PersonId = person.Id;
            oldItem.PersonId = null;

            // Add or update entities
            itemRepo.AddOrUpdate(oldItem);
            itemRepo.AddOrUpdate(newItem);
            personRepo.AddOrUpdate(person);

            uof.Commit(); // only does a SaveChanges()
        }
    }
}

以下是仓库结构和AddOrUpdate方法:

public class PersonRepo : RepositoryBase<Person>
{
    ...
}

public class RepositoryBase<TObject> where TObject : class, IEntity
{
    protected MyEntities entities
    {
        get { return UnitOfWork.Current.Context; }
    }

    public virtual void AddOrUpdate(TObject entity)
    {
        if (entity != null)
        {
            var entry = entities.Entry<IEntity>(entity);

            if (Exists(entity.Id))
            {
                if (entry.State == EntityState.Detached)
                {
                    var set = entities.Set<TObject>();
                    var currentEntry = set.Find(entity.Id);
                    if (currentEntry != null)
                    {
                        var attachedEntry = entities.Entry(currentEntry);
                        attachedEntry.CurrentValues.SetValues(entity);
                    }
                    else
                    {
                        set.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
                else
                    entry.State = EntityState.Modified;
            }
            else
            {
                entry.State = EntityState.Added;
            }
        }
    }
}

这个方法很有效,旧的和新的项目的PersonId属性在数据库中被正确更新。 然而,在SaveChanges()之后,如果我检查person.Items,旧的项目仍然出现,而不是新的项目,为了更新页面控件的值,我需要它是正确的。

尽管我读过相同问题的帖子,但我无法解决它... 我尝试了很多东西,特别是调用entities.Entry(person).Collection(p => p.Items).Load(),但每次尝试都会出现异常。

如果有人有任何想法,请随时提出,如果需要,我可以添加更多的代码。

非常感谢!

编辑:UnitOfWork

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;

public class UnitOfWork : IDisposable
{
    private const string _httpContextKey = "_unitOfWork";
    private MyEntities _dbContext;

    public static UnitOfWork Current
    {
        get { return (UnitOfWork)HttpContext.Current.Items[_httpContextKey]; }
    }

    public UnitOfWork()
    {
        HttpContext.Current.Items[_httpContextKey] = this;
    }

    public MyEntities Context
    {
        get
        {
            if (_dbContext == null)
                _dbContext = new MyEntities();

            return _dbContext;
        }
    }

    public void Commit()
    {
        _dbContext.SaveChanges();
    }

    public void Dispose()
    {
        if (_dbContext != null)
            _dbContext.Dispose();
    }
}

两种有效的解决方案

解决方案一(在SaveChanges后从上下文重新加载)

public partial class MyPage
{
    private MyService service;
    private Person person;

    protected void Page_Load(object sender, EventArgs e)
    {
        service = new MyService();
        person = service.GetCurrentPerson(Request.QueryString["id"]);
        ...
    }

    protected void SelectNewItem(object sender, EventArgs e)
    {
        Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);

        service.SelectNewItem(person, itemId);

        UpdatePage();
    }

    private void UpdatePage()
    {
        if (person != null)
            person = service.GetCurrentPerson(Request.QueryString["id"]);

        // Update controls values using person's properties here
    }
}

public class MyService
{
    private PersonRepo personRepo = new PersonRepo();
    private ItemRepo itemRepo = new ItemRepo();

    public void SwitchItems(Person person, Guid newItemId)
    {
        using (var uof = new UnitOfWork())
        {
            // Get the entities
            Item newItem = itemRepo.Get(newItemId);
            Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)

            // Update the values
            newItem.PersonId = person.Id;
            oldItem.PersonId = null;

            // Add or update entities
            itemRepo.AddOrUpdate(oldItem);
            itemRepo.AddOrUpdate(newItem);
            personRepo.AddOrUpdate(person);

            uof.Commit(); // only does a SaveChanges()
        }
    }
}

解决方案2(更新数据库和属性)

public partial class MyPage
{
    private MyService service;
    private Person person;

    protected void Page_Load(object sender, EventArgs e)
    {
        service = new MyService();
        person = service.GetCurrentPerson(Request.QueryString["id"]);
        ...
    }

    protected void SelectNewItem(object sender, EventArgs e)
    {
        Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);

        service.SelectNewItem(person, itemId);

        UpdatePage();
    }

    private void UpdatePage()
    {
        // Update controls values using person's properties here
    }
}

public class MyService
{
    private PersonRepo personRepo = new PersonRepo();
    private ItemRepo itemRepo = new ItemRepo();

    public void SwitchItems(Person person, Guid newItemId)
    {
        using (var uof = new UnitOfWork())
        {
            // Get the entities
            Item newItem = itemRepo.Get(newItemId);
            Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)

            // Update the values
            newItem.PersonId = person.Id;
            oldItem.PersonId = null;
            person.Items.Remove(oldItem);
            person.Items.Add(newItem);

            // Add or update entities
            itemRepo.AddOrUpdate(oldItem);
            itemRepo.AddOrUpdate(newItem);
            personRepo.AddOrUpdate(person);

            uof.Commit(); // only does a SaveChanges()
        }
    }
}

我不确定更改实体上的外键是否会触发EF将其放置在正确的集合中。如果您启动一个新的上下文并从数据库重新加载,会发生什么? - user2697817
@jlvaquero:当然,我已经编辑了我的第一篇帖子,并加入了UnitOfWork代码 :) - Flash_Back
你的意思是使用 person.Items.Remove(oldItem); person.Items.Add(newItem) 代替 newItem.PersonId = person.Id; oldItem.PersonId = null; 吗? - Flash_Back
是的,如果外键可为空,在调用SaveChanges时它将被设置为null。否则,您将不得不添加到一个新集合或将其删除。 - user2697817
同意user2697817的观点。你正在绕过ORM的精神,试图在低级别上管理你的实体和外键。 - jlvaquero
显示剩余3条评论
2个回答

8

.SaveChanges()方法之后,刷新上下文以确保您拥有最新的数据库更改如何?将要刷新的实体传递给上下文并在其上调用Refresh即可:

((IObjectContextAdapter)_dbContext).ObjectContext.Refresh(RefreshMode.StoreWins, entityPassed);

或者保留 Commit() 方法不变,并采用更具动态性的方法,例如:

var changedEntities = (from item in context.ObjectStateManager.GetObjectStateEntries(
                                        EntityState.Added
                                       | EntityState.Deleted
                                       | EntityState.Modified
                                       | EntityState.Unchanged)
                              where item.EntityKey != null
                              select item.Entity);

    context.Refresh(RefreshMode.StoreWins, changedEntities);
RefreshMode.StoreWins 表示数据库(存储)优先,会覆盖客户端(内存中)的更改。
如果 Refresh 方法不起作用,您可以考虑以下方法:
public void RefreshEntity(T entity)
{
  _dbContext.Entry<T>(entity).Reload();
}

如果其他方法都不起作用,那么保持简单并在每个事务完成后Dispose您的DbContext(在调用SaveChanges()之后)。然后,如果您需要在提交后使用结果,请将其视为新事务,并实例化一个全新的DbContext再次加载必要的数据。


1
非常感谢,我会立即尝试。然而,我的“_dbcontext”没有“Refresh()”方法,也没有“ObjectStateManager”。我不知道这是否重要,但“MyEntities”类型是从“DbContext”继承的。 - Flash_Back
看一下修改后的答案。现在可以访问你的 Refresh() 方法了吗? - Niels Filter
是的,那个方法很管用,谢谢 :) 我在我的第一篇帖子中编辑了我尝试过的方法(在Commit()方法中的SaveChanges()之后添加了刷新),不幸的是这似乎不起作用,数据库是正确的,但是person.Items没有更新。我做错了什么吗? - Flash_Back
在调用 Refresh 时,请确保 personchangedEntities 集合中。或者,您可以尝试重新加载特定的实体?请参见编辑后的答案。 - Niels Filter
uof.Commit()之后,只需对person实体调用_dbContext.Entry<T>(entity).Reload();,我就会收到一个异常,说该实体不存在于上下文中,因此我将其作为第一篇帖子中的新编辑。不再有异常,但是相同的行为:直到在Page_Load中进行以下数据库访问之前,person.Items仍然没有改变。我感到非常绝望:( - Flash_Back
显示剩余8条评论

0

以交易为例。 它运行良好。

 public class UnitOfWork : IUnitOfWork
 {

    public readonly DatabaseContext _context;
    private readonly IDbTransaction _transaction;
    private readonly ObjectContext _objectContext;

      public UnitOfWork(DatabaseContext context)
      {
        _context = context as DatabaseContext ?? new DatabaseContext ();
        this._objectContext = ((IObjectContextAdapter)this._context).ObjectContext;
        if (this._objectContext.Connection.State != ConnectionState.Open)
        {
            this._objectContext.Connection.Open();
            this._transaction = _objectContext.Connection.BeginTransaction();
        }
      }             

    public int Complete()
    {
        int result = 0;
        try
        {
            result = _context.SaveChanges();
            this._transaction.Commit();
        }
        catch (Exception ex)
        {
            Rollback();
        }
        return result;
    }
    private void Rollback()
    {
        this._transaction.Rollback();

        foreach (var entry in this._context.ChangeTracker.Entries())
        {
            switch (entry.State)
            {
                case System.Data.Entity.EntityState.Modified:
                    entry.State = System.Data.Entity.EntityState.Unchanged;
                    break;
                case System.Data.Entity.EntityState.Added:
                    entry.State = System.Data.Entity.EntityState.Detached;
                    break;
                case System.Data.Entity.EntityState.Deleted:
                    entry.State = System.Data.Entity.EntityState.Unchanged;
                    break;
            }
        }
    }
    public void Dispose()
    {
        if (this._objectContext.Connection.State == ConnectionState.Open)
        {
            this._objectContext.Connection.Close();
        }
        _context.Dispose();
    }
}

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