EF 带外键关系更新实体问题,求大神康康!!(Solved)
EF 带外键关系更新实体问题
2020.09.04更新
使用GraphDiff更新集合,如果集合中某些元素对象也被更改如何通知数据库做出改变?
仅使用 map.AssociatedCollection(p => p.TargetPoints)是不够的,这只能改变TargetPoints的改变,即原来是五个TargetPoints,现在是三个,这可以通知到,而TargetPoint本身的改变是通知不到的,这种情况就需要我们手动通知:
model.TargetPoints.All(t =>
{
myDBContext.UpdateGraph(t,
map => map.AssociatedEntity(p => p.StandardData)
);
return true;
});
2020.08.17更新
在国外论坛发现了这个问题,可以通过使用GraphDiff解决:
基本上发生这种情况是因为EntryState.Modified
仅查找标量属性(原始类型)
Install-Package RefactorThis.GraphDiff
using (var context = new Context())
{
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
使用EF遇到了个问题,记录一下:
场景类型如下,这一个表有两个其他表实例:
public class Sample : Model
{
....
public SampleReference Reference{get;set;}
public SampleBase Base{get;set;}
}
数据上下文设置:
public MyDBContext()
: base("name=Sqlite")
{
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.LazyLoadingEnabled = false;
}
查询:
public List<M> GetAll(Expression<Func<M, bool>> predicate, bool isTrack, params Expression<Func<M, dynamic>>[] expressions)
{
using (MyDBContext myDBContext = new MyDBContext())
{
IQueryable<M> qt = myDBContext.Set<M>();
if (expressions != null && expressions.Any())
foreach (var exp in expressions)
qt = qt.Include(exp);
if (!isTrack) qt = qt.AsNoTracking();
if (predicate != null) qt = qt.Where(predicate);
return qt.ToList();
}
}
执行查询:
var list = _modelHelper.GetAll(t => t.Sample.Base, t => t.Spectrogram, t => t.TestingInfomation);
OK ,查询到一些实例,我对他们进行了一些修改,现在我想要Update他们:
(再次说明一下我对实例怎么修改了,修改后多个实例Sample有着共同的实例Reference)
最开始用的是EF提供的AddorUpdate扩展方法:
myDBContext.Set<Sample>().AddOrUpdate(modelList.ToArray());
报错重复主键,经查询此方法很不智能,是暴力的,你的所有表实例要不都得是新的,要不都得是modify状态(all modifed有待商榷)
之后采用改变实体state方法
myDBContext.Entry(model).State = EntityState.Modified;
问题来了,Sample Entity只能改变Sample表的状态,对于之前include的Sample.SampleBase的改变没有提交效果。
最终我采用了以下方法,很傻很笨:
public static void AddorUpdate(this Sample model, DbContext myDBContext)
{
model.Base?.AddorUpdate(myDBContext);
model.Reference?.AddorUpdate(myDBContext);
if (myDBContext.Set<Sample>().AsNoTracking().FirstOrDefault(t => t.ID == model.ID) != null)
{
myDBContext.Entry(model).State = EntityState.Modified;
}
else
{
myDBContext.Entry(model).State = EntityState.Added;
}
}
public static void AddorUpdate(this SampleBase model, DbContext myDBContext)
{
model.Customer?.AddorUpdate(myDBContext);
if (myDBContext.Set<SampleBase>().AsNoTracking().FirstOrDefault(t => t.ID == model.ID) != null)
{
myDBContext.Entry(model).State = EntityState.Modified;
}
else
{
myDBContext.Entry(model).State = EntityState.Added;
}
}
public static void AddorUpdate(this SampleReference model, DbContext myDBContext)
{
if (myDBContext.Set<SampleReference>().AsNoTracking().FirstOrDefault(t => t.ID == model.ID) != null)
{
myDBContext.Entry(model).State = EntityState.Modified;
}
else
{
myDBContext.Entry(model).State = EntityState.Added;
}
}
显示的去一个一个调,代码写死。
很奇怪,我记得原来用好像都可以自己去追踪更改,不用这么费劲啊,我是哪里做错了呢?
更令人气愤的是,这种写法还跟顺序有关,下面这种写法就不行,要先处理关联表,再处理主表
public static void AddorUpdate(this Sample model, DbContext myDBContext)
{
if (myDBContext.Set<Sample>().AsNoTracking().FirstOrDefault(t => t.ID == model.ID) != null)
{
myDBContext.Entry(model).State = EntityState.Modified;
}
else
{
myDBContext.Entry(model).State = EntityState.Added;
}
model.Base?.AddorUpdate(myDBContext);
model.Reference?.AddorUpdate(myDBContext);
}
此时的Sql,没保存,类似于这样:
UPDATE [Sample] xxxxxxxxxxxxx
WHERE ([ID] = @p0) AND SampleBases_ID IS NULL AND Reference_ID IS NULL;
-- @p0: '20200814151039694' (Type = String)
-- @p1: '310946eb64ec4515a10515f6481aaf90' (Type = String)
-- @p2: 'b90f94bc13f448798530bf2f20a9e837' (Type = String)
-- @p3: '0' (Type = Double)
-- @p4: '9fc574f6518b4e3893c47371c64d9ec2' (Type = String)
-- Executing at 2020/8/14 15:11:10 +08:00
-- Completed in 0 ms with result: 1
AND SampleBases_ID IS NULL AND Reference_ID IS NULL!!!!!!!!
是按照顺序执行的sql,你不先写这两个关联标的modified,他认为是null的查找条件!