2011-05-07 47 views
7

什么是在Entity Framework中手动生成主键的最佳方式4.1代码优先?什么是在Entity Framework中手动生成主键的最佳方式4.1代码优先

我正在编程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 

我consern是(但不太可能),这两个(或更多)的用户将试图挽救实体美孚在同一时间:

我目前使用下面的代码生成一个顺序键并且因此将获得相同的ID并且插入将失败。

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

请不要说我无法(也不会)使用数据库生成的标识字段!

+0

为什么你不必使用身份? – 2011-05-07 22:46:23

回答

2

这里是我最终使用。此代码基于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 

通过运行存储过程获取实体美孚新的密钥(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 
0

您可以使用GUID而不是INT吗?如果是这样,你可以只使用

System.Guid.NewGuid().ToString() 

如果没有,你需要锁的线程或使用相同的ID,以避免两个插入表。

+0

我不能使用Guid。你能否提供一个如何锁定线程或表的代码示例? – Thomas 2011-05-07 22:17:35

+1

这是一个广泛的话题,谷歌是您了解它的朋友。您也可以创建专栏(如果它尚未),并在插入时注意错误。如果两个线程一次尝试,您会在其中一个线程上发生错误。具有错误的那个人可以再次尝试下一个整数直到获得成功。这不是很好的设计,但它会起作用。 – Mikecito 2011-05-07 22:41:19

5

您的关注是有效的 - 在经常使用的网站中,这很可能会发生,解决方案不是很容易。您可以使用@Mikecito描述的客户端Guid,但它有显着的性能影响,我想你不想使用它。

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

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

这里是SQL服务器顺序表和序列程序的示例:

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(); 
} 
相关问题