2016-02-19 68 views
0

(我不知道是不是线程安全问题实际上是相关的,所以我会编辑标题为合适。)与实体框架线程安全

.NET 4.6,MVC 5.我跑我管理的网站上的任务抓取了几个CSV文件并遍历它们,根据需要操作内部数据(文件是只读的)。此任务每20分钟运行一次。

任务完全跑了好几天,但昨天它停止处理数据。它在预定时间运行,但每个CSV文件的数据处理都单独失败,并显示错误The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.重新启动应用程序将问题解决了几个小时,然后又开始出错。

我不知道这可能是相关,但我的直觉反应是线程安全的。我不完全熟悉如何在.NET/MVC中处理线程,但粗略搜索显示EF不是线程安全的。我的任务是否有可能试图在自己的顶部运行,并且由于此问题而遇到麻烦?这看起来很奇怪,因为在我看来,单独的任务实例应该有自己的上下文。也许更可能的是服务如何执行数据库操作。

我无法重现,因为它的本质问题;所有尝试在调试时在本地重现它都会显示按预期运行的任务。

什么引起这些错误,我怎么能阻止他们?

我已经包括了第一个CSV文件导入的代码;每个部分的例外情况都是分开的。作为背景,User有一个属性ICollection<PlannerCode>,称为PlannerCodes。这个项目是基于nopCommerce,但这个任务是完全自定义的,所以我相信它适用于nopCommerce而不是以这种方式标记它。

堆栈跟踪:

System.InvalidOperationException: The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects. 
at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.ValidateContextsAreCompatible(RelatedEnd targetRelatedEnd) 
at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges) 
at System.Data.Entity.Core.Objects.ObjectStateManager.PerformAdd(IEntityWrapper wrappedOwner, RelatedEnd relatedEnd, IEntityWrapper entityToAdd, Boolean isForeignKeyChange) 
at System.Data.Entity.Core.Objects.ObjectStateManager.PerformAdd(IList`1 entries) 
at System.Data.Entity.Core.Objects.ObjectStateManager.DetectChanges() 
at System.Data.Entity.Core.Objects.ObjectContext.DetectChanges() 
at System.Data.Entity.Internal.InternalContext.DetectChanges(Boolean force) 
at System.Data.Entity.Internal.InternalContext.GetStateEntries(Func`2 predicate) 
at System.Data.Entity.Internal.InternalContext.GetStateEntries() 
at System.Data.Entity.Infrastructure.DbChangeTracker.Entries() 
at System.Data.Entity.DbContext.GetValidationErrors() 
at System.Data.Entity.Internal.InternalContext.SaveChanges() 
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges() 
at System.Data.Entity.DbContext.SaveChanges() 
at Nop.Data.EfRepository`1.Update(T entity) in C:\Users\username\Documents\projectname\Libraries\Nop.Data\EfRepository.cs:line 120 
at Nop.Services.Users.UserService.UpdateUser(User user) in C:\Users\username\Documents\projectname\Libraries\Nop.Services\Users\UserService.cs:line 477 
at Nop.Services.WorkItems.ImportTask.Execute() in C:\Users\username\Documents\projectname\Libraries\Nop.Services\WorkItems\ImportTask.cs:line 165 

ImportTask代码:

if (File.Exists(plannerFile)) 
{ 
    try 
    { 
     // planners corresponds to the CSV file 
     // foreach is the correct way of iterating through the lines 
     foreach (var p in planners) 
     { 
      var user = _userService.GetUserByEmail(p.Email); 
      var pcode = _codeService.GetPCodeByString(p.Code); 

      // check if pcode already exists. if it doesn't, insert it. 
      if (pcode == null) 
      { 
       pcode = new PlannerCode { P = p.Code }; 
       _codeService.InsertPCode(pcode); 
      } 

      // if no user found or the user is already associated with the pcode, move on 
      if (user == null || user.PlannerCodes.Contains(pcode)) 
       continue; 

      // add the pcode to the user's PlannerCodes 
      user.PlannerCodes.Add(pcode); 

      // update the user to save changes to PlannerCodes 
      _userService.UpdateUser(user); 
     } 
    } 
    catch (Exception ex) 
    { 
     // log exception info, ex.ToString() 
    } 
} 

UserService如下。 CodeService基本上是相同的,但相关存储库类型已更改。

readonly IRepository<User> _userRepository; 

public UserService(IRepository<User> userRepository) 
{ 
    _userRepository = userRepository; 
} 

public virtual User GetUserByEmail(string email) 
{ 
    if (string.IsNullOrWhiteSpace(email)) 
     return null; 

    return _userRepository.Table.FirstOrDefault(u => u.Email == email); 
} 

public virtual void UpdateUser(User user) 
{ 
    if (user == null) 
     throw new ArgumentNullException(nameof(user)); 

    _userRepository.Update(user); 
} 

EfRepository

public virtual void Update(T entity) 
{ 
    // exceptions are caught but snipped from this example 
    _context.SaveChanges(); 
} 

服务注入ImportTask与此代码:

IUserService _userService = EngineContext.Current.Resolve<IUserService>(); 
+0

相关:http://stackoverflow.com/questions/15274539/the-relationship-between-the-two-objects-cannot-be-defined-because-they-are-atta –

+0

IRepository 和IRepository 使用不同背景? –

+0

@ArturoMenchaca我正在通过Autofac文档阅读,我真的不太确定这是如何工作的。我相信相关的代码在'DependencyRegistrar.cs'(source [here](https://github.com/nopSolutions/nopCommerce/blob/develop/src/Presentation/Nop.Web.Framework/DependencyRegistrar.cs))中,它将DbContext设置为'InstancePerLifetimeScope',但我试图更好地理解。 – vaindil

回答

1

当您联系在DbContext不同实例创建的实体这种类型的错误发生。

如果UserServiceCodeService的存储库具有不同的上下文,则此错误将在某个时刻发生。

正如你在评论说,如果你正在使用InstancePerLifetimeScope可能是因为仓库在不同范围内创建的每个服务的环境是不同的。

您应该使用InstancePerRequest来确保在执行整个请求的过程中,上下文是相同的。

+0

这可能是nopCommerce特有的问题,但我不确定。有没有一种方法可以在任务本身内部声明一个上下文,并通过该任务使用该上下文来完成任何事情?我宁愿不编辑框架的核心文件;我可能会破坏一切。 – vaindil

+0

@Vaindil:UserService和CodeService被注入ImportTask? –

+0

正确。我将代码添加到问题的底部。我不知道这是否是nopCommerce特定的。 – vaindil