如何使用List<;TEntity>;
本文关键字:TEntity gt 何使用 lt List | 更新日期: 2023-09-27 18:00:02
我想在实体框架中更新特定书籍的作者列表(多对多关系)。
EF生成的模型如下所示:
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
public ICollection<Author> Authors { get; set; }
}
public class Author
{
public int AuthorId { get; set; }
public string AuthorName { get; set; }
public ICollection<Book> Books { get; set; }
}
我有一个图书实例(poltava),它有许多当前分配的作者(currentAuthors)。现在我想为那本书指定一个新的作者列表。下面是我在代码中的表示:
List<Author> currentAuthors = new List<Author>
{
new Author { AuthorName = "Pushkin" },
new Author { AuthorName = "Anton Delvig"}
}
Book poltava = new Book
{
BookName = "Poltava",
Authors = currentAuthors
};
List<Author> newAuthors = new List<Author>
{
new Author { AuthorName = "Aleksandra Ishimova" },
new Author { AuthorName = "Vladimir Dal"},
new Author { AuthorName = "Anton Delvig"}
}
poltava.Authors = newAuthors;
如何使用实体框架实现这一点?
如果您查看数据库。实体框架自动在书籍和作者之间创建了一个桥接表。你可以为这个桥接表创建一个类,这将允许你插入或更新。
Entity Framework会自动为n:m关系创建一个联接表,除非您明确将其添加到现有的实体层次结构中,否则该联接表不会出现在DbContext中。
基于您的代码示例,我扩展了实现,并添加了名为"Author_Book"的连接表,该表在数据库中应显示为"Authors_Books"。
更新1:
我还添加了一个示例(SwitchAuthorsWithoutJoinTable()),该示例不使用联接表作为实体的一部分来操作n:m关系(在本例中为Novel<->Novelist)。这就是你要找的吗
更新2:
现在,作为如何在n:m关系的集合中添加和删除单个实体的示例,我添加了带有相应注释的方法DeletingAndAddingSingleNovelists()
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace EntityFrameworkManyToMany
{
#region Entities
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
public ICollection<Author_Book> Authors { get; set; }
}
public class Author
{
public int AuthorId { get; set; }
public string AuthorName { get; set; }
public ICollection<Author_Book> Books { get; set; }
}
public class Author_Book
{
// has 1 Author
public int AuthorId { get; set; }
// has 1 Book
public int BookId { get; set; }
public Author Author { get; set; }
public Book Book { get; set; }
}
#endregion Entities
#region Entities without 'join table'
public class Novel
{
public int NovelId { get; set; }
public string Title { get; set; }
public ICollection<Novelist> Novelists { get; set; }
}
public class Novelist
{
public int NovelistId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Novel> Novels { get; set; }
}
#endregion Entities without 'join table'
#region Mock DbContext
public class DbContext : IDisposable
{
// Mock DbSet<T> classes
public List<Author> AuthorsDbSet { get; set; }
public List<Book> BooksDbSet { get; set; }
public List<Author_Book> Author_BooksDbSet { get; set; }
public List<Novel> NovelsDbSet { get; set; }
public List<Novelist> NovelistsDbSet { get; set; }
// so we can use 'using'
public void Dispose()
{
}
// Mock SaveChanges Method from EF's DbContext-Class
public void SaveChanges()
{
}
}
#endregion Mock DbContext
#region DbOperations for initial seeding, then switching and reassiging
public class DbOperations
{
// fill database with values
public void SeedDb()
{
seedBookAuthors();
seedNovelsNovelists();
}
private void seedBookAuthors()
{
var authors = new List<Author>
{
new Author {AuthorId = 1, AuthorName = "Marc Twain"},
new Author {AuthorId = 2, AuthorName = "Pushkin"}
};
var books = new List<Book>
{
new Book {BookId = 1, BookName = "American Literary Regionalism"},
new Book {BookId = 2, BookName = "American Realism"},
new Book {BookId = 3, BookName = "The Bronze Horseman"},
new Book {BookId = 4, BookName = "Poltava"},
};
using (var ctx = new DbContext())
{
ctx.AuthorsDbSet.AddRange(authors);
ctx.BooksDbSet.AddRange(books);
ctx.SaveChanges();
}
using (var ctx = new DbContext())
{
var pushkin = ctx.AuthorsDbSet.FirstOrDefault(a =>
a.AuthorName.ToLower() == "pushkin");
var pushkinsBooks = ctx.BooksDbSet.Where(b => b.BookId > 2).ToList();
var pushkinAuthor_Books = pushkinsBooks.Select(pb => new Author_Book
{
AuthorId = pushkin.AuthorId,
BookId = pb.BookId
}).ToList();
ctx.Author_BooksDbSet.AddRange(pushkinAuthor_Books);
var twain = ctx.AuthorsDbSet.FirstOrDefault(a =>
a.AuthorName.ToLower() == "Marc Twain");
var twainsBooks = ctx.BooksDbSet.Where(b => b.BookId < 3).ToList();
var twainAuthor_Books = twainsBooks.Select(tb =>
new Author_Book
{
AuthorId = twain.AuthorId,
BookId = tb.BookId
}).ToList();
ctx.Author_BooksDbSet.AddRange(twainAuthor_Books);
ctx.SaveChanges();
}
}
private void seedNovelsNovelists()
{
var novels = new List<Novel>
{
new Novel {Title = "Crime and Punishment", NovelId = 1}, // Fyodor Dostoyevsky
new Novel {Title = "Notes from Underground", NovelId = 5}, // Fyodor Dostoyevsky
new Novel {Title = "War and Peace", NovelId = 2}, // Leo Tolstoy
new Novel {Title = "The Idiot", NovelId = 4}, // Leo Tolstoy
new Novel {Title = "The Master and Margarita", NovelId = 3} // Mikhail Bulgakov
};
var writers = new List<Novelist>
{
new Novelist {FirstName = "Fyodor", LastName = "Dostoyevsky", NovelistId = 1},
new Novelist {FirstName = "Leo", LastName = "Tolstoy", NovelistId = 2},
new Novelist {FirstName = "Mikhail", LastName = "Bulgakov", NovelistId = 3},
};
using (var ctx = new DbContext())
{
ctx.NovelsDbSet.AddRange(novels);
ctx.NovelistsDbSet.AddRange(writers);
ctx.SaveChanges();
}
using (var ctx = new DbContext())
{
var dostoyevsky = ctx.NovelistsDbSet.FirstOrDefault(d => d.NovelistId == 1);
var crime = ctx.NovelsDbSet.FirstOrDefault(n => n.NovelId == 1);
var underground = ctx.NovelsDbSet.FirstOrDefault(n => n.NovelId == 5);
dostoyevsky.Novels.Add(crime);
underground.Novelists.Add(dostoyevsky);
var tolstoy = ctx.NovelistsDbSet.FirstOrDefault(d => d.NovelistId == 2);
var war = ctx.NovelsDbSet.FirstOrDefault(n => n.NovelId == 2);
var idiot = ctx.NovelsDbSet.FirstOrDefault(n => n.NovelId == 4);
tolstoy.Novels.Add(war);
idiot.Novelists.Add(tolstoy);
var bulgakov = ctx.NovelistsDbSet.FirstOrDefault(d => d.NovelistId == 3);
var master = ctx.NovelsDbSet.FirstOrDefault(n => n.NovelId == 3);
master.Novelists.Add(bulgakov);
ctx.SaveChanges();
}
}
/// <summary>
/// Example using the join table 'Authors_Tables'
/// as part of the Entities to do operations on the n:m-relationship
/// between Author <-> Book
/// </summary>
public void SwitchAuthorsWithJoinTable()
{
using (var ctx = new DbContext())
{
var twainsBooks = ctx.Author_BooksDbSet.Where(ab =>
ab.AuthorId == ctx.AuthorsDbSet.FirstOrDefault(a =>
a.AuthorName.ToLower() == "marc twain").AuthorId).ToList();
var pushkinsBooks = ctx.Author_BooksDbSet.Where(ab =>
ab.AuthorId == ctx.AuthorsDbSet.FirstOrDefault(a =>
a.AuthorName.ToLower() == "marc pushkin").AuthorId).ToList();
// assign all of Twain's books to Pushkin
twainsBooks.ForEach(tb => tb.AuthorId = pushkinsBooks.FirstOrDefault().AuthorId);
// assign all of Pushkin's books to Twain
pushkinsBooks.ForEach(pb => pb.AuthorId = twainsBooks.FirstOrDefault().AuthorId);
// EF tracks the changes and a call to SaveChanges
// propagates the modifications to the database
ctx.SaveChanges();
}
}
/// <summary>
/// Example for operations on the n:m-relationship between
/// Novelist <-> Novel without the join table 'Novelists_Novels'
/// being part of the Entities.
///
/// I. All Books from Dostoyevsky to Tolstoy.
/// II. Add Bulgakov as Author of 'War and Peave'
/// </summary>
public void SwitchAuthorsWithoutJoinTable()
{
// I.
using (var ctx = new DbContext())
{
var dostoyevsky = ctx.NovelistsDbSet.FirstOrDefault(n => n.LastName == "Dostoyevsky");
var tolstoy = ctx.NovelistsDbSet.FirstOrDefault(n => n.LastName == "Tolstoy");
var booksOfDostoyevsky = dostoyevsky.Novels.ToList();
// add all novels of Dostoyevsky to Tolstoy
booksOfDostoyevsky.ForEach(b => tolstoy.Novels.Add(b));
// clear all novels belonging to Dostoyevsky
dostoyevsky.Novels.Clear();
// save it all!
ctx.SaveChanges();
}
// II.
using (var ctx = new DbContext())
{
Func<string, string, bool> compare = (target, pattern) =>
target.ToLower().Contains(pattern.ToLower());
var war = ctx.NovelsDbSet.FirstOrDefault(n => compare(n.Title, "war"));
var bulgakov = ctx.NovelistsDbSet.FirstOrDefault(n => n.LastName == "Bulgakov");
war.Novelists.Add(bulgakov);
ctx.SaveChanges();
}
}
public void AssignEverythingToTwain()
{
Author twain;
List<Book> nonTwainBooks;
using (var ctx = new DbContext())
{
twain = ctx.AuthorsDbSet.FirstOrDefault(a => a.AuthorName.ToLower() == "marc twain");
// find all books that are not from Twain in the "Books"-Table
nonTwainBooks = ctx.BooksDbSet.Where(b => b.Authors.Any(a =>
a.AuthorId != twain.AuthorId)).ToList();
// find all Author_Book entities for authors that are not Twain
// this search is done in the join table 'Authors_Books'
var nonTwain_AuthorBooks = ctx.Author_BooksDbSet.Where(ab =>
nonTwainBooks.Any(nonTwainBook => nonTwainBook.BookId == ab.BookId)).ToList();
// finally remove all those entries in the join table 'Authors_Books'
nonTwain_AuthorBooks.ForEach(nonTB => ctx.Author_BooksDbSet.Remove(nonTB));
// propagate changes to database
ctx.SaveChanges();
}
using (var ctx = new DbContext())
{
// create new 'Author_Book' entities for all books that previously where not Twains
var reassigningBooksToTwain = nonTwainBooks.Select(b => new Author_Book
{
AuthorId = twain.AuthorId,
BookId = b.BookId
}).ToList();
// add those entities to the database
ctx.Author_BooksDbSet.AddRange(reassigningBooksToTwain);
// propagate changes to database
ctx.SaveChanges();
}
}
/// <summary>
/// Instead of replacing entire collections of Novelists
/// in order to remove or add a single entity to the relationship
/// its possible to just add or delete single records
///
/// I. Remove Dostoyevsky as Novelist from 'War and Peace'
/// II. Add Tolstoy and Bulgakov as additional Novelists to 'Crime and Punishment'
/// </summary>
public void DeletingAndAddingSingleNovelists()
{
// I.
using (var ctx = new DbContext())
{
// get the novel 'War and Peace' from the database
var war = ctx.NovelsDbSet.FirstOrDefault(n => n.Title == "War and Peace");
Novelist dostoyevsky = null;
// havent done that so far but you should
// always check if the query actually returned something
if (war != null)
{
// EF's LazyLoading mechanism allows you to query/traverse navigation properties
// on your entities which is the ICollection<Novelist> Novelists on Novels
// it automatically loads the associated entities which are the novelists
dostoyevsky = war.Novelists.FirstOrDefault(n => n.LastName == "Dostoyevsky");
}
if (dostoyevsky != null)
{
// remove Dostoyevsky from 'War and Peace'
war.Novelists.Remove(dostoyevsky);
}
// save it all!
ctx.SaveChanges();
}
// II.
using (var ctx = new DbContext())
{
var novelists = ctx.NovelistsDbSet.Where(n =>
n.LastName == "Tolstoy"
|| n.LastName == "Bulgakov");
if (novelists != null && novelists.Any())
{
var crime = ctx.NovelsDbSet.FirstOrDefault(n => n.Title == "Crime and Punishment");
if (crime != null)
{
Debug.WriteLine(String.Format("'{2]' currenty has {0} {1}.",
crime.Novelists.Count, crime.Novelists.Count == 1 ? "Author" : "Authors", crime.Title));
// assign the targeted novelists to 'Crime and Punishment'
novelists.ToList().ForEach(n => crime.Novelists.Add(n));
// save it all!
ctx.SaveChanges();
}
}
}
}
}
#endregion DbOperations for initial seeding, then switching and reassiging
#region Execution and verifying results
public static class Core
{
public static void Main(string[] args)
{
var dbOps = new DbOperations();
dbOps.SeedDb();
AssertAuthorSwitch(dbOps);
dbOps.SwitchAuthorsWithoutJoinTable();
}
public static bool AssertAuthorSwitch(DbOperations dbOps)
{
string pushkin_Poltava = "Poltava";
string twain_AmericanRealism = "American Realism";
Author twain;
Author pushkin;
Book americanRealism;
Book poltava;
bool hasPassedBefore = false;
bool hasPassedAfter = false;
using (var ctx = new DbContext())
{
twain = ctx.AuthorsDbSet.FirstOrDefault(a => a.AuthorName == "Marc Twain");
pushkin = ctx.AuthorsDbSet.FirstOrDefault(a => a.AuthorName == "Pushkin");
americanRealism = twain.Books.FirstOrDefault(b =>
b.Book.BookName == twain_AmericanRealism).Book;
poltava = pushkin.Books.FirstOrDefault(b =>
b.Book.BookName == pushkin_Poltava).Book;
}
//before
using (var ctx = new DbContext())
{
var isPushkins = ctx.Author_BooksDbSet.Any(ab =>
ab.AuthorId == pushkin.AuthorId
&& ab.BookId == poltava.BookId);
var isTwains = ctx.Author_BooksDbSet.Any(ab =>
ab.AuthorId == twain.AuthorId
&& ab.BookId == americanRealism.BookId);
hasPassedBefore = isPushkins && isTwains;
Debug.WriteLine("AssertAuthorSwitch {0} asserts to {1}", "before", hasPassedBefore);
}
dbOps.SwitchAuthorsWithJoinTable();
//after
using (var ctx = new DbContext())
{
var isPushkins = ctx.Author_BooksDbSet.Any(ab =>
ab.AuthorId == pushkin.AuthorId
&& ab.BookId == americanRealism.BookId);
var isTwains = ctx.Author_BooksDbSet.Any(ab =>
ab.AuthorId == twain.AuthorId
&& ab.BookId == poltava.BookId);
hasPassedAfter = isPushkins && isTwains;
Debug.WriteLine(String.Format("AssertAuthorSwitch {0} asserts to {1}", "after", hasPassedAfter));
}
Debug.WriteLine(String.Format("AssertAuthorSwitch asserts to {0}", hasPassedBefore && hasPassedAfter));
return hasPassedBefore && hasPassedAfter;
}
public static bool AssertAssigningToTwain(DbOperations dbOps)
{
// you get the idea
return true;
}
}
#endregion Execution and verifying results
}
我希望这有助于进一步澄清问题。我必须承认我有点罗嗦,但那是因为我此刻极度无聊。)
如果你仍然不确定,请提供更详细的问题描述,我很乐意回复你。