在多线程服务器中使用实体框架的最佳做法是什么?
我正在使用实体框架 ObjectContext
来管理所有的数据库操作。现在我知道这个上下文不是线程安全的,因此每当我需要使用它来执行一些数据库操作时,我会用lock
语句将其包围起来以确保安全。这样做是正确的吗?
在多线程服务器中使用实体框架的最佳做法是什么?
我正在使用实体框架 ObjectContext
来管理所有的数据库操作。现在我知道这个上下文不是线程安全的,因此每当我需要使用它来执行一些数据库操作时,我会用lock
语句将其包围起来以确保安全。这样做是正确的吗?
在多线程环境中使用Entity Framework的一些快速建议:
锁
的唯一上下文(不要使用单例模式)你可以只使用一个上下文,但除非你真的知道自己在做什么,否则强烈不建议这样做。
我看到两个主要问题经常发生在这种方法中:
你将使用大量的内存,因为你的上下文永远不会被释放,所有操作的实体将被缓存在内存中(在查询结果中出现的每个实体都会被缓存)。
如果你从另一个程序或上下文修改数据,你将面临许多并发问题。例如,如果你直接在数据库中修改某些内容,并且相关实体已经被缓存在你唯一的上下文对象中,那么你的上下文将永远不会知道在数据库中直接进行的修改。你将使用一个过时的缓存实体,相信我,这将导致难以发现和修复的问题。
同时不要担心使用多个上下文的性能问题:在90%的使用情况下,每次请求创建/释放新上下文的开销几乎可以忽略不计。请记住,创建新上下文并不一定会创建到数据库的新连接(因为数据库通常使用连接池)。
我应该这样做吗??
不,至少每个线程使用一个上下文,但我强烈鼓励您将上下文视为工作单元,并因此在每个线程的每个工作单元中使用一个上下文。
关于如何定义您的应用程序的“工作单元”,由您决定。但不要使用lock
来跨多个线程使用上下文。它不能扩展。
我在多线程环境中使用实体框架,其中任何线程(UI和后台线程(STA和MTA))都可以同时更新同一个数据库。我通过在任何新的后台线程上开始使用时从头重新创建实体连接来解决这个问题。检查实体连接实例ConnectionString显示一个读取器guid,我假设它用于链接公共连接实例。通过从头重新创建实体连接,每个线程的guid值都不同,似乎没有发生冲突。请注意,程序集只需要与模型所在的程序集相同。
public static EntityConnection GetEntityConnection(
// Build the connection string.
var sqlBuilder = new SqlConnectionStringBuilder();
sqlBuilder.DataSource = serverName;
sqlBuilder.InitialCatalog = databaseName;
sqlBuilder.MultipleActiveResultSets = true;
...
var providerString = sqlBuilder.ToString();
var sqlConnection = new SqlConnection(providerString);
// Build the emtity connection.
Assembly metadataAssembly = Assembly.GetExecutingAssembly();
Assembly[] metadataAssemblies = { metadataAssembly };
var metadataBase = @"res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl";
var dbModelMetadata = String.Format(metadataBase, objectContextTypeModelName);
// eg: "res://*/Models.MyDatabaseModel.csdl|res://*/Models.MyDatabaseModel.ssdl|res://*/Models.MyDatabaseModel.msl"
var modelMetadataPaths = modelMetadata.Split('|');
var metadataWorkspace = new MetadataWorkspace(modelMetadataPaths, metadataAssemblies);
var entityDbConnection = new EntityConnection(metadataWorkspace, sqlConnection);
return entityDbConnection;
关于WinForm绑定场景中的Datacontext生命周期,已经有讨论。