数据上下文的 SubmitChanges 方法导致实体引用设置为 null
我知道这看起来有点长,但我试图尽可能彻底地解释这个问题。
我们在 linq to sql 数据上下文类中遇到了一个非常“奇特”的问题。我们有一个像这样的 n 层架构: 我们有 3 个类 MotherClass、ChildClass、ChildChildrenClass
MotherClass 看起来像这样:
public class MotherClass
{
private EntitySet<ChildClass> _Children;
[Column]
public int Id { get; set; }
[Association(Storage = "_Children", ThisKey = "Id", OtherKey = "MotherId")]
public EntitySet<ChildClass> Children
{
get { return _Children; }
set { _Children= value; }
}
}
ChildClass 看起来像这样:
public class ChildClass
{
private EntityRef<MotherClass> _Mother;
private EntitySet<ChildChildrenClass> _ChildChildren;
[Column]
public int Id { get; set; }
[Column]
public int MotherId { get; set; }
[Association(Storage = "_Mother", IsForeignKey = true, ThisKey = "MotherId", OtherKey = "Id")]
public MotherClass Mother
{
get { return _Mother.Entity; }
set { _Mother.Entity = value; }
}
[Association(Storage = "_ChildChildren", ThisKey = "Id", OtherKey = "ChildId", DeleteRule = "NO ACTION")]
public EntitySet<ChildChildrenClass> ChildChildren
{
get { return _ChildChildren; }
set { _ChildChildren= value; }
}
}
第三个类被神奇地命名为 ChildChildrenClass:
public class ChildChildrenClass
{
private EntityRef<ChildClass> _Child;
[Column]
public int Id { get; set; }
[Column]
public int ChildId { get; set; }
[Association(Storage = "_Child", IsForeignKey = true, ThisKey = "ChildId", OtherKey = "Id")]
public ChildClass Child
{
get { return _Child.Entity; }
set { _Child.Entity = value; }
}
}
当我们进行更新时,问题就出现了放在 ChildClass 对象上,并删除一些与其关联的 ChildChildrenClass 项。代码如下所示:
DataContext dc = new DataContext(conStr);
dc.StartTransaction();//our custom method for handling transactions
ChildClass cclass = dc.ChildClass.GetById(id);//our method for getting the ChildClass from db
//... here we set some values we want to edit
//...
//...
dc.SubmitChanges(ConflictMode.FailOnFirstConflict);//these actions are cool
//after this the problems arise
List<ChildChildrenClass> ccc = GetAllChildren();//method that gets all the childChildrenClass objects from db
foreach (ChildChildrenClass child in ccc)
{
dc.GetTable(child.GetType()).DeleteOnSubmit(child);
}
dc.SubmitChanges(ConflictMode.FailOnFirstConflict);
//AFTER CALLING THIS METHOD THE PROBLEM APPEARS
上面提到的问题是 cclass.Mother 属性被神奇地设置为 null。经过大量调试(在 Mother set 方法中放置制动点揭示了这一点),我们注意到在某些外部代码中的 SubmitChanges() 期间该属性被设置为 null。
SubmitChanges() 方法成功完成(ChildChildrenClass 项被删除),但这会导致此后运行的代码出现问题。我们使用相同的 DataContext(因为事务)并再次调用引发此异常的 SubmitChanges() 方法:
System.InvalidOperationException: 尝试删除 MotherClass 和 ChildClass 之间的关系。但是,关系的外键之一 (ChildClass.MotherId) 不能设置为 null。 在 System.Data.Linq.ChangeTracker.StandardChangeTracker.StandardTrackedObject.SynchDependentData() 在 System.Data.Linq.ChangeProcessor.ValidateAll(IEnumerable`1 列表) 在 System.Data.Linq.ChangeProcessor.SubmitChanges(冲突模式失败模式) 在 System.Data.Linq.DataContext.SubmitChanges(冲突模式失败模式)
I know this looks a bit long but i tried to explain the problem as throughly as i could.
We are having a very 'exotic' problem with the linq to sql data context class. We have a n-tiered architectured structured like this: We have 3 classes MotherClass, ChildClass, ChildChildrenClass
MotherClass looks something like this:
public class MotherClass
{
private EntitySet<ChildClass> _Children;
[Column]
public int Id { get; set; }
[Association(Storage = "_Children", ThisKey = "Id", OtherKey = "MotherId")]
public EntitySet<ChildClass> Children
{
get { return _Children; }
set { _Children= value; }
}
}
And ChildClass looks something like:
public class ChildClass
{
private EntityRef<MotherClass> _Mother;
private EntitySet<ChildChildrenClass> _ChildChildren;
[Column]
public int Id { get; set; }
[Column]
public int MotherId { get; set; }
[Association(Storage = "_Mother", IsForeignKey = true, ThisKey = "MotherId", OtherKey = "Id")]
public MotherClass Mother
{
get { return _Mother.Entity; }
set { _Mother.Entity = value; }
}
[Association(Storage = "_ChildChildren", ThisKey = "Id", OtherKey = "ChildId", DeleteRule = "NO ACTION")]
public EntitySet<ChildChildrenClass> ChildChildren
{
get { return _ChildChildren; }
set { _ChildChildren= value; }
}
}
And the third class that is magically named ChildChildrenClass:
public class ChildChildrenClass
{
private EntityRef<ChildClass> _Child;
[Column]
public int Id { get; set; }
[Column]
public int ChildId { get; set; }
[Association(Storage = "_Child", IsForeignKey = true, ThisKey = "ChildId", OtherKey = "Id")]
public ChildClass Child
{
get { return _Child.Entity; }
set { _Child.Entity = value; }
}
}
The problem arises when we do an update on a ChildClass object and delete some of the ChildChildrenClass items that are associated with it.The code looks something like this:
DataContext dc = new DataContext(conStr);
dc.StartTransaction();//our custom method for handling transactions
ChildClass cclass = dc.ChildClass.GetById(id);//our method for getting the ChildClass from db
//... here we set some values we want to edit
//...
//...
dc.SubmitChanges(ConflictMode.FailOnFirstConflict);//these actions are cool
//after this the problems arise
List<ChildChildrenClass> ccc = GetAllChildren();//method that gets all the childChildrenClass objects from db
foreach (ChildChildrenClass child in ccc)
{
dc.GetTable(child.GetType()).DeleteOnSubmit(child);
}
dc.SubmitChanges(ConflictMode.FailOnFirstConflict);
//AFTER CALLING THIS METHOD THE PROBLEM APPEARS
The problem mentioned above is that the cclass.Mother property is magically set to null. After a lot of debugging (placing brakepoints in the Mother set method revealed this) we noticed that the property is being set to null during SubmitChanges() in some external code.
The SubmitChanges() method completes successfully (the ChildChildrenClass items are deleted) but this causes a problem with the code that runs after this. We are using the same DataContext (because of the transaction) and calling again the SubmitChanges() method that throws this exception:
System.InvalidOperationException: An attempt was made to remove a relationship between a MotherClass and a ChildClass. However, one of the relationship's foreign keys (ChildClass.MotherId) cannot be set to null.
at System.Data.Linq.ChangeTracker.StandardChangeTracker.StandardTrackedObject.SynchDependentData()
at System.Data.Linq.ChangeProcessor.ValidateAll(IEnumerable`1 list)
at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我很早以前写博客引擎时也遇到过这个问题。当我从连接表中删除几行后,问题就出现了。删除内容一切顺利,然后无论我做什么,下一个
SubmitChanges()
都会出现这个确切的异常。花了大约一天左右的时间解决问题后,我恢复了一种解决方法:
我知道这是非常 hacky ,但这是我解决这个问题的唯一方法。我看到你在那里使用交易,这会让事情变得有点困难。也许尝试使用两个不同的事务(一个用于旧的 DataContext,一个用于新的),并在第二个失败时回滚第一个事务?
我知道这很糟糕。
也许尝试使用另一个没有问题的 ORM(例如 NHibernate)。
I have also encountered this issue long time ago when I wrote a blog engine. The problem arised after I deleted a couple of rows from a junction table. Deleting the things went okay, then no matter what I did, this exact exception came up on the next
SubmitChanges()
.After spending about a day or so solving the problem, I restorted to a workaround:
I know this is very hacky, but this was the only way I could solve it. I see you use a transaction there, that would make this a little bit harder. Perhaps try to use two different transactions (one for the old DataContext, and one for the new), and roll back the first one if the second one fails?
I know this is hacky.
Perhaps try to use another ORM (eg. NHibernate) which doesn't have issues with this.
DataContext
实例应该永远被重用。每次 由于框架无法确保在SubmitChanges()
调用之间数据没有发生更改,因此建议的方法是在提交更改后处理 DataContext,并在以下情况下创建一个新的 DataContext:需要另一个需要SubmitChanges()
调用的事务。此外,
DataContext
对象已经包装了事务中的所有插入、更改和/或删除。DataContext
instances should never be reused. Every time Because the framework has no way to ensure that the data has not been changed betweenSubmitChanges()
calls, the recommended approach is to dispose of the DataContext after changes have been submitted and create a new one if another transaction requiring aSubmitChanges()
call is needed.In addition, the
DataContext
object already wraps any inserts, changes, and/or deletes in a transaction.不要使用 LinQ-To-SQL,因为
简单的域。它有它的问题
与关系。
您需要设计时支持选择
ADO.NET 实体框架。如果你
有一个复杂的域要映射尝试
NHibernate,它还具有
大执着无明。
亚音速的粉丝也不少
在那里。
Don't use LinQ-To-SQL, because
simple domains. It has its problems
with relations.
you need Design-Time Support choose
ADO.NET Entity Framework. If you
have a complex domain to map try
NHibernate, which also features
great persistence ignorance.
There're also many fans of subsonic
out there.