我已经开始通过编写Windows应用程序来学习实体框架CTP5。 我有两个模型(单位和好)如下:
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类型的通用存储库。 GoodForm中引用GoodRepository,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中插入重复键。 但是如果我将我的上下文移动到我构建Good对象的表单并将其插入那里一切都很好。
有人能指导我如何解决这个问题吗?
提前谢谢
答案 0 :(得分:2)
问题的根源在于:
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();
}
在这里,您只使用一个单独的上下文,并且存储库将注入此上下文(例如在构造函数中)。他们从不在内部创建自己的上下文。
答案 1 :(得分:0)
我怀疑是因为GetUnitMaxId多次返回相同的值。 Id是自动递增标识列吗?如果是这样,您不应该尝试对代码中的值进行任何假设。
即使它不是自动递增的标识列,只有当所有其他标识列都已提交到数据库时,您才能确定它的值。
作为一般设计模式,尽量避免在存储之前在代码中引用ID。 EF可以通过利用导航属性(实体间对象引用)来帮助解决这个问题。