該文檔指出EF沒有內置的悲觀併發支持。但這並不意味着你不能用EF來悲觀鎖定。 因此,您可以使用EF進行緊急鎖定!
配方很簡單:
- 使用交易(不一定是序列化的,因爲它會導致PERF差。) - READCOMMITTED是確定使用......不過要看...
- 進行更改,請致電dbcontext.savechanges()
- 確實鎖定了您的表 - 手動執行T-SQL,或者隨意使用代碼att。下面。
- 給定的T-SQL命令與提示將保持該數據庫鎖定,直到給定事務的持續時間。
- 您需要注意一件事:您的加載實體可能在您執行鎖定時已經過時,因此鎖定表中的所有實體都應該重新獲取(重新加載)。
我做了很多悲觀鎖定,但樂觀鎖定更好。你不能出錯。
悲觀鎖定無法幫助的一個典型示例是父級子關係,您可能會鎖定父級並將其視爲聚集(因此您認爲您是唯一有權訪問子級的人)。因此,如果其他線程試圖訪問父對象,它將無法工作(將被阻止),直到其他線程從父表釋放鎖。但是對於一個ORM,任何其他編碼器都可以獨立地加載孩子 - 從這一點開始,2個線程將對孩子對象進行更改......悲觀鎖定可能會弄亂數據,樂觀地說你會得到一個例外,你可以重新加載有效的數據,並會盡力再次保存...
因此,代碼:
public static class DbContextSqlExtensions
{
public static void LockTable<Entity>(this DbContext context) where Entity : class
{
var tableWithSchema = context.GetTableNameWithSchema<Entity>();
context.Database.ExecuteSqlCommand(string.Format("SELECT null as dummy FROM {0} WITH (tablockx, holdlock)", tableWithSchema));
}
}
public static class DbContextExtensions
{
public static string GetTableNameWithSchema<T>(this DbContext context)
where T : class
{
var entitySet = GetEntitySet<T>(context);
if (entitySet == null)
throw new Exception(string.Format("Unable to find entity set '{0}' in edm metadata", typeof(T).Name));
var tableName = GetStringProperty(entitySet, "Schema") + "." + GetStringProperty(entitySet, "Table");
return tableName;
}
private static EntitySet GetEntitySet<T>(DbContext context)
{
var type = typeof(T);
var entityName = type.Name;
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
IEnumerable<EntitySet> entitySets;
entitySets = metadata.GetItemCollection(DataSpace.SSpace)
.GetItems<EntityContainer>()
.Single()
.BaseEntitySets
.OfType<EntitySet>()
.Where(s => !s.MetadataProperties.Contains("Type")
|| s.MetadataProperties["Type"].ToString() == "Tables");
var entitySet = entitySets.FirstOrDefault(t => t.Name == entityName);
return entitySet;
}
private static string GetStringProperty(MetadataItem entitySet, string propertyName)
{
MetadataProperty property;
if (entitySet == null)
throw new ArgumentNullException("entitySet");
if (entitySet.MetadataProperties.TryGetValue(propertyName, false, out property))
{
string str = null;
if (((property != null) &&
(property.Value != null)) &&
(((str = property.Value as string) != null) &&
!string.IsNullOrEmpty(str)))
{
return str;
}
}
return string.Empty;
}
}
感謝您回答我的問題。我的印象是,可串行隔離會爲我鎖定桌子。當我跑了一些測試這似乎也工作。我是否正確地理解了你,保存它不會,我需要手動完成。謝謝 –
可序列化鎖定。但這是最貴的鎖定,所以應該避免。在一個Web應用程序中,如果有超過10個用戶同時請求序列化隔離,那麼他們將互相等待... 如果將有超過5個併發用戶,請避免可序列化。或者至少只限於特殊用途! 我真的建議你嘗試樂觀鎖定 - 它只需要一個額外的領域。唯一的缺點是你可能會遇到需要處理的異常。但樂觀可以與表鎖(我附加的代碼)混合使用。 – baHI
感謝您的回答。我明白這個昂貴的隔離級別的缺點。實際上,我的首選方式確實是樂觀鎖定。就是這樣,因爲我處於過渡期,在一個數據庫上有兩個應用程序,這只是一個臨時解決方案。在我們扼殺舊應用程序之前,我無需更改鎖定級別,只會給項目帶來風險。再次感謝您回答我的問題。 –