实体框架6和SQL Server序列

oce*_*rer 13 c# sql-server entity-framework-6

我正在使用EF6与数据库第一个项目.我们需要使用序列,这是SQL Server 2012中引入的一个特性(我相信).

在表上,标识列具有使用以下设置的默认值:

(NEXT VALUE FOR [ExhibitIdentity])
Run Code Online (Sandbox Code Playgroud)

之所以使用它,因为我们有两个表来存储不同部门的展览信息,但我们需要在两个表中都具有唯一性,因为它在许多其他共享公用表中用作参考.

我的问题是在实体框架中使用它,我用谷歌搜索但找不到与EF6是否支持它们有关的信息.我曾尝试设置StoreGeneratedPattternEFdesigner身份,但在保存时这种抱怨零行了,因为它是用影响scope_identity,看看如果插入成功,但因为我们使用的序列这回来为空.

将其设置为computed会抛出一个错误,说我应该将其设置为identity并将其设置为none会导致它插入0作为id值并失败.

我是否需要调用函数/过程以获取下一个序列,然后在保存记录之前将其分配给id值?

任何帮助深表感谢.

Ger*_*old 17

很明显,你不能通过玩DatabaseGeneratedOptions 而逃离这个捕获22 .

如您所建议的,最佳选择是在保存新记录之前设置DatabaseGeneratedOption.None并从序列中获取下一个值(例如,在此问题中).然后将其分配给Id值,然后保存.这是并发安全的,因为您将是唯一一个从序列中绘制特定值的人(假设没有人重置序列).

但是,有一个可能的黑客......

一个糟糕的,我应该在这里停止......

EF 6引入了命令拦截器API.它允许您在执行命令之前和之后操作EF的SQL命令及其结果.当然,我们不应该篡改这些命令吗?

好吧......如果我们看一下DatabaseGeneratedOption.Identity设置时执行的插入命令,我们会看到如下内容:

INSERT [dbo].[Person]([Name]) VALUES (@0)
SELECT [Id]
FROM [dbo].[Person]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
Run Code Online (Sandbox Code Playgroud)

SELECT命令用于从数据库中获取生成的主键值,并将新对象的标识属性设置为此值.这使EF使用中,通过在同一事务外键参考这个新的对象随后插入语句此值.

如果主键是由默认值生成的,则从序列中获取其值(正如您所做的那样),很明显没有scope_identity().然而,序列的当前值可以通过诸如此类的命令找到

SELECT current_value FROM sys.sequences WHERE name = 'PersonSequence'
Run Code Online (Sandbox Code Playgroud)

如果只有我们可以让EF在插入后执行此命令而不是scope_identity()!

好吧,我们可以.

首先,我们必须创建一个实现IDbCommandInterceptor或继承默认实现的类DbCommandInterceptor:

using System.Data.Entity.Infrastructure.Interception;

class SequenceReadCommandInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuting(DbCommand command
           , DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

我们通过命令将此类添加到拦截上下文中

DbInterception.Add(new SequenceReadCommandInterceptor());
Run Code Online (Sandbox Code Playgroud)

ReaderExecuting命令在command执行之前运行.如果这是INSERT带有标识列的命令,则其文本类似于上面的命令.现在我们可以scope_identity()通过获取当前序列值的查询替换该部分:

command.CommandText = command.CommandText
                             .Replace("scope_identity()",
                             "(SELECT current_value FROM sys.sequences
                               WHERE name = 'PersonSequence')");
Run Code Online (Sandbox Code Playgroud)

现在命令看起来像

INSERT [dbo].[Person]([Name]) VALUES (@0)
SELECT [Id]
FROM [dbo].[Person]
WHERE @@ROWCOUNT > 0 AND [Id] = 
    (SELECT current_value FROM sys.sequences
     WHERE name = 'PersonSequence')
Run Code Online (Sandbox Code Playgroud)

如果我们运行它,有趣的是:它的工作原理.在SaveChanges命令之后,新对象已收到其持久的Id值.

我真的不认为这是生产准备.当它是一个插入命令时,你必须修改命令,根据插入的实体选择正确的序列,所有这些都是在一个相当模糊的地方通过脏字符串操作.而且我不知道如果使用大量并发,您将始终获得正确的序列值.但谁知道,也许EF的下一个版本将支持这种开箱即用.

  • 也许翻录整个`select`并在`values`之前放置一个`output inserted.Id`子句,而不是以更健壮的方式. (2认同)
  • @Frederic 有趣,但是 select 语句也用于获取计算列的值(如果有)。如果表有触发器,`output` 子句必须包含在 `into` 变量中,这使得获取插入的 id 值变得更加复杂(如果可能的话)。 (2认同)
  • `output`语句也可以返回计算列.支持触发器的好点,我们必须使用`into`.所以hack会添加第一行`declare @InsId table(Id int)`,将`output inserted.Id'插入@InsId`,保持`select`但在[Person]上添加`inner join @InsId ii .[Id] = ii.Id`,最后删除`和[Id] = ...`条件.对于已经很糟糕的黑客来说,开始有点沉重. (2认同)