使用实体框架进行线程安全

时间:2016-02-19 19:57:19

标签: c# asp.net-mvc entity-framework

(我不知道问题是否与线程安全有关,所以我会根据需要编辑标题。)

.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不是线程安全的。我的任务是否可能试图在自身上运行而ObjectContext因此而遇到麻烦?这看起来很奇怪,因为在我看来,任务的单独实例应该有自己的上下文。也许更有可能是服务如何执行数据库操作。

由于其性质,我无法重现这个问题;所有尝试在调试时在本地重现它都表明任务正在按预期运行。

可能导致这些错误的原因是什么,我该如何预防?

我已经包含了第一个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>();

1 个答案:

答案 0 :(得分:1)

当您关联在DbContext的不同实例中创建的实体时会发生此类错误。

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

正如您在评论中所说,如果您使用InstancePerLifetimeScope,则每个服务的上下文可能不同,因为存储库是在不同的范围内创建的。

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