在实体框架中添加+修改对象时出现奇怪的困难

本文关键字:对象 框架 实体 添加 修改 | 更新日期: 2023-09-27 18:31:26

我在使用代码优先实体框架时遇到了很多麻烦。

我有一个包含两个表的数据库 - UsersCountries. Users有一个外键为 CountryId 的字段。首次将用户添加到数据库时,FK 会正常更新。但是,如果我尝试更新附加到新 DbContext 的用户的 FK,则不会发生任何更改。

我可以在最初创建用户的上下文中更新 FK。此外,我可以在从数据库中检索用户的上下文中更新 FK。但是,在我只是Attach用户的上下文中,FK 字段不会更新,即使正常的 nvarcharint字段确实会更新。

这是我的数据库上下文:

class DataContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Country> Countries { get; set; }
}

这是我的模型:

public class User
{
    public int UserId { get; private set; }
    public string Name { get; set; }
    public virtual Country Country { get; set; }
}
public class Country
{
    public int CountryId { get; private set; }
    public string Name { get; set; }
}

此方法演示了该问题。我将 3 个用户添加到我的数据库中,然后尝试以 3 种不同的方式修改它们 - 在添加用户的原始上下文中,在具有 Attach 的新上下文中,以及在从数据库中检索用户进行修改的新上下文中:

static void Main(string[] args)
{
    Country[] countries;
    using (var dc = new DataContext())
    {
        countries = dc.Countries.ToArray();
    }
    var u = new User();
    // FIRST ROW
    using (var dc = new DataContext())
    {
        u.Name = "First";
        u.Country = countries[0];
        dc.Users.Attach(u);
        dc.Entry(u).State = System.Data.EntityState.Added;
        dc.SaveChanges();
        u.Name = "FirstChanged";
        u.Country = countries[1];
        // defaults to 'added' once u is tracked.
        dc.Entry(countries[1]).State = EntityState.Unchanged; 
        dc.SaveChanges();
    }
    // SECOND ROW
    u = new User();
    using (var dc = new DataContext())
    {
        u.Name = "Second";
        u.Country = countries[0];
        dc.Users.Attach(u);
        dc.Entry(u).State = System.Data.EntityState.Added;
        dc.SaveChanges();
    }
    using (var dc = new DataContext())
    {
        u.Name = "SecondChanged";
        u.Country = countries[1];
        dc.Users.Attach(u);
        dc.Entry(u).State = System.Data.EntityState.Modified;
        dc.SaveChanges();
    }
    // THIRD ROW
    u = new User();
    using (var dc = new DataContext())
    {
        u.Name = "Third";
        u.Country = countries[0];
        dc.Users.Attach(u);
        dc.Entry(u).State = System.Data.EntityState.Added;
        dc.SaveChanges();
    }
    using (var dc = new DataContext())
    {
        u = dc.Users.Find(u.UserId);
        u.Name = "ThirdChanged";
        u.Country = countries[1];
        dc.Entry(countries[1]).State = EntityState.Modified;
        dc.SaveChanges();
    }
}

此应用程序在数据库中生成以下结果:

UserId  Name    Country_CountryId   
------------------------------------------------------------------------
1   FirstChanged    2
2   SecondChanged   1
3   ThirdChanged    2

如您所见,在所有情况下,Name都已成功更新。在第一种情况和第三种情况下,CountryId将更新。但是,在第二种情况下,国家/地区 ID 不会更新。不幸的是,这种情况与我最相关,因为我希望能够持久保存模型以供用户在 DataContext 之外进行编辑。

我不明白为什么会发生这种情况 - 如果Name字段没有更新我可以理解,但因为它只是CountryId我无法弄清楚为什么。在这两种情况下,都会跟踪Country对象并将其标记为Unchanged 。我不明白为什么只有第二种情况不起作用。

请帮我弄清楚这里发生了什么。我能想到的唯一解决方案是在需要修改时使用第三种情况,以便在保存时将数据从持久化的对象复制到从保存的数据库中检索的新对象......但显然这是一个愚蠢的解决方案。

下面,我将包含一个演示此问题的控制台应用程序。若要运行它,需要使用 NuGet(或类似)和如下所示的连接字符串检索实体框架:

<add name="DataContext" connectionString="DataSource=.'DataContext.sdf" providerName="System.Data.SqlServerCe.4.0" />

法典:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Country[] countries;
            using (var dc = new DataContext())
            {
                countries = dc.Countries.ToArray();
            }
            var u = new User();
            // FIRST ROW
            using (var dc = new DataContext())
            {
                u.Name = "First";
                u.Country = countries[0];
                dc.Users.Attach(u);
                dc.Entry(u).State = System.Data.EntityState.Added;
                dc.SaveChanges();
                u.Name = "FirstChanged";
                u.Country = countries[1];
                // defaults to 'added' once u is tracked.
                dc.Entry(countries[1]).State = EntityState.Unchanged;
                dc.SaveChanges();
            }
            // SECOND ROW
            u = new User();
            using (var dc = new DataContext())
            {
                u.Name = "Second";
                u.Country = countries[0];
                dc.Users.Attach(u);
                dc.Entry(u).State = System.Data.EntityState.Added;
                dc.SaveChanges();
            }
            using (var dc = new DataContext())
            {
                u.Name = "SecondChanged";
                u.Country = countries[1];
                dc.Users.Attach(u);
                dc.Entry(u).State = System.Data.EntityState.Modified;
                dc.SaveChanges();
            }
            // THIRD ROW
            u = new User();
            using (var dc = new DataContext())
            {
                u.Name = "Third";
                u.Country = countries[0];
                dc.Users.Attach(u);
                dc.Entry(u).State = System.Data.EntityState.Added;
                dc.SaveChanges();
            }
            using (var dc = new DataContext())
            {
                u = dc.Users.Find(u.UserId);
                u.Name = "ThirdChanged";
                u.Country = countries[1];
                dc.Entry(countries[1]).State = EntityState.Modified;
                dc.SaveChanges();
            }
        }
    }
    class DataContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Country> Countries { get; set; }
        public DataContext()
        {
            Database.SetInitializer<DataContext>(new ModelInitializer());
            Database.Initialize(false);
        }
    }
    class ModelInitializer : DropCreateDatabaseAlways<DataContext>
    {
        protected override void Seed(DataContext context)
        {
            var jsds = new List<Country>
                {
                    new Country("United Kingdom"),
                    new Country("United States Of America")
                };
            jsds.ForEach(jsd => context.Countries.Add(jsd));
        }
    }
    public class User
    {
        public int UserId { get; private set; }
        public string Name { get; set; }
        public virtual Country Country { get; set; }
    }
    public class Country
    {
        public int CountryId { get; private set; }
        public string Name { get; set; }
        public Country() { }
        public Country(string name)
        {
            Name = name;
        }
    }
}

感谢您查看此问题 - 任何帮助将不胜感激。我在尝试修复它的第二天,我慢慢发疯了......

在实体框架中添加+修改对象时出现奇怪的困难

这是一个基于外键关联与独立关联之间差异的棘手问题。如果使用外键关联(User实体中将具有CountryId属性),则第二种情况也可以使用,但您使用的是独立关联,其中关联本身具有自己的状态,必须设置为Added才能使其工作。 DbContext不提供 API 来更改关联的状态,因此您必须使用 ObjectContext

u = new User();
using (var dc = new DataContext())
{
    u.Name = "Second";
    u.Country = countries[0];
    dc.Users.Attach(u);
    dc.Entry(u).State = System.Data.EntityState.Added;
    dc.SaveChanges();
}
using (var dc = new DataContext())
{
    u.Name = "SecondChanged";
    u.Country = countries[1];
    dc.Users.Attach(u);
    ObjectContext oc = ((IObjectContextAdapter)dc).ObjectContext;
    oc.ObjectStateManager.ChangeObjectState(u, EntityState.Modified);
    oc.ObjectStateManager.ChangeRelationshipState(u, countries[1], x => x.Country, EntityState.Added);
    dc.SaveChanges();
}

当你使用独立的关联时,你会发现很多复杂的东西。