简体   繁体   English

实体框架 6 和 SQL Server 序列

[英]Entity Framework 6 and SQL Server Sequences

I am using EF6 with a database first project.我在数据库第一个项目中使用 EF6。 We have a requirement to use sequences which was a feature introduced in SQL server 2012 (I believe).我们需要使用序列,这是 SQL Server 2012 中引入的功能(我相信)。

On the table the identity column has a default value set using:在表上,标识列具有使用以下方法设置的默认值:

(NEXT VALUE FOR [ExhibitIdentity])

This is used as we have two tables which store exhibit information for separate departments but we need the identity to be unique across both of the tables as it is then used as a reference in lots of other shared common tables.这是因为我们有两个表存储不同部门的展览信息,但我们需要标识在两个表中都是唯一的,因为它随后用作许多其他共享公用表的参考。

My problem is using this within the Entity Framework, I have googled but couldn't find much information in relation to whether EF6 supports them.我的问题是在实体框架中使用它,我用谷歌搜索但找不到与 EF6 是否支持它们有关的太多信息。 I have tried setting StoreGeneratedPatttern in the EFdesigner to Identity but when saving this complains that zero rows were affected as it is using scope_identity to see if the insert succeeded but as we are using sequences this comes back as null.我曾尝试将StoreGeneratedPatttern中的EFdesigner设置为Identity,但是当保存它时抱怨零行受到影响,因为它使用scope_identity来查看插入是否成功,但是当我们使用序列时,它返回为空。

Setting it to computed throws an error saying I should set it to identity and setting it to none causes it to insert 0 as the id value and fail.将它设置为计算会抛出一个错误,说我应该将它设置为身份并将其设置为无会导致它插入 0 作为 id 值并失败。

Do I need to call a function/procedure in order to get the next sequence and then assign it to the id value before saving the record?我是否需要调用函数/过程来获取下一个序列,然后在保存记录之前将其分配给 id 值?

Any help is much appreciated.任何帮助深表感谢。

It's clear that you can't escape from this catch-22 by playing with DatabaseGeneratedOption s.很明显,您无法通过使用DatabaseGeneratedOption来摆脱这个 catch-22。

The best option, as you suggested, is to set DatabaseGeneratedOption.None and get the next value from the sequence (eg as in this question ) right before you save a new record.正如您所建议的,最好的选择是在保存新记录之前设置DatabaseGeneratedOption.None并从序列中获取下一个值(例如在这个问题中)。 Then assign it to the Id value, and save.然后将其分配给 Id 值,并保存。 This is concurrency-safe, because you will be the only one drawing that specific value from the sequence (let's assume no one resets the sequence).这是并发安全的,因为您将是唯一一个从序列中提取特定值的人(假设没有人重置序列)。

However, there is a possible hack...然而,有一个可能的黑客......

A bad one, and I should stop here...一个糟糕的,我应该停在这里......

EF 6 introduced the command interceptor API . EF 6 引入了命令拦截器 API It allows you to manipulate EF's SQL commands and their results before and after the commands are executed.它允许您在执行命令之前和之后操作 EF 的 SQL 命令及其结果。 Of course we should not tamper with these commands, should we?当然,我们不应该篡改这些命令,对吗?

Well... if we look at an insert command that is executed when DatabaseGeneratedOption.Identity is set, we see something like this:好吧...如果我们查看在设置DatabaseGeneratedOption.Identity时执行的插入命令,我们会看到如下内容:

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

The SELECT command is used to fetch the generated primary key value from the database and set the new object's identity property to this value. SELECT命令用于从数据库中获取生成的主键值并将新对象的标识属性设置为该值。 This enables EF to use this value in subsequent insert statements that refer to this new object by a foreign key in the same transaction.这使 EF 能够在后续插入语句中使用此值,这些语句通过同一事务中的外键引用此新对象。

When the primary key is generated by a default taking its value from a sequence (as you do) it is evident that there is no scope_identity() .当主键由默认值从序列中获取时(如您所做的那样)生成,很明显没有scope_identity() There is however a current value of the sequence, which can be found by a command like然而,序列的当前值可以通过类似的命令找到

SELECT current_value FROM sys.sequences WHERE name = 'PersonSequence'

If only we could make EF execute this command after the insert instead of scope_identity() !如果我们可以让 EF 在插入后执行此命令而不是scope_identity()就好了!

Well, we can.嗯,我们可以。

First, we have to create a class that implements IDbCommandInterceptor , or inherits from the default implementation DbCommandInterceptor :首先,我们必须创建一个实现IDbCommandInterceptor的类,或者从默认实现DbCommandInterceptor继承:

using System.Data.Entity.Infrastructure.Interception;

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

We add this class to the interception context by the command我们通过命令将这个类添加到拦截上下文中

DbInterception.Add(new SequenceReadCommandInterceptor());

The ReaderExecuting command runs just before command is executed. ReaderExecuting命令在command执行之前运行。 If this is an INSERT command with an identity column, its text looks like the command above.如果这是一个带有标识列的INSERT命令,它的文本看起来像上面的命令。 Now we could replace the scope_identity() part by the query getting the current sequence value:现在我们可以用获取当前序列值的查询替换scope_identity()部分:

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

Now the command will look like现在命令看起来像

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')

And if we run this, the funny thing is: it works.如果我们运行它,有趣的是:它有效。 Right after the SaveChanges command the new object has received its persisted Id value.SaveChanges命令之后,新对象立即收到了其持久化的 Id 值。

I really don't think this is production-ready.我真的不认为这是生产就绪的。 You'd have to modify the command when it's an insert command, choose the right sequence based on the inserted entity, all by dirty string manipulation in a rather obscure place.当它是插入命令时,您必须修改命令,根据插入的实体选择正确的序列,所有这些都是通过在一个相当晦涩的地方进行脏字符串操作。 And I don't know if with heavy concurrency you will always get the right sequence value back.而且我不知道在大量并发的情况下,您是否总能得到正确的序列值。 But who knows, maybe a next version of EF will support this out of the box.但谁知道呢,也许 EF 的下一个版本会支持开箱即用。

This issue has been already solved:这个问题已经解决了:

https://github.com/dotnet/ef6/issues/165 https://github.com/dotnet/ef6/issues/165

A workaround for this issue was added in 8e38ed8. 8e38ed8 中添加了此问题的解决方法。 It requires setting the following flag in the application:它需要在应用程序中设置以下标志:

SqlProviderServices.UseScopeIdentity = false; SqlProviderServices.UseScopeIdentity = false; Here is the description of the new flag (which includes some caveats worth knowing):以下是新标志的说明(其中包括一些值得了解的注意事项):

Gets or sets a value indicating whether to use the SCOPE_IDENTITY() function to retrieve values generated by the database for numeric columns during an INSERT operation.获取或设置一个值,该值指示在 INSERT 操作期间是否使用 SCOPE_IDENTITY() 函数检索数据库为数字列生成的值。 The default value of true is recommended and can provide better performance if all numeric values are generated using IDENTITY columns.建议使用默认值 true,如果所有数值都使用 IDENTITY 列生成,则可以提供更好的性能。 If set to false, an OUTPUT clause will be used instead.如果设置为 false,则将使用 OUTPUT 子句。 An OUTPUT clause makes it possible to retrieve values generated by sequences or other means. OUTPUT 子句可以检索由序列或其他方式生成的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM