Che*_*hen 134 c# asp.net-mvc entity-framework asp.net-web-api
这两个实体是一对多的关系(由代码第一流畅的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; }
}
Run Code Online (Sandbox Code Playgroud)
在我的WebApi控制器中,我有动作创建父实体(工作正常)并更新父实体(有一些问题).更新操作如下所示:
public void Update(UpdateParentModel model)
{
//what should be done here?
}
Run Code Online (Sandbox Code Playgroud)
目前我有两个想法:
获取一个名为existing
by 的跟踪父实体model.Id
,并将值model
逐个赋值给实体.这听起来很愚蠢.在model.Children
我不知道哪个孩子是新的,哪个孩子被修改(甚至删除).
通过创建一个新的父实体model
,并将其附加到DbContext并保存.但是DbContext如何知道子进程的状态(new add/delete/modified)?
实现此功能的正确方法是什么?
Sla*_*uma 195
由于发布到WebApi控制器的模型与任何实体框架(EF)上下文分离,唯一的选择是从数据库加载对象图(父项包括其子项)并比较已添加,删除或更新.(除非您在分离状态(在浏览器中或在任何地方)使用您自己的跟踪机制跟踪更改,否则我认为这些更改比以下更复杂.)它可能如下所示:
public void Update(UpdateParentModel model)
{
var existingParent = _dbContext.Parents
.Where(p => p.Id == model.Id)
.Include(p => p.Children)
.SingleOrDefault();
if (existingParent != null)
{
// Update parent
_dbContext.Entry(existingParent).CurrentValues.SetValues(model);
// Delete children
foreach (var existingChild in existingParent.Children.ToList())
{
if (!model.Children.Any(c => c.Id == existingChild.Id))
_dbContext.Children.Remove(existingChild);
}
// Update and Insert children
foreach (var childModel in model.Children)
{
var existingChild = existingParent.Children
.Where(c => c.Id == childModel.Id)
.SingleOrDefault();
if (existingChild != null)
// Update child
_dbContext.Entry(existingChild).CurrentValues.SetValues(childModel);
else
{
// Insert child
var newChild = new Child
{
Data = childModel.Data,
//...
};
existingParent.Children.Add(newChild);
}
}
_dbContext.SaveChanges();
}
}
Run Code Online (Sandbox Code Playgroud)
...CurrentValues.SetValues
可以根据属性名称将任何对象和属性值映射到附加实体.如果模型中的属性名称与实体中的名称不同,则无法使用此方法,并且必须逐个分配值.
bre*_*man 10
我一直在搞这样的事......
protected void UpdateChildCollection<Tparent, Tid , Tchild>(Tparent dbItem, Tparent newItem, Func<Tparent, IEnumerable<Tchild>> selector, Func<Tchild, Tid> idSelector) where Tchild : class
{
var dbItems = selector(dbItem).ToList();
var newItems = selector(newItem).ToList();
if (dbItems == null && newItems == null)
return;
var original = dbItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>();
var updated = newItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>();
var toRemove = original.Where(i => !updated.ContainsKey(i.Key)).ToArray();
var removed = toRemove.Select(i => DbContext.Entry(i.Value).State = EntityState.Deleted).ToArray();
var toUpdate = original.Where(i => updated.ContainsKey(i.Key)).ToList();
toUpdate.ForEach(i => DbContext.Entry(i.Value).CurrentValues.SetValues(updated[i.Key]));
var toAdd = updated.Where(i => !original.ContainsKey(i.Key)).ToList();
toAdd.ForEach(i => DbContext.Set<Tchild>().Add(i.Value));
}
Run Code Online (Sandbox Code Playgroud)
您可以使用以下内容调用:
UpdateChildCollection(dbCopy, detached, p => p.MyCollectionProp, collectionItem => collectionItem.Id)
Run Code Online (Sandbox Code Playgroud)
不幸的是,如果子类型上还有需要更新的集合属性,这种情况就会失败.考虑尝试通过传递IRepository(使用基本CRUD方法)来解决这个问题,该IRepository将负责自己调用UpdateChildCollection.将调用repo而不是直接调用DbContext.Entry.
不知道这将如何大规模地执行,但不知道还有什么可以解决这个问题.
小智 7
如果您正在使用EntityFrameworkCore,则可以在控制器后期操作中执行以下操作(Attach方法递归地附加导航属性,包括集合):
_context.Attach(modelPostedToController);
IEnumerable<EntityEntry> unchangedEntities = _context.ChangeTracker.Entries().Where(x => x.State == EntityState.Unchanged);
foreach(EntityEntry ee in unchangedEntities){
ee.State = EntityState.Modified;
}
await _context.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)
假设已更新的每个实体具有在来自客户端的发布数据中设置和提供的所有属性(例如,将不用于实体的部分更新).
您还需要确保为此操作使用新的/专用实体框架数据库上下文.
好了朋友们。我曾经有这个答案,但一路迷路了。当您知道有更好的方法但不记得或找不到它时,绝对折磨!非常简单 我只是对其进行了多种测试。
var parent = _dbContext.Parents
.Where(p => p.Id == model.Id)
.Include(p => p.Children)
.FirstOrDefault();
parent.Children = _dbContext.Children.Where(c => <Query for New List Here>);
_dbContext.Entry(parent).State = EntityState.Modified;
_dbContext.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
您可以将整个列表替换为新列表!SQL代码将根据需要删除和添加实体。无需为此担心。确保包括儿童收集或没有骰子。祝好运!
小智 6
public async Task<IHttpActionResult> PutParent(int id, Parent parent)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != parent.Id)
{
return BadRequest();
}
db.Entry(parent).State = EntityState.Modified;
foreach (Child child in parent.Children)
{
db.Entry(child).State = child.Id == 0 ? EntityState.Added : EntityState.Modified;
}
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ParentExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok(db.Parents.Find(id));
}
Run Code Online (Sandbox Code Playgroud)
这就是我解决这个问题的方法。这样,EF 就知道要添加哪些要更新。
小智 5
这应该可以做到...
private void Reconcile<T>(DbContext context,
IReadOnlyCollection<T> oldItems,
IReadOnlyCollection<T> newItems,
Func<T, T, bool> compare)
{
var itemsToAdd = new List<T>();
var itemsToRemove = new List<T>();
foreach (T newItem in newItems)
{
T oldItem = oldItems.FirstOrDefault(arg1 => compare(arg1, newItem));
if (oldItem == null)
{
itemsToAdd.Add(newItem);
}
else
{
context.Entry(oldItem).CurrentValues.SetValues(newItem);
}
}
foreach (T oldItem in oldItems)
{
if (!newItems.Any(arg1 => compare(arg1, oldItem)))
{
itemsToRemove.Add(oldItem);
}
}
foreach (T item in itemsToAdd)
context.Add(item);
foreach (T item in itemsToRemove)
context.Remove(item);
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
96196 次 |
最近记录: |