关于 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 问题 - 使用存储库模式的主要内容,如果未能解决你的问题,请参考以下文章

[EF]关于EF使用Contains关键字的问题

关于EF执行返回表的存储过程

EF关于decimal精度的问题

关于EF的五种状态

关于EF的五种状态

关于EF实体框架中的 dbContext