实体框架4.3从控制器的动作的参数附加对象

本文关键字:参数 对象 实体 控制器 框架 | 更新日期: 2023-09-27 18:28:54

我有一种情况,我有一个对象通过一个操作从窗体加载回MVC控制器。我们不使用FormCollection,而是直接使用类的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; 

尽管如此,我还是遇到了一个关于"Store update,insert,or delete语句影响了意外的行数(0)"的异常,我可以使用删除它

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

因此,为了解决我的问题,为什么我必须附加+更改状态+调用刷新。没有更好的方法来更新包含在其他表中引用的对象的对象吗。我使用的是代码优先实体框架(Poco对象)。另外,我不喜欢使用Refresh,因为它在我的数据库上下文中是隐藏的。

实体框架4.3从控制器的动作的参数附加对象

我用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.
            }
        }
    }
}

这毫无例外。问题是:您的代码中有什么不同可能导致错误?

编辑

对于您在customer类中没有外键属性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.

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

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

如果客户在DB中有一个子对象[SubObject_Id]而不是NULL,则条件不满足,并且不会发生UPDATE(或发生"意外行数0")。

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

WHERE [Customers].[Id] = 1

因此,SubObjectSubObjectId的原始值是多少并不重要。您可以保留值null,但UPDATE仍然有效。

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

public int SubObjectId { get; set; }

或者,如果不需要关系:

public int? SubObjectId { get; set; }