Tho*_*mas 7 vb.net sql-server ef-code-first entity-framework-4.1
在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
Run Code Online (Sandbox Code Playgroud)
我的观点是(但不太可能)两个(或更多)用户将尝试同时保存实体Foo,因此将获得相同的ID并且插入将失败.
这是一个好方法,还是有更好的方法?
请注意,我不能(也不会)使用数据库生成的身份字段!
您的担忧是有效的 - 在经常使用的网站中,这种情况最有可能发生,解决方案并不容易.您可以使用Guid@Mikecito所描述的客户端,但它有很大的性能影响,我想你不想使用它.
你现在这样做的方式是非常糟糕的,因为唯一的解决方案是将你的代码包装在单个可序列化的事务中 - 事务必须同时包含选择Id和保存记录.这将访问您的InventoryObjects顺序因为每个select max将锁定整个表直到提交事务 - 在插入事务期间没有其他人能够读取或写入数据到表.它不一定是很少访问的网站中的问题,但它可以在经常访问的网站中没有GO.在当前设置中无法以不同方式执行此操作.
部分改进是使用单独的表来保存最大值+存储过程以获取下一个值并在原子操作中增加存储的值 - (它实际上模拟来自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
Run Code Online (Sandbox Code Playgroud)
该表不需要首先按代码映射 - 您永远不会直接访问它.您必须创建自定义数据库初始化程序,以便在EF创建数据库时为您添加表和存储过程.您可以尝试此处所述的类似方法.您还必须为序列添加初始化记录和起始值.
现在,您只需要在保存记录之前调用存储过程来获取值:
// 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();
}
Run Code Online (Sandbox Code Playgroud)
这是我最终使用的。此代码基于 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
Run Code Online (Sandbox Code Playgroud)
初始化数据库并创建一个存储过程来获取和自动递增序列。
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
Run Code Online (Sandbox Code Playgroud)
通过运行存储过程获取实体 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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5059 次 |
| 最近记录: |