实体框架4.3从Controller的Action参数中附加对象

时间:2012-03-22 18:48:59

标签: c# entity-framework entity-framework-4

我有一种情况,我有一个通过动作从表单加载回MVC控制器的对象。我们不使用FormCollection,而是使用直接使用类的那个。

    [HttpPost]
    public ActionResult AjaxUpdate(Customer customer) { ...

Customer对象包含一个名为customer的对象,它似乎已更新但在上下文中使用SaveDatabase()时根本不起作用。

为了使它有效,我必须在动作中使用:

myDbContext.Customers.Attach(customer) 
//...Code here that set to the customer.SubObject a real object from the database so I am sure that the SubObject contain an id which is valid and the datacontext is aware of it...
myDbContext.Entry(customer).State = EntityState.Modified; 

尽管如此,我有一个例外,关于“存储更新,插入或删除语句影响了意外的行数(0)”,我可以使用以下方法删除:

Database.ObjectContext().Refresh(RefreshMode.ClientWins,customer);

所以,为了解决我的问题,为什么我必须附加+更改状态+调用刷新。是不是有更好的方法来更新包含在其他表中引用的对象的对象。我正在使用Code第一个实体框架(Poco对象)。另外,我不喜欢使用Refresh,因为它隐藏在我的Databasecontext中。

1 个答案:

答案 0 :(得分:2)

我使用EF 4.3.1制作了一个控制台测试项目。代码是我的猜测你的意思是注释行和你的评论下面的问题(但我的猜测可能是错误的,因为该程序不会重现你的错误):

您可以将代码复制到program.cs中并添加对EF 4.3.1的引用:

using System.Data;
using System.Data.Entity;

namespace EFUpdateTest
{
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int SubObjectId { get; set; }
        public SubObject SubObject { get; set; }
    }

    public class SubObject
    {
        public int Id { get; set; }
        public string Something { get; set; }
    }

    public class CustomerContext : DbContext
    {
        public DbSet<Customer> Customers { get; set; }
        public DbSet<SubObject> SubObjects { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int customerId = 0;
            int subObject1Id = 0;
            int subObject2Id = 0;
            using (var ctx = new CustomerContext())
            {
                // Create customer with subobject
                var customer = new Customer { Name = "John" };
                var subObject = new SubObject { Something = "SubObject 1" };
                customer.SubObject = subObject;
                ctx.Customers.Add(customer);

                // Create a second subobject, not related to any customer
                var subObject2 = new SubObject { Something = "SubObject 2" };
                ctx.SubObjects.Add(subObject2);

                ctx.SaveChanges();

                customerId = customer.Id;
                subObject1Id = subObject.Id;
                subObject2Id = subObject2.Id;
            }

            // New context, simulate detached scenario -> MVC action
            using (var ctx = new CustomerContext())
            {
                // Changed customer name
                var customer = new Customer { Id = customerId, Name = "Jim" };
                ctx.Customers.Attach(customer);

                // Changed reference to another subobject
                var subObject2 = ctx.SubObjects.Find(subObject2Id);
                customer.SubObject = subObject2;

                ctx.Entry(customer).State = EntityState.Modified;

                ctx.SaveChanges();
                // No exception here.
            }
        }
    }
}

这无一例外地起作用。问题是:代码中哪些内容可能会导致错误?

修改

对于您在客户类中没有外键属性SubObjectId的注释:如果我在上面的示例程序中删除了该属性,则可以重现该错误。

解决方案是在更改关系之前从数据库加载原始子对象:

// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);

// Load original SubObject from database
ctx.Entry(customer).Reference(c => c.SubObject).Load();

// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;

ctx.Entry(customer).State = EntityState.Modified;

ctx.SaveChanges();
// No exception here.

如果没有外键属性,您将拥有独立关联,要求在更改之前,包含所有引用的对象必须代表数据库中的状态。如果未在SubObject中设置customer的引用,则EF假定数据库中的原始状态是该客户未引用任何子对象。生成的UPDATE语句的SQL包含一个WHERE子句,如下所示:

WHERE [Customers].[Id] = 1 AND [Customers].[SubObject_Id] IS NULL

如果客户在数据库[SubObject_Id]中有子对象 NULL,则不满足条件且不会发生UPDATE(或者发生“意外的行数0” “)。

如果您有外键属性(外键关联),则不会出现此问题:在这种情况下,WHERE子句仅为:

WHERE [Customers].[Id] = 1

因此,SubObjectSubObjectId的原始值无关紧要。您可以保留值null,然后更新UPDATE。

因此,加载原始子对象的替代解决方案是在Customer中引入外键属性:

public int SubObjectId { get; set; }

或者,如果不需要这种关系:

public int? SubObjectId { get; set; }