1

我有两张桌子,HOTEL 和 OWNER。两者都有 Identity 列,但其中一个具有另一个表所需的外键。

我需要同时以事务方式添加两条记录,但如果在主表上插入失败,我需要回滚为辅助表写入记录的事务。

据我了解,我需要 .SaveChanges() 从辅助表中获取自动生成的 ID,但这似乎也正在提交事务。

有没有其他方法可以做到这一点?

public class HOTEL
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int64 HOTEL_ID { get; set; }

    public string blah1 { get; set; }

    public string blah2 { get; set; }
}

public class OWNER
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Int64 OWNER_ID { get; set; }

    public string blah3 { get; set; }

    public string blah4 { get; set; }

    public Int64 HOTEL_ID { get; set; }

    [ForeignKey("HOTEL_ID")]
    public virtual HOTEL HOTEL { get; set; }
}

...

public class MyContext : DbContext
{
    public MyContext() : base() { }
    public MyContext(string connectionString) : base(connectionString) { }

    public DbSet<HOTEL> HOTELs { get; set; }
    public DbSet<OWNER> OWNERs { get; set; }

    public ObjectContext ObjectContext
    {
        get
        {
            return (this as IObjectContextAdapter).ObjectContext;
        }
    }
}

...

Int64 ret = 0;

// Suppress required for DB2.
using (var transaction = new TransactionScope(TransactionScopeOption.Suppress))  
{
    try
    {
        using (var context = new MyContext())
        {
            var secondaryEntity = new HOTEL();
            context.HOTELs.Add(secondaryEntity);

            // This appears to commit the changes in the trasaction.
            context.SaveChanges();

            primaryEntity.HOTEL_ID = secondaryEntity.HOTEL_ID;

            context.OWNERs.Attach(primaryEntity);
            context.Entry(primaryEntity).State = primaryEntity.OWNER_ID == 0 ? EntityState.Added : EntityState.Modified;

            context.SaveChanges();
            ret = primaryEntity.OWNER_ID;
        }
    }
    catch (Exception ex)
    {
        // Deal with errors.
    }

    if (ret != 0)
    {
        transaction.Complete();
    }
}

return ret;
4

1 回答 1

0

解决这个问题后,事实证明真正的问题是因为我使用的是 TransactionScopeOption.Suppress。我正在使用它,因为 DB2 似乎没有使用任何其他选项。这是因为我使用的是 Client 9.7 Fixpack 4。使用任何其他 TransactionScopeOption 时它返回以下错误。

ERROR [57016] [IBM][DB2/NT64] SQL0668N  Operation not allowed for reason code "7" on table "DB2ADMIN.HOTEL".

深入研究,这是因为 DB2 没有参与 TransactionScope。在查看了有关此问题的其他一些问题后,通过以下步骤解决了该问题。

  • MSDTC 属性中启用的 XA 事务
    1. 开始,运行“dcomcnfg”以启动组件服务管理控制台。
    2. 导航树到“组件服务 > 计算机 > 我的电脑 > 分布式事务协调器 > 本地 DTC”。
    3. 在“本地 DTC”上,右键单击并选择属性。
    4. 选择“安全”选项卡。
    5. 检查“启用 XA 事务”选项卡。
    6. 按确定。
  • 如果我继续使用 DB2 9.7 FP4,我需要在注册表项 HKLM\SOFTWARE\Microsoft\MSDTC\XADLL 下输入一个名称为“DB2CLI.DLL”和值为“C:\ Program Files \IBM\SQLLIB\BIN\”的值DB2CLI.DLL'

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC\XADLL] "DB2CLI.DLL"="C:\Program Files\IBM\SQLLIB\BIN\db2cli.dll"

  • 或者(从注册表项),至少安装 DB2 Client 9.7 FP6。

  • 重新启动计算机。
  • 在处置 TransactionScope 之前不要关闭连接。

最终的代码变成了这样。

Int64 ret = 0;

using (var transaction = new TransactionScope(TransactionScopeOption.Required))
{
    try
    {
        using (var context = new MyContext())
        {
            var secondaryEntity = new HOTEL();
            context.HOTELs.Add(secondaryEntity);

            // SaveChanges(bool) has been depricated, use SaveOptions.
            // SaveChanges is required to generate the autogenerated Identity HOTEL_ID.
            context.ObjectContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            primaryEntity.HOTEL_ID = secondaryEntity.HOTEL_ID;

            context.OWNERs.Attach(primaryEntity);
            context.Entry(primaryEntity).State = primaryEntity.OWNER_ID == 0 ? EntityState.Added : EntityState.Modified;

            context.ObjectContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            ret = primaryEntity.OWNER_ID;

            if (ret != 0)
            {
                transaction.Complete();
                context.ObjectContext.AcceptAllChanges();
            }
        }
    }
    catch (Exception ex)
    {
        // Deal with errors.
    }
}

return ret;

阅读@Pawel 的评论后的另一种选择,它将生成secondaryEntity 的ID 并将其自动附加到primaryEntity 而无需手动分配值:

Int64 ret = 0;

using (var transaction = new TransactionScope(TransactionScopeOption.Required))
{
    try
    {
        using (var context = new MyContext())
        {
            var secondaryEntity = new HOTEL();
            primaryEntity.HOTEL = secondaryEntity;

            context.HOTELs.Add(secondaryEntity);
            context.OWNERs.Attach(primaryEntity);
            context.Entry(primaryEntity).State = primaryEntity.OWNER_ID == 0 ? EntityState.Added : EntityState.Modified;

            context.SaveChanges();
            ret = primaryEntity.OWNER_ID;

            // TransactionScopeOption.Required can still used in case something 
            // goes wrong with additional processing at this point.

            if (ret != 0)
            {
                transaction.Complete();
            }
        }
    }
    catch (Exception ex)
    {
        // Deal with errors.
    }
}

return ret;
于 2013-03-27T22:52:21.987 回答