Your concern is valid - in frequently used web site this will most likely to happen and solution is not very easy. You can use client side Guid
as described by @Mikecito but it has significant performance hit and I guess you don't want to use it.
The way you are doing this at the moment is very bad because the only solution is to wrap your code in single serializable transaction - transaction must contain both selecting Id and saving the record. This will make access to your InventoryObjects
sequential because each select max will lock whole table until transaction is committed - nobody else will be able to read or write data to the table during the insert transaction. It don't have to be a problem in rarely visited site but it can be NO GO in frequently visited site. There is no way to do it differently in your current set up.
The partial improvement is using separate table to hold max value + stored procedure to get the next value and increment the stored value in atomic operation - (it actually simulates sequences from Oracle). Now the only complication is if you need the sequence without gaps. For example if something goes wrong with saving of new InventoryObject
the selected Id will be lost and it will create a gap in the id's sequence. If you need sequence without gaps you must again use transaction to get the next Id and save the record but this time you will only lock single record in sequence table. Retrieving the Id from sequence table should be as close to saving changes as possible to minimize time when sequence record is locked.
Here is sample of sequence table and sequence procedure for 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
The table don't need to be mapped by code first - you will never access it directly. You must create custom database initializer to add table and stored procedure for you when EF creates a database. You can try similar approach as described here. You must also add initialization record for you sequence with start value.
Now you only need to call stored procedure to get a value before you are going to save the record:
// 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();
}