关于 CommitTransaction 的 EF Code First 问题 - 使用存储库模式
Posted
技术标签:
【中文标题】关于 CommitTransaction 的 EF Code First 问题 - 使用存储库模式【英文标题】:EF Code First issue on CommitTransaction - using Repository pattern 【发布时间】:2011-06-17 03:00:13 【问题描述】:我在使用“代码优先”的 EF 4.1 时遇到问题。让我在开始发布任何代码之前设置我的情况。我在一个名为 Data.EF 的类库项目中有一个名为 MemberSalesContext 的 DBContext 类。我在一个名为 Domain 的单独类库项目中有我的 POCO。 My Domain 项目对实体框架一无所知,没有引用,什么都没有。我的 Data.EF 项目具有对 Domain 项目的引用,因此我的 DB 上下文类可以连接位于 Data.EF.Mapping 中的映射类中的所有内容。我正在使用 EntityFramework 中的 EntityTypeConfiguration 类在此命名空间中进行所有映射。所有这些都是非常标准的东西。在实体框架之上,我使用了存储库模式和规范模式。
我的 SQL Server 数据库表定义了一个复合主键。作为键的一部分的三列是 Batch_ID、RecDate 和 Supplier_Date。此表作为标识列(数据库生成的值 => +1)称为 XREF_ID,它不是 PK 的一部分。
我的映射类位于 Data.EF.Mapping 中,如下所示:
public class CrossReferenceMapping : EntityTypeConfiguration<CrossReference>
public CrossReferenceMapping()
HasKey(cpk => cpk.Batch_ID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.Supplier_Date);
Property(p => p.XREF_ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("wPRSBatchXREF");
我的 MemberSalesContext 类(继承自 DBContext)如下所示:
public class MemberSalesContext : DbContext, IDbContext
//...more DbSets here...
public DbSet<CrossReference> CrossReferences get; set;
//...more DbSets here...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
//...more modelBuilder here...
modelBuilder.Configurations.Add<CrossReference>(new CrossReferenceMapping());
//...more modelBuilder here...
我在一个类中有一个私有方法,它使用我的存储库返回一个被迭代的对象列表。我所指的列表是下面示例中最外层的 foreach 循环。
private void CloseAllReports()
//* get list of completed reports and close each one (populate batches)
foreach (SalesReport salesReport in GetCompletedSalesReports())
try
//* aggregate sales and revenue by each distinct supplier_date in this report
var aggregates = BatchSalesRevenue(salesReport);
//* ensure that the entire SalesReport breaks out into Batches; success or failure per SalesReport
_repository.UnitOfWork.BeginTransaction();
//* each salesReport here will result in one-to-many batches
foreach (AggregateBySupplierDate aggregate in aggregates)
//* get the batch range (type) from the repository
BatchType batchType = _repository.Single<BatchType>(new BatchTypeSpecification(salesReport.Batch_Type));
//* get xref from repository, *if available*
//* some will have already populated the XREF
CrossReference crossReference = _repository.Single<CrossReference>(new CrossReferenceSpecification(salesReport.Batch_ID, salesReport.RecDate, aggregate.SupplierDate));
//* create a new batch
PRSBatch batch = new PRSBatch(salesReport,
aggregate.SupplierDate,
BatchTypeCode(batchType.Description),
BatchControlNumber(batchType.Description, salesReport.RecDate, BatchTypeCode(batchType.Description)),
salesReport.Zero_Sales_Flag == false ? aggregate.SalesAmount : 1,
salesReport.Zero_Sales_Flag == false ? aggregate.RevenueAmount : 0);
//* populate CrossReference property; this will either be a crossReference object, or null
batch.CrossReference = crossReference;
//* close the batch
//* see PRSBatch partial class for business rule implementations
batch.Close();
//* check XREF to see if it needs to be added to the repository
if (crossReference == null)
//*add the Xref to the repository
_repository.Add<CrossReference>(batch.CrossReference);
//* add batch to the repository
_repository.Add<PRSBatch>(batch);
_repository.UnitOfWork.CommitTransaction();
catch (Exception ex)
//* log the error
_logger.Log(User, ex.Message.ToString().Trim(), ex.Source.ToString().Trim(), ex.StackTrace.ToString().Trim());
//* move on to the next completed salesReport
外循环的第一次迭代一切顺利。在外循环的第二次迭代中,代码在 _repository.UnitOfWork.CommitTransaction() 处失败。返回的错误信息如下:
“对数据库的更改已成功提交,但在更新对象上下文时出错。ObjectContext 可能处于不一致的状态。内部异常消息:AcceptChanges 无法继续,因为对象的键值与对象中的另一个对象冲突ObjectStateManager。在调用 AcceptChanges 之前确保键值是唯一的。"
在这种情况下,第二次迭代中的数据库更改未成功提交,但第一次迭代中的更改已成功提交。我已确保外循环和内循环中的对象都是唯一的,并遵循数据库主键。
这里有什么我遗漏的吗?如果证明有帮助,我愿意增加我的代码示例。除了修改数据库表上的复合主键集之外,我已尽我所能解决此问题。
谁能帮忙???非常感谢提前!顺便说一句,对不起,很长的帖子!
【问题讨论】:
为什么要明确处理交易? 我不确定...但是 EF 可能会因为您声明 XREF_ID 属性具有数据库生成的“身份”选项而感到困惑。也许 EF 假设这是这种情况下的 PK?你玩过那个映射吗? 【参考方案1】:我在这里回答我自己的问题...
我的问题与我的映射类中如何定义复合主键有关。使用 EF Code First 定义复合主键时,必须这样定义:
HasKey(cpk => new cpk.COMPANYID, cpk.RecDate, cpk.BATTYPCD, cpk.BATCTLNO );
与我之前的定义相反:
HasKey(cpk => cpk.COMPANYID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.BATTYPCD);
HasKey(cpk => cpk.BATCTLNO);
我收到的错误是 ObjectContext 包含多个相同类型但不唯一的元素。这成为我在 CommitTransaction 上的 UnitOfWork 中的一个问题。这是因为当映射类从我的 DBContext 类中实例化时,它执行了上面显示的 4 个 HasKey 语句,只有属性 BATCTLNO 的最后一个成为主键(不是复合键)。像我上面的第一个代码示例那样内联定义它们可以解决问题。
希望这对某人有所帮助!
【讨论】:
以上是关于关于 CommitTransaction 的 EF Code First 问题 - 使用存储库模式的主要内容,如果未能解决你的问题,请参考以下文章