Entity Framework CTP5 Нарушение ограничения PRIMARY KEY - PullRequest
3 голосов
/ 07 августа 2011

Я начал изучать сущность CTP5, написав приложение для Windows. у меня есть две модели (единица и товар) следующим образом:

 public class Unit : BaseEntity
{
   public Unit()
   {
     Goods = new List<Good>();
   }
   public string name { get; set; }
   public virtual ICollection<Good> Goods { get; set; }
}

public class Good : BaseEntity
{
        public Int64 code { get; set; }
        public string name { get; set; }

 public virtual Unit Unit { get; set; }

}

Я использую интерфейс хранилища с именем IRepository, как показано ниже:

public interface IRepository
    {
        BaseEntity GetFirst();
        BaseEntity GetNext(Int32 id);
        BaseEntity GetPrevoius(Int32 id);
        BaseEntity GetLast();
        BaseEntity GetById(Int32 id);

        void Update(int id, BaseEntity newEntity);
        void Delete(int id);
        void Insert(BaseEntity entity);
        int GetMaxId();
        IList GetAll();
    }

каждая модель имеет свой собственный репозиторий, но, возможно, лучше использовать универсальный репозиторий типа BaseEntity. Ссылка на GoodRepository выполняется в GoodForm, а соответствующий объект типа Good создается объектом Activator в таких общих методах, как Insert / Update / Delete ..., как показано ниже:

private void InsertButton_Click(object sender, EventArgs e)
{
   Unit unit = goodRepo.GetUnitById(Convert.ToInt32(UnitIdTextBox.Text));
   if (unit == null)
   {
       unit = new Unit { Id = goodRepo.GetUnitMaxId(), Name = "Gram" };
   }
   var good = Activator.CreateInstance<Good>();
   good.Id = string.IsNullOrEmpty(IdTextBox.Text) ? goodRepo.GetMaxId() : Convert.ToInt32(IdTextBox.Text);

   IdTextBox.Text = good.Id.ToString();
   good.Name = NameTextBox.Text;
   good.Description = DescriptionTextBox.Text;
   good.Unit = unit;
   goodRepo.Insert(good);
}

и метод GoodRepository.Insert:

public void Insert(Model.BaseEntity entity)
{
  using (PlanningContext context = new PlanningContext())
  {
      context.Goods.Add(entity as Good);
      int recordsAffected = context.SaveChanges();
      MessageBox.Show("Inserted " + recordsAffected + " entities to the database");
  }
}

Моя проблема заключается в том, что SaveChanges () генерирует ошибку «Нарушение ограничения PRIMARY KEY» и говорит, что не может вставить дубликат ключа в объект «dbo.Units». но если я перенесу свой контекст в форму, в которой я создаю Хороший объект, и вставлю его туда, то все в порядке.

Кто-нибудь может подсказать мне, как решить эту проблему?

заранее благодарю

Ответы [ 2 ]

2 голосов
/ 07 августа 2011

Источник вашей проблемы здесь:

using (PlanningContext context = new PlanningContext())
{
    context.Goods.Add(entity as Good);
    //...
}

Вы добавляете сущность Good во вновь созданный и, следовательно, изначально пустой контекст.Теперь, если вы добавляете объект в контекст, EF также добавит в контекст весь граф объектов связанных объектов, если только связанные объекты уже не присоединены к контексту.Это означает, что good.Unit будет также помещен в контекст в состоянии Added.Поскольку у вас, похоже, нет автоматически сгенерированного ключа идентификации для класса Unit, EF пытается вставить good.Unit в БД с тем же ключом, который уже находится в базе данных.Это вызывает исключение.

Теперь вы могли бы специально решить эту проблему, подключив модуль к контексту, прежде чем добавить новый товар:

using (PlanningContext context = new PlanningContext())
{
    context.Units.Attach((entity as Good).Unit);
    context.Goods.Add(entity as Good);
    //...
}

Но я бы лучше переосмыслилдизайн вашего хранилища.Не стоит создавать новый контекст в каждом методе репозитория.Контекст играет роль единицы работы, а единица работы обычно является больше контейнером для многих операций базы данных, которые тесно связаны друг с другом и должны быть зафиксированы в одной транзакции базы данных.

Таким образом, операции, подобные вашему InsertButton_Click методу, должны иметь такую ​​структуру:

using (var context = CreateSomehowTheContext())
{
    var goodRepo = CreateSomehowTheRepo(context); // Inject this context
    var perhapsAnotherRepo = CreateTheOtherRepo(context); // Inject same context

    Unit unit = goodRepo.GetUnitById(Convert.ToInt32(UnitIdTextBox.Text));
    // unit is now attached to context

    // ...
    good.Unit = unit;
    goodRepo.Insert(good); // should use the injected context and only do
                           // context.Goods.Add(good);
                           // It doesn't add unit to the context since
                           // it's already attached

    // ...

    context.SaveChanges();
}

Здесь вы работаете только с одним единственным контекстом, и репозитории получат этот контекст, введенный (например, в конструкторе),Они никогда не создают свой собственный контекст внутри.

0 голосов
/ 07 августа 2011

Я подозреваю, что это потому, что GetUnitMaxId возвращает одно и то же значение более одного раза. Идентификатор - это автоматически увеличивающийся столбец идентификаторов? Если это так, вы не должны пытаться делать какие-либо предположения о том, что это значение может быть в коде.

Даже если это не автоинкрементный столбец идентификаторов, вы можете быть уверены в его значении только тогда, когда все остальные зафиксированы в БД.

Как общий шаблон проектирования, старайтесь избегать необходимости ссылаться на идентификаторы в коде до их сохранения. EF может помочь в этом, используя свойства навигации (ссылки на объекты между сущностями).

...