开发者

Entity framework CTP5 Violation of PRIMARY KEY constraint

开发者 https://www.devze.com 2023-03-26 05:27 出处:网络
i have started learning entity framework CTP5 by writing a windows application. i have two models (Unit and Good) as following:

i have started learning entity framework CTP5 by writing a windows application. i have two models (Unit and Good) as following:

 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; }

}

i'm using a repository inteface named IRepository as below :

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(BaseEntit开发者_如何学Goy entity);
        int GetMaxId();
        IList GetAll();
    }

every model has its own repository but maybe it is better to use a generic repository of BaseEntity type. A reference of GoodRepository is made in GoodForm and appropriate object of Good type is made by Activator object in common form methods like Insert/Update/Delete... as below :

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);
}

and GoodRepository.Insert method is :

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");
  }
}

My problem is SaveChanges() generate an error "Violation of PRIMARY KEY constraint" and says it can not inset duplicate key in object 'dbo.Units. but if i move my context to the form which i build Good object and insert it there everything is fine.

Can anybody guid me how to solve this issue?

thank in advance


The source of your problem is here:

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

You are adding the Good entity to a newly created and therefore initially empty context. Now, if you add an entity to the context EF will add the whole object graph of related entities to the context as well, unless the related entities are already attached to the context. That means that good.Unit will be put into the context in Added state as well. Since you don't seem to have an autogenerated identity key for the Unit class, EF tries to insert the good.Unit into the DB with the same key which is already in the database. This causes the exception.

Now, you could ad-hoc fix this problem by attaching the Unit to the context before you add a new Good:

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

But I would better rethink the design of your repository. It's not a good idea to create a new context in every repository method. The context plays the role of a unit of work and a unit of work is usually more a container for many database operations which belong closely together and should be committed in a single database transaction.

So, operations like your InsertButton_Click method should rather have a structure like this:

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();
}

Here you are working only with one single context and the repositories will get this context injected (in the constructor for instance). They never create their own context internally.


I suspect it's because GetUnitMaxId is returning the same value more than once. Is Id an auto-incrementing identity column? If so, you shouldn't try to make any assumptions about what that value might be in code.

Even if it's not an auto-incrementing identity column, you can only be sure of it's value when all others have been committed to the DB.

As a general design pattern, try to avoid the need to refer to Ids in code before they've been stored. EF can help with this by exploiting navigation properties (inter-entity object references).

0

精彩评论

暂无评论...
验证码 换一张
取 消