【问题标题】:AddOrUpdate throws "A foreign key value cannot be inserted" with one to many relationshipAddOrUpdate 抛出具有一对多关系的“无法插入外键值”
【发布时间】:2013-05-14 17:50:50
【问题描述】:

我的应用程序面向 .NET 4.5 并使用 EntityFramework 5.0、Sql Server Compact 4.0。

我正在尝试在我的数据库中添加一些实体,但它一直在抛出:

“System.Data.SqlServerCe.SqlCeException:无法插入外键值,因为对应的主键值不存在。[外键约束名称= FK_dbo.User_dbo.Account_AccountKey]”

这是我的域实体的简化版本:

public class Account
{
    public int AccountKey { get; set; }
    public string Name { get; set; }

    public ICollection<User> Users { get; set; }
}

internal class AccountMap : EntityTypeConfiguration<Account>
{
    public AccountMap()
    {
        this.HasKey(e => e.AccountKey);
        this.Property(e => e.AccountKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(e => e.Name).IsRequired();
    }
}


public class User
{
    public int UserKey { get; set; }
    public string Name { get; set; }

    public Account Account { get; set; }
    public int AccountKey { get; set; }
}

internal class UserMap : EntityTypeConfiguration<User>
{
    public UserMap()
    {
        this.HasKey(e => e.UserKey);
        this.Property(e => e.UserKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.Property(e => e.Name).IsRequired();


        this.HasRequired(e => e.Account)
            .WithMany(e => e.Users)
            .HasForeignKey(e => e.AccountKey);
    }
}

public class TestContext : DbContext
{
    public TestContext()
    {
        this.Configuration.LazyLoadingEnabled = false;
    }

    public DbSet<User> Users { get; set; }

    public DbSet<Account> Accounts { get; set; }



    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();

        modelBuilder.LoadConfigurations();
    }

}

连接字符串:

<connectionStrings>
    <add name="TestContext" connectionString="Data Source=|DataDirectory|\TestDb.sdf;" providerName="System.Data.SqlServerCe.4.0" />
  </connectionStrings>

这是一个显示问题的示例应用程序:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TestContext>());
            using (var context = new TestContext())
                context.Database.Initialize(false);

            for (int i = 0; i < 2; i++ )
                using (var context = new TestContext())
                {
                    var account1 = new Account()
                    {
                        Name = "Account1"
                    };

                    var user1 = new User()
                    {
                        Name = "User1",
                        Account = account1
                    };

                    context.Accounts.AddOrUpdate(
                        e => e.Name,
                        account1
                    );

                    context.Users.AddOrUpdate(
                        e => e.Name,
                        user1
                    );

                    context.SaveChanges();

                    Console.WriteLine("\nChanges saved.");
                }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }

        Console.WriteLine("\nPress any key to exit...");
        Console.ReadLine();
    }
}

Account 类与 User 类是一对多的关系。我的种子方法尝试使用默认用户初始化默认帐户。有人会认为这是 AddOrUpdate 方法的一种常见用法,但在这种情况下似乎不起作用。如果我只是添加两次相同的帐户,没有用户,它可以正常工作。

有人能看出我遗漏了什么吗?

这种简单的关系有问题吗?

AddOrUpdate 方法是否设计用于此类情况?

如果不是,那么完成这种播种的正确方法是什么?

【问题讨论】:

    标签: entity-framework sql-server-ce entity-framework-5


    【解决方案1】:

    这应该会让你走上正轨:

    using (var context = new TestContext())
    {
        // with 1:many we don't need to specify PK when seeding, EF will pick this up
        var accounts = new List<Account>
        {
            // new a list of type User for each account
            new Account() { Name = "Account1", Users = new List<User>() },
            new Account() { Name = "Account2", Users = new List<User>() }
        };
        accounts.ForEach(a => context.Accounts.AddOrUpdate(a));
        context.SaveChanges();
    
        var users = new List<User>
        {
            // set the account key for each User
            new User() { Name = "User1", AccountKey = 1 },
            new User() { Name = "User2", AccountKey = 1 },
            new User() { Name = "User3", AccountKey = 2 },
            new User() { Name = "User4", AccountKey = 2 }
        };
        users.ForEach(u => context.Users.AddOrUpdate(u));
        context.SaveChanges();
    
        // add users to list of type user in an account
        accounts[0].Users.Add(users[0]);
        accounts[0].Users.Add(users[1]);
        accounts[1].Users.Add(users[2]);
        accounts[1].Users.Add(users[3]);
        context.SaveChanges();
    }
    

    编辑

    在执行此操作之前,您可能希望删除并重新创建您的数据库,因为如果您多次为数据库设置种子并“添加两次相同的帐户,没有用户”,那么帐户表中将会有很多行并且在用户表中没有。

    您还可以在User.Account 中添加以下注解:

    [ForeignKey("AccountKey")]
    public Account Account { get; set; }
    

    另外,您的导航属性都不是虚拟的原因是什么?

    【讨论】:

    • 我没有使用属性,因为我更喜欢配置sintax而不是它们,你建议的行为应该与UserMap中的“.HasForeignKey(e => e.AccountKey)”基本相同。我的导航属性不是虚拟的,因为我不需要/不想支持延迟加载。
    • 很公平,注释看起来不那么漂亮。我同意使用 Fluent API 应该有同样的效果——你总是可以尝试使用注释而不是 Fluent API 来查看你的 Fluent 配置是否导致了问题。当您尝试使用我提供的示例时,您是否遇到了同样的错误?
    • 您的示例不起作用,因为您否认 AddOrUpdate 方法的目的,使其通过 id 验证实体存在(未指定标识属性时的默认方法),您没有没有。在我的示例中,您的方法将创建四个帐户,两个名为“Account1”,另外两个名为“Account2”。此外,您假设创建了帐户密钥 (1, 2),但您不能这样做,因为种子可能​​在现有数据库上的迁移中使用。
    • 我的例子确实有效,我自己用过。它非常适合首次为数据库播种,从您在问题中提供的信息量来看,这似乎是您想要做的。我的示例不会创建四个帐户,您是否在循环中看到它会迭代两次?出于说明目的,我添加了一个额外的帐户。请注意我所说的“这应该让你走上正轨”,这意味着答案应该作为帮助你解决问题的基础。
    • 是的,@MattSull,我同意你的观点:你的例子确实适用于首次播种。但是,如果是这种情况,就不需要 AddOrUpdate 方法,一个简单的 Add 就足够了。这就是我在示例中包含“循环”的原因,以表示种子方法将在已填充的数据库中运行。
    【解决方案2】:

    我已经通过以下修改使其工作:

    var account1 = new Account()
    {
        Name = "Account1"
    };
    
    var user1 = new User()
    {
        Name = "User1",
        //Account = account1
    };
    
    account1.Users = new List<User>(new[]{user1});
    
    context.Accounts.AddOrUpdate(
        e => e.Name,
        account1
    );
    
    context.SaveChanges();
    

    但是,我不知道为什么会这样。我仍然认为第一次尝试是有意义的,应该没有问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-29
      • 1970-01-01
      • 2013-01-12
      • 1970-01-01
      • 1970-01-01
      • 2017-11-19
      相关资源
      最近更新 更多