实体框架 CTP5 违反 PRIMARY KEY 约束
我已经开始通过编写Windows应用程序来学习实体框架CTP5。 我有两个模型(Unit 和 Good),如下所示:
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() 生成一个错误“违反主键约束”并表示无法在对象“dbo.Units”中插入重复键。 但是如果我将上下文移动到我构建 Good 对象并将其插入那里的形式,一切都很好。
有人可以指导我如何解决这个问题吗?
提前致谢
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(BaseEntity 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题的根源在于:
您正在将
Good
实体添加到新创建的、因此最初为空的上下文中。现在,如果将实体添加到上下文,EF 也会将相关实体的整个对象图添加到上下文,除非相关实体已经附加到上下文。这意味着good.Unit
也将以Added
状态放入上下文中。由于您似乎没有为Unit
类自动生成的身份密钥,因此 EF 尝试使用已存在的相同密钥将good.Unit
插入到数据库中数据库。这会导致异常。现在,您可以在添加新的 Good 之前通过将 Unit 附加到上下文来临时解决此问题:
但我最好重新考虑存储库的设计。在每个存储库方法中创建新上下文并不是一个好主意。上下文扮演着工作单元的角色,工作单元通常更多地是许多数据库操作的容器,这些操作紧密地联系在一起,并且应该在单个数据库事务中提交。
因此,像
InsertButton_Click
方法这样的操作应该具有如下结构:在这里,您仅使用一个上下文,并且存储库将注入此上下文(例如在构造函数中)。他们从不在内部创建自己的环境。
The source of your problem is here:
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 thatgood.Unit
will be put into the context inAdded
state as well. Since you don't seem to have an autogenerated identity key for theUnit
class, EF tries to insert thegood.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:
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: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.
我怀疑这是因为 GetUnitMaxId 多次返回相同的值。 Id 是自增标识列吗?如果是这样,您不应该尝试对代码中的值进行任何假设。
即使它不是自动递增的标识列,只有当所有其他列都已提交到数据库时,您才能确定它的值。
作为一般设计模式,请尽量避免在存储 Id 之前在代码中引用 Id。 EF 可以通过利用导航属性(实体间对象引用)来帮助解决此问题。
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).