C#在多线程服务器中使用Entity Framework

42

在多线程服务器中使用实体框架的最佳做法是什么? 我正在使用实体框架 ObjectContext 来管理所有的数据库操作。现在我知道这个上下文不是线程安全的,因此每当我需要使用它来执行一些数据库操作时,我会用lock语句将其包围起来以确保安全。这样做是正确的吗?

6个回答

73

在多线程环境中使用Entity Framework的一些快速建议:

  • 不要使用带有的唯一上下文(不要使用单例模式)
  • 提供无状态服务(您需要实例化和处理每个请求一个上下文
  • 尽可能缩短上下文生命周期
  • 确保实现并发控制系统。可以使用Entity Framework轻松实现乐观并发(如何)。这将确保在使用未更新的实体时,您不会覆盖数据库中的更改。

我有点困惑,我以为只使用一个上下文很好,因为我相信它会进行一些缓存,所以当我在连续请求中处理相同的实体时,使用同一个上下文比每次都创建新的上下文要快得多。那么如果它更慢而且仍然不是线程安全的,为什么像这样使用它很好呢?

你可以只使用一个上下文,但除非你真的知道自己在做什么,否则强烈不建议这样做

我看到两个主要问题经常发生在这种方法中:

  1. 你将使用大量的内存,因为你的上下文永远不会被释放,所有操作的实体将被缓存在内存中(在查询结果中出现的每个实体都会被缓存)。

  2. 如果你从另一个程序或上下文修改数据,你将面临许多并发问题。例如,如果你直接在数据库中修改某些内容,并且相关实体已经被缓存在你唯一的上下文对象中,那么你的上下文将永远不会知道在数据库中直接进行的修改。你将使用一个过时的缓存实体,相信我,这将导致难以发现和修复的问题。

同时不要担心使用多个上下文的性能问题:在90%的使用情况下,每次请求创建/释放新上下文的开销几乎可以忽略不计。请记住,创建新上下文并不一定会创建到数据库的新连接(因为数据库通常使用连接池)。


如果我每个请求(或每个数据库操作)创建一个上下文,并且同时有多个上下文更改来自数据库的特定对象,这会成为问题吗? - Eyal
@Eyal,通常你会遇到并发问题。在多个线程同时尝试更新同一实体时,如果没有妥协,这将很难实现。 - ken2k
谢谢,我理解创建每个工作上下文的优点,那么如果我理解正确,我的唯一关注点就是并发问题,这个问题我只能捕获异常并处理吗? - Eyal
感谢您的详细解释。我一直遇到连接数据库失败的问题,后来发现是因为我使用了静态 ObjectContext。这对我非常有帮助。 - Chris Barlow
拥有每个请求上下文会导致性能问题吗?因为DbContext是一个占用内存的实体。 - Hossein Shahdoost
显示剩余2条评论

12

我应该这样做吗??

不,至少每个线程使用一个上下文,但我强烈鼓励您将上下文视为工作单元,并因此在每个线程的每个工作单元中使用一个上下文。

关于如何定义您的应用程序的“工作单元”,由您决定。但不要使用lock来跨多个线程使用上下文。它不能扩展。


这个。另外一个要点是 - 理论上,您试图节省的资源已经通过连接池得到了保留。 - Chris B. Behrens
你的意思是在进行每一次数据库操作前都需要创建一个上下文,完成后再销毁它吗?例如当客户端发送登录请求时,服务器将创建一个上下文->使用此上下文在数据库中检查用户名和密码->完成后销毁该上下文。 - Eyal
好的,谢谢。那我会为每个工作使用一个上下文,但我仍在尝试理解如何处理多个上下文同时更改特定实体的情况... - Eyal

6
您将ObjectContext视为极其昂贵的实体,因此只实例化一次,然后将其视为“facade”。其实没有必要这样做。即使只考虑到连接在幕后进行池化并且成本非常低(微秒级别?-可能更少?)来完全设置使用ObjectContext抽象的“对象链”。
与直接使用SqlConnection等类似,ObjectContext旨在采用“尽可能晚实例化和尽快丢弃”的方法使用。
EF可以确保您在提交之前拥有最新的对象,从而提供了一定的安全性(乐观并发)。这并不是“线程安全”,但如果您遵守规则,就可以达到相同的效果。

2
通常情况下,ObjectContext 不应在整个应用程序范围内全局使用。您应该频繁创建新的 ObjectContext 并处理旧的。它们肯定也不是线程安全的。如果您继续使用相同的 ObjectContext(取决于应用程序的生命周期),则在修改大量数据时容易出现内存不足异常,因为对象上下文保留对您更改的实体的引用。

0

我在多线程环境中使用实体框架,其中任何线程(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;

0
我为每个原子操作创建一个新的上下文并处理该上下文。根据我的书籍和文章所知,我更喜欢将Context的生命周期尽可能短。但这取决于您的方法和应用程序类型,如Winform或web。
请在以下优秀文章中查找更多信息。 http://www.west-wind.com/weblog/posts/2008/Feb/05/Linq-to-SQL-DataContext-Lifetime-Management

好书推荐:http://books.google.co.th/books?id=Io7hHlVN3qQC&pg=PA580&lpg=PA580&dq=DbContext+lifetime+for+desktop+application&source=bl&ots=ogCOomQwEE&sig=At3G1Y6AbbJH7OHxgm-ZvJo0Yt8&hl=th&ei=rSlzTrjAIovOrQeD2LCuCg&sa=X&oi=book_result&ct=result&resnum=2&ved=0CCgQ6AEwAQ#v=onepage&q&f=false

关于WinForm绑定场景中的Datacontext生命周期,已经有讨论。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接