EF 4.1 Code First - 对象图中的重复实体导致异常

P2l*_*P2l 7 entity-framework ef-code-first

尝试保存我的实体时,我收到以下异常:

"AcceptChanges无法继续,因为对象的键值与ObjectStateManager中的另一个对象冲突.在调用AcceptChanges之前,请确保键值是唯一的."

我正在创建一个3层应用程序,其中数据访问层使用EF Code First,并且客户端使用WCF调用中间层.因此,在客户端上构建实体时,我无法让上下文跟踪实体状态.

在某些情况下,我发现在对象图中包含两次相同的实体.在这种情况下,当我尝试设置副本的实体状态时,它会失败.

例如,我有以下实体:Customer Country Curreny

  1. 从客户端我创建一个Customer的新实例.然后,我进行服务调用以获取Country实例并将其分配给Customer.Country实例具有关联的Currency.
  2. 然后,用户可以将货币与客户相关联.他们可能会选择与国家相关的相同货币.
  3. 我打了另一个服务电话来得到这个.因此,在这个阶段,我们可能有两个相同货币的独立实例.

所以我最终得到的是对象图中同一个实体的两个实例.

然后保存实体(在我的服务中)我需要告诉EF两个货币实体都没有被修改(如果我不这样做,我会得到重复).问题是我得到了上面的例外.

保存如果我将Country实例上的Currency实例设置为null,它解决了问题,但我觉得代码变得越来越混乱(由于这和其他WCF相关的EF解决方法我不得不放到位).

有没有关于如何以更好的方式解决这个问题的建议?

非常感谢您提前提供任何帮助.这是代码:

using System;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;

namespace OneToManyWithDefault
{

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Country Country { get; set; }
        public Currency Currency { get; set; }
        public byte[] TimeStamp { get; set; }
    }

    public class Country
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Currency Currency { get; set; }
        public byte[] TimeStamp { get; set; }
    }

    public class Currency
    {
        public int Id { get; set; }
        public string Symbol { get; set; }
        public byte[] TimeStamp { get; set; }
    }


    public class MyContext
        : DbContext
    {
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Currency> Currency { get; set; }
        public DbSet<Country> Country { get; set; }

        public MyContext(string connectionString)
            : base(connectionString)
        {
            Configuration.LazyLoadingEnabled = false;
            Configuration.ProxyCreationEnabled = false;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new CustomerConfiguration());
            modelBuilder.Configurations.Add(new CountryConfiguration());
            modelBuilder.Configurations.Add(new CurrencyConfiguration());
            base.OnModelCreating(modelBuilder);
        }
    }

    public class CustomerConfiguration
        : EntityTypeConfiguration<Customer>
    {
        public CustomerConfiguration()
            : base()
        {
            HasKey(p => p.Id);
            Property(p => p.Id)
                .HasColumnName("Id")
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .IsRequired();
            Property(p => p.TimeStamp)
                .HasColumnName("TimeStamp")
                .IsRowVersion();

            ToTable("Customers");
        }
    }

    public class CountryConfiguration
        : EntityTypeConfiguration<Country>
    {
        public CountryConfiguration()
            : base()
        {
            HasKey(p => p.Id);
            Property(p => p.Id)
                .HasColumnName("Id")
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .IsRequired();
            Property(p => p.TimeStamp)
                .HasColumnName("TimeStamp")
                .IsRowVersion();

            ToTable("Countries");
        }
    }

    public class CurrencyConfiguration
        : EntityTypeConfiguration<Currency>
    {
        public CurrencyConfiguration()
            : base()
        {
            HasKey(p => p.Id);
            Property(p => p.Id)
                .HasColumnName("Id")
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
                .IsRequired();
            Property(p => p.TimeStamp)
                .HasColumnName("TimeStamp")
                .IsRowVersion();

            ToTable("Currencies");
        }
    }

    class Program
    {
        private const string ConnectionString =
            @"Server=.\sql2005;Database=DuplicateEntities;integrated security=SSPI;";

        static void Main(string[] args)
        {
            // Seed the database
            MyContext context1 = new MyContext(ConnectionString);

            Currency currency = new Currency();
            currency.Symbol = "GBP";
            context1.Currency.Add(currency);

            Currency currency2 = new Currency();
            currency2.Symbol = "USD";
            context1.Currency.Add(currency2);

            Country country = new Country();
            country.Name = "UK";
            country.Currency = currency;
            context1.Country.Add(country);

            context1.SaveChanges();

            // Now add a new customer
            Customer customer = new Customer();
            customer.Name = "Customer1";

            // Assign a country to the customer
            // Create a new context (to simulate making service calls over WCF)
            MyContext context2 = new MyContext(ConnectionString);
            var countries = from c in context2.Country.Include(c => c.Currency) where c.Name == "UK" select c;
            customer.Country = countries.First();

            // Assign a currency to the customer
            // Again create a new context (to simulate making service calls over WCF)
            MyContext context3 = new MyContext(ConnectionString);
            customer.Currency = context3.Currency.First(e => e.Symbol == "GBP");

            // Again create a new context (to simulate making service calls over WCF)
            MyContext context4 = new MyContext(ConnectionString);
            context4.Customers.Add(customer);

            // Uncommenting the following line prevents the exception raised below
            //customer.Country.Currency = null;

            context4.Entry(customer.Country).State = System.Data.EntityState.Unchanged;
            context4.Entry(customer.Currency).State = System.Data.EntityState.Unchanged;

            // The following line will result in this exception:
            // AcceptChanges cannot continue because the object's key values conflict with another     
            // object in the ObjectStateManager. Make sure that the key values are unique before 
            // calling AcceptChanges.
            context4.Entry(customer.Country.Currency).State = System.Data.EntityState.Unchanged;
            context4.SaveChanges();

            Console.WriteLine("Done.");
            Console.ReadLine();
        }
    }



}
Run Code Online (Sandbox Code Playgroud)

Sla*_*uma 5

我猜你得到的异常只有customer.Currencycustomer.Country.Currency指同一种货币,即具有相同的身份密钥.问题是这两个货币对象来自不同的对象上下文,因此它们是不同的对象(ReferenceEquals(customer.Currency, customer.Country.Currency)false).当您将两者都附加到上一个上下文(通过设置State)时,会发生异常,因为它们是具有相同键的两个不同对象.

查看您的代码,也许最简单的选择是在您加载货币之前检查您要分配给客户的货币是否与国家/地区的货币相同,例如:

if (customer.Country.Currency.Symbol == "GBP")
    customer.Currency = customer.Country.Currency;
    // currencies refer now to same object, avoiding the exception
else
{
    MyContext context3 = new MyContext(ConnectionString);
    customer.Currency = context3.Currency.First(e => e.Symbol == "GBP");
}
Run Code Online (Sandbox Code Playgroud)

(我假设这Symbol是货币的关键或数据库中最不唯一的.)如果货币相同,您还可以避免一次服务/数据库调用.

其他选项包括:如果可以,请不要在国家/地区查询中包含货币.你的解决方案设置customer.Country.Currencynull(一点也不差).在添加customer(if (customer.Country.Currency.Symbol == customer.Currency.Symbol) customer.Currency = customer.Country.Currency;)之前,在最后一个上下文中对两种货币的引用相等.在上一个上下文中重新加载货币并将其分配给客户.

但在我看来,这只是一种解决问题的"更好的方式",只是另一种方式.