实体框架基线完整性

时间:2017-02-17 13:40:52

标签: c# sql-server entity-framework

我首先使用Entity Framework数据库,并且我有一个使用基线ID存储历史值的表。我们使用基线ID在此表上存储父/子链接。以下专栏构成了这个设计: -

  • Id int(主键 - 唯一)
  • BaselineId int(不是唯一的,但是对同一项目进行唯一标识修订)
  • ParentBaselineId int nullable(指链接实体的基线,无FK)
  • 最新位(表示这是系列中最新的基线)

清晰的示例数据

Id  BaselineId  ParentBaselineId Latest
1   1           NULL             0
2   1           NULL             1
3   2           1                0
4   2           1                1

这显示了两个项目,每个项目都有两个修订版本。基线1是基线2的父级。

我的问题是,由于下面列出的原因,我在C#中查找下一个可用的基线,并手动指定要保存的BaselineId / ParentBaselineId。当两个用户同时触发此方法时,它们会保存相同的Baseline ID,因为在第二个用户查找下一个可用的基准ID之前,保存未完成。

  • 该方法可以一次添加许多必须通过基线ID链接在一起的项目
  • 这必须是单个SQL事务,因此它可以在出错时完全回滚
  • SQL触发器不能用于设置基线,因为它们需要提前指示关系

我可以采取哪些措施来确保同时运行该方法的两个用户不会使用相同的基线?

我的C#看起来像这样

using (var tx = new TransactionScope())
{
    using (var context = new DbContext(connectionString))
    {
        int baseline = context.MyTable.Max(e => e.BaselineId);
        context.MyTable.Add(new MyTable() {BaselineId = baseline + 1, Latest = true});
        context.MyTable.Add(new MyTable() { BaselineId = baseline + 2, ParentBaselineId = baseline + 1, Latest = true });
        context.SaveChanges();
    }

    tx.Complete();
}

1 个答案:

答案 0 :(得分:0)

使用@Steve Greene的建议,我能够使用SQL序列。在我的数据库中创建一个新序列并设置起始值以匹配我现有的数据后,我将我的代码更新为以下内容。

public long NextBaseline(DbContext context)
{
    DataTable dt = new DataTable();
    var conn = context.Database.Connection;
    var connectionState = conn.State;
    try
    {
        if (connectionState != ConnectionState.Open)
            conn.Open();
        using (var cmd = conn.CreateCommand())
        {
            cmd.CommandText = "SELECT NEXT VALUE FOR MySequence;";
            using (var reader = cmd.ExecuteReader())
            {
                dt.Load(reader);
            }
        }
    }
    catch (Exception ex)
    {
        throw new HCSSException(ex.Message, ex);
    }
    finally
    {
        if (connectionState != ConnectionState.Open)
            conn.Close();
    }
    return Convert.ToInt64(dt.AsEnumerable().First().ItemArray[0]);
}

public void Save()
{
    using (var tx = new TransactionScope())
    {
        using (var context = new DbContext(connectionString))
        {
            var parent = new MyTable() { BaselineId = NextBaseline(context), Latest = true };
            var child = new MyTable() { BaselineId = NextBaseline(context), ParentBaselineId = parent.BaselineId, Latest = true }
            context.MyTable.Add(parent);
            context.MyTable.Add(child);
            context.SaveChanges();
        }

        tx.Complete();
    }
}