在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并且插入将失败.
解决方法
您的担忧是有效的 – 在经常使用的网站中,这种情况最有可能发生,解决方案并不容易.您可以使用@Mikecito所描述的客户端Guid,但它有很大的性能损失,我想您不想使用它.
你现在这样做的方式非常糟糕,因为唯一的解决方案是将你的代码包装在单个可序列化的事务中 – 事务必须同时包含选择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
该表不需要首先按代码映射 – 您永远不会直接访问它.您必须创建自定义数据库初始化程序,以便在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();
- }