这两个实体是一对多关系(由代码第一个fluent api构建)。
public class Parent
{
public Parent()
{
this.Children = new List<Child>();
}
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Data { get; set; }
}
在我的WebApi控制器中,我有创建父实体(工作正常)和更新父实体(有一些问题)的操作。更新操作如下所示:
public void Update(UpdateParentModel model)
{
//what should be done here?
}
目前我有两个想法:
获取一个被跟踪的父实体,命名为按模型存在的。Id,并将模型中的值逐个分配给实体。这听起来很愚蠢。在模型中。我不知道哪个子是新的,哪个子是修改的(甚至是删除的)。
通过模型创建一个新的父实体,并将其附加到DbContext并保存。但是DbContext如何知道子节点的状态(新增/删除/修改)呢?
实现这个功能的正确方法是什么?
VB。NET开发人员使用这个通用子标记子状态,易于使用
注:
PromatCon:实体对象
amList:要添加或修改的子列表
rList:要删除的子列表
updatechild(objCas.ECC_Decision, PromatCon.ECC_Decision.Where(Function(c) c.rid = objCas.rid And Not objCas.ECC_Decision.Select(Function(x) x.dcid).Contains(c.dcid)).toList)
Sub updatechild(Of Ety)(amList As ICollection(Of Ety), rList As ICollection(Of Ety))
If amList IsNot Nothing Then
For Each obj In amList
Dim x = PromatCon.Entry(obj).GetDatabaseValues()
If x Is Nothing Then
PromatCon.Entry(obj).State = EntityState.Added
Else
PromatCon.Entry(obj).State = EntityState.Modified
End If
Next
End If
If rList IsNot Nothing Then
For Each obj In rList.ToList
PromatCon.Entry(obj).State = EntityState.Deleted
Next
End If
End Sub
PromatCon.SaveChanges()
这不是最优雅的方法,但很有效。干杯!
var entity = await context.Entities.FindAsync(id);
var newEntity = new AmazingEntity() {
p1 = child1
p2 = child2
p3 = child3.child4 //... nested collections
};
if (entity != null)
{
db.Entities.Remove(entity);
}
db.Entities.Add(newEntity);
await db.SaveChangesAsync();
记住去掉PK。
var child4 = Tools.CloneJson(deepNestedElement);
child4.id = 0;
child3.Add(child4);
public static class Tools
{
public static JsonSerializerSettings jsonSettings = new JsonSerializerSettings {
ObjectCreationHandling = ObjectCreationHandling.Replace,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
public static string JSerialize<T>(T source) {
return JsonConvert.SerializeObject(source, Formatting.Indented, jsonSettings);
}
public static T JDeserialize<T>(string source) {
return JsonConvert.DeserializeObject<T>(source, jsonSettings);
}
public static T CloneJson<T>(this T source)
{
return CloneJson<T, T>(source);
}
public static TOut CloneJson<TIn, TOut>(TIn source)
{
if (Object.ReferenceEquals(source, null))
return default(TOut);
return JDeserialize<TOut>(JSerialize(source));
}
}
So, I finally managed to get it working, although not fully automatically.
Notice the AutoMapper <3. It handles all the mapping of properties so you don't have to do it manually. Also, if used in a way where it maps from one object to another, then it only updates the properties and that marks changed properties as Modified to EF, which is what we want.
If you would use explicit context.Update(entity), the difference would be that entire object would be marked as Modified and EVERY prop would be updated.
In that case you don't need tracking but the drawbacks are as mentioned.
Maybe that's not a problem for you but it's more expensive and I want to log exact changes inside Save so I need correct info.
// We always want tracking for auto-updates
var entityToUpdate = unitOfWork.GetGenericRepository<Article, int>()
.GetAllActive() // Uses EF tracking
.Include(e => e.Barcodes.Where(e => e.Status == DatabaseEntityStatus.Active))
.First(e => e.Id == request.Id);
mapper.Map(request, entityToUpdate); // Maps it to entity with AutoMapper <3
ModifyBarcodes(entityToUpdate, request);
// Removed part of the code for space
unitOfWork.Save();
ModifyBarcodes部分在这里。
我们希望以一种EF跟踪不会被打乱的方式修改集合。
不幸的是,AutoMapper映射会创建一个全新的集合实例,因此会搞砸跟踪,尽管,我很确定它应该工作。
无论如何,因为我从FE发送完整的列表,在这里我们实际上决定了应该添加/更新/删除什么,只是处理列表本身。
由于EF跟踪是打开的,EF处理它就像一个魅力。
var toUpdate = article.Barcodes
.Where(e => articleDto.Barcodes.Select(b => b.Id).Contains(e.Id))
.ToList();
toUpdate.ForEach(e =>
{
var newValue = articleDto.Barcodes.FirstOrDefault(f => f.Id == e.Id);
mapper.Map(newValue, e);
});
var toAdd = articleDto.Barcodes
.Where(e => !article.Barcodes.Select(b => b.Id).Contains(e.Id))
.Select(e => mapper.Map<Barcode>(e))
.ToList();
article.Barcodes.AddRange(toAdd);
article.Barcodes
.Where(e => !articleDto.Barcodes.Select(b => b.Id).Contains(e.Id))
.ToList()
.ForEach(e => article.Barcodes.Remove(e));
CreateMap<ArticleDto, Article>()
.ForMember(e => e.DateCreated, opt => opt.Ignore())
.ForMember(e => e.DateModified, opt => opt.Ignore())
.ForMember(e => e.CreatedById, opt => opt.Ignore())
.ForMember(e => e.LastModifiedById, opt => opt.Ignore())
.ForMember(e => e.Status, opt => opt.Ignore())
// When mapping collections, the reference itself is destroyed
// hence f* up EF tracking and makes it think all previous is deleted
// Better to leave it on manual and handle collecion manually
.ForMember(e => e.Barcodes, opt => opt.Ignore())
.ReverseMap()
.ForMember(e => e.Barcodes, opt => opt.MapFrom(src => src.Barcodes.Where(e => e.Status == DatabaseEntityStatus.Active)));