如何使用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;

如何使用实体框架实现这一点?

如何使用List<;TEntity>;

如果您查看数据库。实体框架自动在书籍和作者之间创建了一个桥接表。你可以为这个桥接表创建一个类,这将允许你插入或更新。

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
}

我希望这有助于进一步澄清问题。我必须承认我有点罗嗦,但那是因为我此刻极度无聊。)

如果你仍然不确定,请提供更详细的问题描述,我很乐意回复你。