关于CommitTransaction的EF Code First问题 - 使用Repository模式

时间:2011-06-17 03:00:14

标签: repository-pattern entity-framework-4.1 unit-of-work

我在使用“Code First”时遇到了EF 4.1的问题。让我在开始发布任何代码之前设置我的情况。我在名为Data.EF的类库项目中有我的DBContext类,名为MemberSalesContext。我将POCO放在一个名为Domain的独立类库项目中。 My Domain项目对Entity Framework一无所知,没有引用,也没有任何内容。我的Data.EF项目引用了Domain项目,因此我的DB上下文类可以连接位于Data.EF.Mapping中的映射类中的所有内容。我正在使用EntityFramework中的EntityTypeConfiguration类来执行此命名空间中的所有映射。所有这些都是非常标准的东西。在Entity Framework之上,我使用的是Repository模式和Specification模式。

我的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之前,请确保键值是唯一的。“

在这种情况下,第二次迭代的数据库更改未成功提交,但第一次迭代中的更改是。我确保外部和内部循环中的对象都是唯一的,并且遵守数据库主键。

我在这里缺少一些东西吗?如果证明有用,我愿意扩充我的代码示例。我已经完成了解决此问题的所有功能,减去了修改数据库表上的复合主键集。

任何人都可以帮忙吗?非常感谢提前!顺便说一句,对不起,很长的帖子!

1 个答案:

答案 0 :(得分:0)

我在这里回答我自己的问题......

我的问题与我的映射类中如何定义复合主键有关。使用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包含多个不同的同一类型的元素。这成为我的UnitOfWork上的CommitTransaction问题。这是因为当我的DBContext类实例化了映射类时,它执行了上面显示的4个HasKey语句,只有属性BATCTLNO的最后一个成为主键(不是复合键)。如上面的第一个代码示例所示,内联定义它们可以解决问题。

希望这有助于某人!