实体框架 6 和 Web API 中的多对多更新控制器

本文关键字:更新 控制器 API 框架 Web 实体 | 更新日期: 2023-09-27 18:34:14

我无法在 Web API 中获取更新控制器来更新同一调用期间调用的对象和引用的对象。

我有两个类:

public class Foo
{
    public int Id { get; set; }
    [Required]
    [MaxLength(250)]
    public string Title { get; set; }
    ...
    public virtual ICollection<Bar> Bars { get; set; }
}
public class Bar
{
     public int Id { get; set }
     public string Name { get; set; }
     ...
     public virtual ICollection<Foo> Foos { get; set; }
}

和一个控制器

public class BarController : ApiController
{
    private DbContext db = new DbContext();
    [HttpGet]
    public IQueryable<Bar> getBar(int id)
    {
         Bar bar = await db.Bars.FindAsync(id);
         if(bar == null)
         {
             return NotFound();
         }
         return OK(bar);
    }
    [ResponseType(typeof(Bar))]
    public async Task<IHttpActionResult> PostBar(Bar bar)
    {
        if (!ModelState.IsValid)
        {
             return BadRequest(ModelState);
        }
        db.Bar.Add(bar);
        foreach(var foo in bar.Foos)
        {
            db.Foos.Attach(foo);
        }
        await db.SaveChangesAsync();
        return CreatedAtRoute("DefaultApi", new { id = bar.Id }, bar);
    }
    [ResponseType(typeof(void))]
    public async Task(IHttpActionResult) PutBar(int id, Bar bar)
    {
        if (!ModelState.IsValid)
        {
             return BadRequest(ModelState);
        }
        if (id != ticket.Id)
        {
            return BadRequest();
        }
        db.Entry(bar).State = EntityState.Modified;
        await db.SaveChangesAsync();
        return StatusCode(HttpStatusCode.NoContent);
    }
    ...
}

实体框架创建一个正确称为 FooBar 的链接表(在此示例中(,其外键关系分别与 Foo 和 Bar 相关。

">

get"调用解析引用,并返回一个包含柱线对象的其余部分的"foos"数组。

"post"调用也可以正确解析,尽管没有foreach部分,但它会在链接之前创建新的 foo 对象。

但是,"put"调用仅更新 Bar 对象,但不更新指向 Foo 对象的链接。

将 put 控制器更新为:

    [ResponseType(typeof(void))]
    public async Task<IHttpActionResult> PutBar(int id, Bar bar)
    {
        ...
        Bar _bar = db.Bars.Find(id);
        List<Foo> addedFoos = bar.Foos.Except(_bar.Foos,
            new KeyEqualityComparer<Bar, int>(x => x.Id)).ToList();
        List<Foo> removedFoos = _bar.Foos.Except(bar.Foos,
            new KeyEqualityComparer<Bar, int>(x => x.Id)).ToList();
        removedFoos.ForEach(c => _bar.Foos.Remove(c));
        addedFoos.ForEach(c =>
        {
            if (db.Entry(c).State == EntityState.Detached)
            {
                db.Foos.Attach(c);
            }
            _bar.Foos.Add(c);
        });
        await db.SaveChangesAsync();
        return StatusCode(HttpStatusCode.NoContent);
    }

将允许更新控制器更新 Bar 的 Foo 集合,但不允许更新 Bar 的其余部分。

不幸的是,尝试同时使用这两个调用总是会抛出"附加类型的实体"错误,因为每个柱线项将一个 foo 项加载到上下文中,反之亦然。

尝试类似以下内容:

db.Entry(_bar).CurrentValues.SetValues(bar);
db.Entry(_bar).Collection(b => b.Foos).Load();
db.Entry(_bar).Collection(b => b.Foos).CurrentValue = bar.Foos;

导致每次更新时 Foo 和 Bar 的副本数量呈指数级增长。

有什么办法吗?令我感到奇怪的是,实体框架会生成正确的数据库结构,但使其无法工作。

实体框架 6 和 Web API 中的多对多更新控制器

尝试此操作,如果您有问题,请告诉我

Bar _bar = db.Bars.Find(id);
var removed =_bar.Foos.Except(bar.Foos);
var added = bar.Foos.Except(_bar.Foos);
var toRemoveTracked =_bar.Foos.Where(foo => removed.Any(r => r.FooId == foo.FooId))
                              .ToList();
added.ToList().ForEach(_bar.Foos.Add);
toRemoveTracked.ForEach((fooo) => {_bar.Foos.Remove(fooo);});
await db.SaveChangesAsync();

还要从ICollection<Foo>属性中删除延迟加载(通过删除前面的 virtual 关键字来完成ICollection(