一开始对这个没怎么重视,只知道efcore的m2m需要中间表,今天做到这块的时候才发现不会写了...查了很多资料,才找到正确写法。赶紧记录一下
首先模型:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<BookAuthor> Authors { get; set; }
}
public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
public virtual ICollection<BookAuthor> Books { get; set; }
}
public class BookAuthor
{
public int BookId { get; set; }
public int AuthorId { get; set; }
public Book Book { get; set; }
public Author Author { get; set; }
}
public class EfCoreContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<BookAuthor> BookAuthors { get; set; }
public EfCoreContext(DbContextOptions<EfCoreContext> options): base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookAuthor>().HasKey(x => new { x.BookId, x.AuthorId });
}
}
C
这个最简单,直接上代码
[HttpPost]
public IActionResult Create(BookCreateDTO bookDTO )
{
if (bookDTO.Author!=null)
{
bookDTO.Book.Authors = new List<BookAuthor>
{
new BookAuthor
{
Author = bookDTO.Author,
Book = bookDTO.Book,
Order=0
}
};
}
_context.Books.Add(bookDTO.Book);
_context.SaveChanges();
return Ok();
}
// BookCreateDTO
public class BookCreateDTO
{
public Book Book { get; set; }
public Author Author { get; set; }
}
其实这个Author不要也行,等用户创建完毕后再添加也可以。
R
- 使用eagar loading的查询(Include)
[HttpGet]
public IActionResult List()
{
return Ok(_context.Books.Include(x => x.Authors).ThenInclude(y => y.Author).ToList());
}
[HttpGet("{id}")]
public IActionResult One(int id)
{
return Ok(_context.Books.Include(x => x.Authors).ThenInclude(y => y.Author).FirstOrDefault(x=>x.BookId == id));
}
查询结果:
{
"bookId": 2,
"title": "newBook2",
"description": null,
"publishedOn": "0001-01-01T00:00:00",
"publisher": null,
"price": 0,
"imageUrl": null,
"softDeleted": false,
"authorsLink": [
{
"bookId": 2,
"authorId": 1,
"order": 0,
"author": {
"authorId": 1,
"name": "Monodev2",
"bookAuthors": []
}
},
{
"bookId": 2,
"authorId": 2,
"order": 0,
"author": {
"authorId": 2,
"name": "Monodev2",
"bookAuthors": []
}
}
]
}
- 分别查询然后组合
[HttpGet("{id}")]
public IActionResult Detail(int id)
{
List<Author> authors = _context.BookAuthors
.Include(x => x.Author)
.Where(c => c.BookId == id)
.Select(x=>x.Author)
.ToList();
Book book = _context.Books.Single(x => x.BookId == id);
var result = new
{
book=book,
authors = authors
};
return Ok(result);
}
上面方法关键在于Select这个地方,因为_context.BookAuthors.Include(x => x.Author).Where(c => c.BookId == id)
这样查出来是BookAuthor
对象,直接返回客户端数据就会很脏,并且有嵌套,只选择author更符合前端消费习惯
{
"book": {
"bookId": 2,
"title": "newBook2",
"description": null,
"publishedOn": "0001-01-01T00:00:00",
"publisher": null,
"price": 0,
"imageUrl": null,
"softDeleted": false,
"authors": null
},
"authors": [
{
"authorId": 1,
"name": "Monodev2",
"books": null
},
{
"authorId": 2,
"name": "Monodev2",
"books": null
}
]
}
上面两种方法比较明显第二种方法更好。
U
更新是比较麻烦的,首先,需要写一个静态扩展:
public static class Extensions
{
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.ExceptThat(newItems, getKey));
db.Set<T>().AddRange(newItems.ExceptThat(currentItems, getKey));
}
private static IEnumerable<T> ExceptThat<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
}
然后,定义更新模型:
public class BookUpdateDTO
{
public Book Book { get; set; }
public List<int> Authors { get; set; }
}
然后,在Controller中这样写:
[HttpPut]
public IActionResult Update(BookUpdateDTO bookUpdateDTO)
{
var model = _context.Books.Include(x => x.Authors).FirstOrDefault(x => x.BookId == bookUpdateDTO.Book.BookId);
_context.TryUpdateManyToMany(model.Authors, bookUpdateDTO.Authors
.Select(x => new BookAuthor
{
AuthorId = x,
BookId = bookUpdateDTO.Book.BookId
}),x=>x.AuthorId);
_context.SaveChanges();
return Ok();
}
本质上,就是遍历传进的model中的MM,然后用静态扩展中的方法进行更新。
D
[HttpDelete("{id}")]
public IActionResult Remove(int id)
{
var model = _context.Books.FirstOrDefault(x => x.BookId == id);
if (model != null)
{
_context.Books.Remove(model);
_context.SaveChanges();
return Ok();
}
return NoContent();
}
EFCore 默认即为级联删除,所以关联表中bookid==2的数据将被一并删除,当然authors不会被删除:
全文完,希望有帮助