【问题标题】:What is the best way to manually generate Primary Keys in Entity Framework 4.1 Code First在 Entity Framework 4.1 Code First 中手动生成主键的最佳方法是什么
【发布时间】:2011-08-20 22:08:44
【问题描述】:

在 Entity Framework 4.1 Code First 中手动生成主键的最佳方法是什么?

我正在编写 ASP.NET MVC 3 并且我使用存储库模式。

我目前使用以下代码按顺序生成密钥:

'Code First Class
Public Class Foo
    <Key()>
    <DatabaseGenerated(DatabaseGeneratedOption.None)>
    Public Property iId As Integer

    Public Property sBar As String
End Class

'Context Class
 Public Class FooBarContext : Inherits DbContext
     Public Property Foos As DbSet(Of Foo)
 End Class

'Get the current Id

'Part of code in repository that stores Entity Foo.
Dim iCurrId as Integer = (From io In context.Foo
                         Select io.iId()).Max

Dim iNewId as Integer = iCurrId + 1

Foo.iId = iNewId

我担心(尽管不太可能)两个(或更多)用户会尝试同时保存一个实体 Foo,因此会获得相同的 ID,插入会失败。

这是一个好方法,还是有更好的方法?

请注意我不能(也不会)使用数据库生成的身份字段!

【问题讨论】:

  • 为什么不用身份?

标签: sql-server vb.net ef-code-first entity-framework-4.1


【解决方案1】:

您可以使用 GUID 代替 INT 吗?如果是这样,您可以使用

System.Guid.NewGuid().ToString()

如果没有,您需要锁定线程或表以避免使用相同 ID 进行两次插入。

【讨论】:

  • 我无法使用 Guid。您能否提供一个如何锁定线程或表的代码示例?
  • 这是一个广泛的话题,谷歌是你了解它的朋友。您还可以使列唯一(如果还没有),并在插入时注意错误。如果两个线程同时尝试,其中一个会出错。有错误的人可以用下一个整数再试一次,直到成功。这不是好的设计,但它会起作用。
【解决方案2】:

您的担忧是有道理的 - 在经常使用的网站中,这很可能会发生,而且解决方案不是很容易。您可以使用@Mikecito 所述的客户端Guid,但它对性能有很大影响,我猜您不想使用它。

您目前这样做的方式非常糟糕,因为唯一的解决方案是将您的代码包装在单个可序列化事务中 - 事务必须包含选择 Id 和保存记录。这将使您可以按顺序访问您的InventoryObjects,因为每个 select max 都会锁定整个表,直到事务提交 - 在插入事务期间,没有其他人能够读取或写入数据到表中。在很少访问的站点中它不一定是问题,但在经常访问的站点中它可能是不行的。在您当前的设置中没有办法做到这一点。

部分改进是使用单独的表来保存最大值 + 存储过程来获取下一个值并在原子操作中递增存储值 - (它实际上模拟了来自 Oracle 的序列)。现在唯一的复杂情况是您是否需要没有间隙的序列。例如,如果保存新的InventoryObject 出现问题,则选定的 ID 将丢失,并且会在 ID 的序列中产生间隙。如果您需要没有间隙的序列,您必须再次使用事务来获取下一个 Id 并保存记录,但这次您只会锁定序列表中的单个记录。从序列表中检索 Id 应该尽可能接近保存更改,以最大限度地减少序列记录被锁定时的时间。

以下是 SQL server 的序列表和序列过程示例:

CREATE TABLE [dbo].[Sequences]
(
    [SequenceType] VARCHAR(20) NOT NULL, /* Support for multiple sequences */
    [Value] INT NOT NULL
)

CREATE PROCEDURE [dbo].[GetNextSequenceValue]
    @SequenceType VARCHAR(20)
AS
BEGIN
    DECLARE @Result INT

    UPDATE [dbo].[Sequences] WITH (ROWLOCK, UPDLOCK)
    SET @Result = Value = Value + 1
    WHERE SequenceType = @SequenceType

    RETURN @Result
END

表不需要首先通过代码映射 - 您永远不会直接访问它。当 EF 创建数据库时,您必须创建自定义数据库初始化程序来为您添加表和存储过程。您可以尝试与described here 类似的方法。您还必须为您的序列添加初始值的初始化记录。

现在你只需要调用存储过程来获取一个值就可以保存记录了:

// Prepare and insert record here

// Transaction is needed only if you don't want gaps
// This whole can be actually moved to overriden SaveChanges in your context
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, 
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
   record.Id = context.Database.ExecuteStoreCommand("dbo.GetNextSequenceValue @SequenceType", 
       new SqlParameter("SequenceType", "InventoryObjects"));
   context.SaveChanges();
}

【讨论】:

    【解决方案3】:

    这是我最终使用的。此代码基于 Ladislav Mrnka 的帖子,但已修改为与 DbContext 一起使用。

    用于存储序列信息的模型(不要忘记将其作为 DBSet 添加到您的上下文中)。

    <Table("tSequences")>
    Public Class Sequence
        <Key()>
        <DatabaseGenerated(DatabaseGeneratedOption.None)>
        <Display(Name:="Model name", Order:=1)>
        Public Property sModelName As String
    
        <Required()>
        <Display(Name:="Current Primary key value", AutoGenerateField:=False, Order:=2)>
        Public Property iCurrentPKeyValue As Integer
    End Class
    

    初始化数据库并创建一个存储过程来获取和自动递增序列。

    Public Class DBInitializer
        Inherits CreateDatabaseIfNotExists(Of Context)
    
        Protected Overrides Sub Seed(context As Context)
            'Create stored procedure to hold
            Dim sStoredProcSQL As String = "CREATE PROCEDURE [dbo].[spGetNextSequenceValue]" & vbCrLf & _
                                            "@sModelName VARCHAR(30)" & vbCrLf & _
                                            "AS BEGIN" & vbCrLf & _
                                            "DECLARE" & vbCrLf & _
                                            "@Result INT" & vbCrLf & _
                                            "UPDATE [dbo].[tSequences] WITH (ROWLOCK, UPDLOCK)" & vbCrLf & _
                                            "SET @Result = iCurrentPKeyValue = iCurrentPKeyValue + 1" & vbCrLf & _
                                            "WHERE sModelName = @sModelName" & vbCrLf & _
                                            "RETURN @Result" & vbCrLf &
                                            "END"
    
            context.Database.ExecuteSqlCommand(sStoredProcSQL)
        End Sub
    End Class
    

    通过运行存储过程为实体 Foo 获取一个新密钥 (iNewKey)。

    Dim iNewKey As Integer
    
    Using scope = New TransactionScope(TransactionScopeOption.RequiresNew, New TransactionOptions() With { _
        .IsolationLevel = IsolationLevel.ReadCommitted _
        })
        iNewKey = context.Database.SqlQuery(Of Integer)("DECLARE @return_value int" & vbCrLf & _
                                                        "EXEC @return_value = [dbo].[spGetNextSequenceValue]" & vbCrLf & _
                                                        "@sModelName = 'Foo'" & vbCrLf & _
                                                        "SELECT 'Return Value' = @return_value").ToList().First()
    'Indicate that all operations are completed.
        scope.Complete()
    
        context.SaveChanges()
    End Using
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-15
      • 2023-04-02
      • 2011-08-01
      • 2015-06-25
      • 2011-09-28
      • 2011-08-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多