我一直在做一些研究,寻找一种方法来简化代码以更新实体框架中的一对多关系,关于如何更新一对多关系有很多类似的问题但我找不到任何简单的解决方案。
假设我有以下模型类:
public class Author
{
public int AuthorId { get; set; }
...
public virtual ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
...
public int? AuthorId { get; set; }
public virtual Author Author { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasOptional(b => b.Author)
.WithMany(v => v.Books)
.HasForeignKey(s => s.AuthorId)
.WillCascadeOnDelete(true);
...
考虑到我想要的断开连接的场景:
我的第一个方法是清除作者的书籍集并再次设置一个新书:
var author = authorRepository.GetAuthor(authorId);
author.Books.Clear();
author.Books = updatedBooksCollection;
authorRepository.UpdateAuthor(author);
因此,这种方法有效,但它会在数据库中创建孤立记录。
解决此问题的一种方法是使用识别关系:
public class Book
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int BookId { get; set; }
...
[Key, Column(Order = 2), ForeignKey("Author")]
public int AuthorId { get; set; }
public virtual Author Author { get; set; }
}
当我清除集合并设置一个新集合时,这很正常但我必须更改我的表架构添加复合键。
经过一段时间的研究,我发现这篇文章(http://www.entityframeworktutorial.net/EntityFramework4.3/update-one-to-many-entity-using-dbcontext.aspx)清楚地解释了如何在断开连接的场景中管理一对多关系更新。
我将此方法添加到我的存储库类:
private void UpdateBooks(IEnumerable<Book> existingBooks, IEnumerable<Book> updatedBooks)
{
var addedBooks = updatedBooks.Except(existingBooks, x => x.BookId);
var deletedBooks = existingBooks.Except(updatedBooks, x => x.BookId);
var modifiedBooks = updatedBooks.Except(addedBooks, x => x.BookId);
addedBooks.ToList<Book>().ForEach(x => UnitOfWork.Context.Entry(x).State = EntityState.Added);
deletedBooks.ToList<Book>().ForEach(x => UnitOfWork.Context.Entry(x).State = EntityState.Deleted);
foreach (var book in modifiedBooks)
{
var existingBook = UnitOfWork.Context.Set<Book>().Find(book.BookId);
if (existingBook != null)
{
var contextBook = UnitOfWork.Context.Entry(existingBook);
contextBook.CurrentValues.SetValues(book);
}
}
UnitOfWork.Context.SaveChanges();
}
这很好用,但似乎很多代码只是为了处理一些简单的事情作为集合的更新,我不想为我想要更新的模型中的每个集合执行此操作它
我的问题是:是否有一种简化的方法来处理实体框架中的一对多关系更新而不更改表架构,我的意思是不添加识别关系?
答案 0 :(得分:3)
我认为只要做一些小改动,你所拥有的就好了。
var author = authorRepository.GetAuthor(authorId);
//author.Books.Clear();
//author.Books.ToList().ForEach(b => bookRepository.RemoveBook(b)); (Option 1)
context.Books.RemoveRange(author.Books); // (Option 2)
author.Books = updatedBooksCollection;
authorRepository.UpdateAuthor(author);
清除集合会导致您的孤儿记录,因为Clear
仅从书中删除作者的FK参考。清除引用时,FK设置为null。
答案 1 :(得分:0)
好的,虽然我接受了@ Shoe的答案,但我找到了另一种简单且可重复使用的方法来处理这个问题。 我决定在我的BaseRepository中使用泛型方法,所以我可以通过调用它来重用所有其他集合:
UpdateRelatedEntityCollection<Book>(existingBooks, updatedBooks);
这是重构的代码:
BaseRepository 类:
public void UpdateRelatedEntityCollection<T>(IEnumerable<T> existingEntities, IEnumerable<T> updatedEntities) where T : BaseRelatedEntity
{
var addedEntities = updatedEntities.Except(existingEntities, x => x.Id);
var deletedEntities = existingEntities.Except(updatedEntities, x => x.Id);
var modifiedEntities = updatedEntities.Except(addedEntities, x => x.Id);
addedEntities.ToList<T>().ForEach(x => UnitOfWork.Context.Entry(x).State = EntityState.Added);
deletedEntities.ToList<T>().ForEach(x => UnitOfWork.Context.Entry(x).State = EntityState.Deleted);
foreach (var entity in modifiedEntities)
{
var existingEntity = UnitOfWork.Context.Set<T>().Find(entity.Id);
if (existingEntity != null)
{
var contextEntry = UnitOfWork.Context.Entry(existingEntity);
contextEntry.CurrentValues.SetValues(entity);
}
}
}
AuthorRepository 类:
public void UpdateBooks(int id, List<Book> updatedBooks)
{
var author = GetAuthorById(id);
var existingBooks = author.Books.ToList();
UpdateRelatedEntityCollection<Book>(existingBooks, updatedBooks);
UnitOfWork.Context.SaveChanges();
}
不确定这是否是在Entity Framework中管理一对多关系更新的最佳方式,但我发现它对于不复制代码很有用。