【发布时间】:2016-02-19 19:57:18
【问题描述】:
(我不知道问题是否真的与线程安全有关,所以我会酌情编辑标题。)
.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 有一个名为 PlannerCodes 的属性 ICollection<PlannerCode>。这个项目是基于 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>();
【问题讨论】:
-
IRepository
和 IRepository 使用不同的上下文? -
@ArturoMenchaca 我现在正在阅读 Autofac 文档,老实说,我不太确定这一切是如何工作的。我相信相关代码在
DependencyRegistrar.cs(来源here)中,它将DbContext设置为InstancePerLifetimeScope,但我试图更好地理解。 -
您必须对相同的请求使用相同的上下文。如果你需要在任何东西之间共享实体,你应该使用 UnitOfWork
标签: c# asp.net-mvc entity-framework