代码优先实体框架具有多对多的

时间:2015-06-04 06:54:31

标签: ef-code-first entity-framework-5

我在这里有另一个实体框架问题。我有一个名为Book的复杂对象,该对象有许多类型为Contributor的集合,例如Writer,Letterer,Colorist等。但贡献者不一定是特定角色的范围。因此,相同的贡献者(具有相同的ContributorId)可以是Writer和Colorist。

public Book {
        public ICollection<Contributor> Writers { get; set; }
        public ICollection<Contributor> Artists { get; set; }
        public ICollection<Contributor> Pencilers { get; set; }
        public ICollection<Contributor> Inkers { get; set; }
        public ICollection<Contributor> Colorists { get; set; }
        public ICollection<Contributor> Letterers { get; set; }
        public ICollection<Contributor> CoverArtists { get; set; }
        public ICollection<Contributor> OtherContributors { get; set; }
}

public Contributor {
     public int ContributorId { get; set; }
     public string Name { get; set; }
}

我遇到了麻烦,查看我在此处和其他网站上找到的示例,确定如何表示相应的模型。我希望Db Model能像这样。我想要避免的是一个模型,其中我为每个贡献者角色都有一个单独的表,或者对于每个贡献者与任何角色的书相关联的实例,在Contributor表中有一个单独的行。

+ Books 
     --BookId
+ Contributors
      --ContributorId
+ BookContributors
      --BookId
      --ContributorId
      --Discriminator

我就像ADO.NET那样,我并不是真的觉得这太令人愉快了,但我决心至少在这个重要的框架中熟练掌握。

快速注释: 自从打开这个问题以来,我在工作中被拉开了,没有时间彻底审查答案并玩弄结果。但我不想放弃赏金,因为我很欣赏每个人提供的答案。所以我选择了对我来说最感兴趣的答案。我想为此感谢大家。

5 个答案:

答案 0 :(得分:3)

我已经开发了一个实现您提出的模型的解决方案,尽管它的工作方式与您预期的有所不同。希望这能回答你的问题。

<强>模型

[Table("Book")]
public class Book
{
    [Column("BookId")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int BookId { get; set; }

    [NotMapped]
    public ICollection<Contributor> Writers { get; set; }

    [NotMapped]
    public ICollection<Contributor> Artists { get; set; }

    [NotMapped]
    public ICollection<Contributor> Pencilers { get; set; }

    [NotMapped]
    public ICollection<Contributor> Inkers { get; set; }

    [NotMapped]
    public ICollection<Contributor> Colorists { get; set; }

    [NotMapped]
    public ICollection<Contributor> Letterers { get; set; }

    [NotMapped]
    public ICollection<Contributor> CoverArtists { get; set; }

    [NotMapped]
    public ICollection<Contributor> OtherContributors { get; set; }
}

[Table("Contributor")]
public class Contributor
{
    [Column("ContributorId")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ContributorId { get; set; }
}

// Contributor Type is one of the following options: Writer, Artist, Penciler, etc.
[Table("ContributorType")]
public class ContributorType
{
    [Column("ContributorTypeId")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ContributorTypeId { get; set; }

    [Column("Name")]
    public string Name { get; set; }
}

[Table("BookContributor")]
public class BookContributor
{
    [Column("BookContributorId")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int BookContributorId { get; set; }

    [Column("BookId")]
    public int BookId { get; set; }

    [Column("ContributorId")]
    public int ContributorId { get; set; }

    [Column("RoleId")]
    public int RoleId { get; set; }

    [ForeignKey("BookId")]
    public virtual Book Book { get; set; }

    [ForeignKey("ContributorId")]
    public virtual Contributor Contributor { get; set; }

    [ForeignKey("RoleId")]
    public virtual ContributorType Role { get; set; }
}

数据库上下文

AppDbContext.cs:

public class AppDbContext : DbContext
{
    public AppDbContext()
    {
        Database.SetInitializer<AppDbContext>(new AppDbInitializer());
    }

    public AppDbContext(string connectionString)
        : base(connectionString)
    {
        Database.SetInitializer<AppDbContext>(new AppDbInitializer());
    }

    public DbSet<Book> Books { get; set; }

    public DbSet<Contributor> Contributors { get; set; }

    public DbSet<ContributorType> ContributorTypes { get; set; }

    public DbSet<BookContributor> BookContributors { get; set; }
}

AppDbInitializer.cs:

public class AppDbInitializer : DropCreateDatabaseAlways<AppDbContext>
{
    protected override void Seed(AppDbContext context)
    {
        // default contributor types
        var contributorTypes = new List<ContributorType>();
        contributorTypes.Add(new ContributorType() { Name = "Writer" });
        contributorTypes.Add(new ContributorType() { Name = "Artist" });
        contributorTypes.Add(new ContributorType() { Name = "Penciler" });
        contributorTypes.Add(new ContributorType() { Name = "Inker" });
        contributorTypes.Add(new ContributorType() { Name = "Colorist" });
        contributorTypes.Add(new ContributorType() { Name = "Letterer" });
        contributorTypes.Add(new ContributorType() { Name = "CoverArtist" });
        contributorTypes.Add(new ContributorType() { Name = "OtherContributor" });

        // adding it to the context
        foreach (var type in contributorTypes)
            context.ContributorTypes.Add(type);

        base.Seed(context);
    }
}

将所有内容整合在一起

的Program.cs:

class Program
{
    static void Main(string[] args)
    {
        // enter name of the connection string in App.Config file
        var connectionSettings = ConfigurationManager.ConnectionStrings["..."];
        using (var dbContext = new AppDbContext(connectionSettings.ConnectionString)) 
        {
            // Creating a book
            var book = new Book();
            dbContext.Books.Add(book);
            dbContext.SaveChanges();

            // Creating contributor
            var contributor = new Contributor();
            dbContext.Contributors.Add(contributor);
            dbContext.SaveChanges();

            // Adding contributor to the book
            var bookContributor = new BookContributor()
            {
                BookId = book.BookId,
                ContributorId = contributor.ContributorId,
                RoleId = dbContext.ContributorTypes.First(t => t.Name == "Writer").ContributorTypeId
            };
            dbContext.BookContributors.Add(bookContributor);
            dbContext.SaveChanges();

            // retrieving a book
            var book = dbContext.Books.Where(b => b.BookId == 2).FirstOrDefault();
            if (book != null) 
            {
                book.Writers = 
                    from contributor in dbContext.Contributors
                    join bookContributor in dbContext.BookContributors on contributor.BookId equals bookContributor.BookId
                    join contributorType in dbContext.ContributorTypes on contributorType.ContributorTypeId equals bookContributor.ContributorTypeId
                    where 
                        bookContributor.BookId == 2 and
                        contributorType.Name == "Writer"
                    select contributor;

                // do the same for other types of contributors
            }
        }
    }
}

答案 1 :(得分:3)

使用M:N映射在Contributor实体中创建类似的集合,并使用InverseProperty属性声明Contributor类中的哪个集合与Book类中的哪个集合相对应。

public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Contributor> Writers { get; set; }
    public virtual ICollection<Contributor> Artists { get; set; }
    public virtual ICollection<Contributor> Pencilers { get; set; }
    public virtual ICollection<Contributor> Inkers { get; set; }
    public virtual ICollection<Contributor> Colorists { get; set; }
    public virtual ICollection<Contributor> Letterers { get; set; }
    public virtual ICollection<Contributor> CoverArtists { get; set; }
    public virtual ICollection<Contributor> OtherContributors { get; set; }

    public Book()
    {
        Writers = new List<Contributor>();
        Artists = new List<Contributor>();
        Pencilers = new List<Contributor>();
        Inkers = new List<Contributor>();
        Colorists = new List<Contributor>();
        Letterers = new List<Contributor>();
        CoverArtists = new List<Contributor>();
        OtherContributors = new List<Contributor>();
    }
}

public class Contributor
{
    public int ContributorId { get; set; }
    public string Name { get; set; }

    [InverseProperty("Writers")]
    public virtual ICollection<Book> WriterOfBooks { get; set; }

    [InverseProperty("Artists")]
    public virtual ICollection<Book> ArtistOfBooks { get; set; }

    [InverseProperty("Pencilers")]
    public virtual ICollection<Book> PencilerOfBooks { get; set; }

    [InverseProperty("Inkers")]
    public virtual ICollection<Book> InkerOfBooks { get; set; }

    [InverseProperty("Colorists")]
    public virtual ICollection<Book> ColoristOfBooks { get; set; }

    [InverseProperty("Letterers")]
    public virtual ICollection<Book> LettererOfBooks { get; set; }

    [InverseProperty("CoverArtists")]
    public virtual ICollection<Book> CoverArtistOfBooks { get; set; }

    [InverseProperty("OtherContributors")]
    public virtual ICollection<Book> OtherContributorOfBooks { get; set; }

    public Contributor()
    {
        WriterOfBooks = new List<Book>();
        ArtistOfBooks = new List<Book>();
        PencilerOfBooks = new List<Book>();
        InkerOfBooks = new List<Book>();
        ColoristOfBooks = new List<Book>();
        LettererOfBooks = new List<Book>();
        CoverArtistOfBooks = new List<Book>();
        OtherContributorOfBooks = new List<Book>();
    }
}

使用非常简单:

using (var dc = new MyDbContext())
{
    // create sample data
    var book1 = new Book() { Name = "Book 1" };
    dc.Books.Add(book1);

    var contrib1 = new Contributor() { Name = "Contributor 1" };
    var contrib2 = new Contributor() { Name = "Contributor 2" };
    var contrib3 = new Contributor() { Name = "Contributor 3" };
    dc.Contributors.Add(contrib1);
    dc.Contributors.Add(contrib2);
    dc.Contributors.Add(contrib3);

    dc.SaveChanges();



    // add relationships
    book1.Writers.Add(contrib1);
    book1.Artists.Add(contrib1);
    book1.Artists.Add(contrib2);
    book1.OtherContributors.Add(contrib3);
    dc.SaveChanges();
}

// verify that the contributor 1 has both Artist and Writer relations
using (var dc = new MyDbContext())
{
    var contrib1 = dc.Contributors.Single(c => c.Name == "Contributor 1");
    var hasWriter = contrib1.WriterOfBooks.Count == 1;
    var hasArtist = contrib1.ArtistOfBooks.Count == 1;
    if (!hasWriter || !hasArtist)
    {
        throw new Exception("Houston, we have a problem.");
    }
}

答案 2 :(得分:2)

我正在test.polarcomputer.com上读书 如果你有一个书籍对象,这个对象有作家,出版商,设计师......那么,你只需要3个对象:

1.book对象
2.贡献者对象。
3.整合对象

书籍对象有 - bookid
- 书名

贡献者对象有   - 贡献者
  - 姓名
  - typeofcontributor // 0-writer 1-colorist 2-CoverArtists 3-whoever

集成对象有   - bookid
  - 贡献者
  - typeofcontributor // 0-writer 1-colorist 2-CoverArtists 3-whoever

检查这一点,如果我真的理解它,我可以给你完整的解决方案。

答案 3 :(得分:2)

您显示的数据模型没问题,但有一件事是清楚的。您无法将其映射为纯粹的多对多关联。只有当联结表BookContributors仅包含BookIdContributorId时,才有可能。

所以你总是需要一个明确的BookContributor类,并且获得一个贡献者类型的集合总是会采用这种基本形状:

book.BookContributors
    .Where(bc => bc.Type == type)
    .Select(bc => bc.Contributor)
与你的想法相比,笨拙。但是我害怕,没办法解决它。剩下的是实现细节中的一些选项。

选项1:获取所有贡献者,稍后过滤。

首先,让我们得到正确的基本模型:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public virtual ICollection<BookContributor> BookContributors { get; set; }
}

public class Contributor
{
    public int ContributorId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<BookContributor> BookContributors { get; set; }
}

public class BookContributor
{
    public int BookId { get; set; }
    public virtual Book Book { get; set; }

    public int ContributorId { get; set; }
    public virtual Contributor Contributor { get; set; }

    public string Type { get; set; }
}

映射:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Book>().HasMany(b => b.BookContributors)
        .WithRequired(bc => bc.Book)
        .HasForeignKey(bc => bc.BookId);
    modelBuilder.Entity<Contributor>().HasMany(c => c.BookContributors)
        .WithRequired(bc => bc.Contributor)
        .HasForeignKey(bc => bc.ContributorId);
    modelBuilder.Entity<BookContributor>()
                .HasKey(bc => new {bc.BookId, bc.ContributorId, bc.Type});
}

(顺便说一下,在这里我避免使用“判别器”一词,因为这表明TPH继承,但这不适用)。

现在您可以向Book添加属性,如下所示:

[NotMapped]
public IEnumerable<Contributor> Writers
{ 
    get 
    {
        return BookContributors.Where(bc => bc.Type == "writer")
                               .Select(bc => bc.Contributor);
    }
}

这种方法的缺点是,您始终必须确保书籍加载了BookContributors他们的 Contributor,或者延迟加载是可能的。并且您不能直接在LINQ查询中使用这些属性。此外,获取书籍并且只有他们独特的贡献者(即不同的)有点困难。

选项2:继承 - 基本相同

您可以使BookContributor成为具有多个继承者的抽象基类:

public abstract class BookContributor
{
    public int Id { get; set; }
    public int BookId { get; set; }
    public virtual Book Book { get; set; }
    public int ContributorId { get; set; }
    public virtual Contributor Contributor { get; set; }
}

public class Artist : BookContributor
{ }

public class Writer : BookContributor
{ }

BookContributor现在需要一个代理键Id,因为EF现在将使用隐藏的字段Discriminator,因此无法将其配置为主键的一部分

现在Book可以拥有类似......

的属性
    [NotMapped]
    public ICollection<Artist> Artists
    {
        get { return BookContributors.OfType<Artist>().ToList(); }
    }

......但是这些仍然会有与上面提到的相同的缺点。唯一可能的优点是您现在可以使用类型(使用编译时检查)而不是字符串(或枚举值)来获取各种BookContributor类型。

选项3:不同的模型

也许最有希望的方法是略有不同的模式:书籍和贡献者,他们之间的每个关联都可以有一组贡献者类型。 BookContributor现在看起来像这样:

public class BookContributor
{
    public int BookId { get; set; }
    public virtual Book Book { get; set; }
    public int ContributorId { get; set; }
    public virtual Contributor Contributor { get; set; }

    public virtual ICollection<BookContributorType> BookContributorTypes { get; set; }
}

一种新型:

public class BookContributorType
{
    public int ID { get; set; }
    public int BookId { get; set; }
    public int ContributorId { get; set; }
    public string Type { get; set; }
}

修改后的映射:

modelBuilder.Entity<BookContributor>().HasKey(bc => new { bc.BookId, bc.ContributorId });

附加映射:

modelBuilder.Entity<BookContributor>().HasMany(bc => bc.BookContributorTypes).WithRequired();
modelBuilder.Entity<BookContributorType>().HasKey(bct => bct.ID);

使用此模型,如果您对贡献者的类型不感兴趣,您可以获得书籍及其独特的贡献者......

context.Books.Include(b => b.BookContributors
                            .Select(bc => bc.Contributor))

......或者类型......

context.Books.Include(b => b.BookContributors
                            .Select(bc => bc.Contributor))
             .Include(b => b.BookContributors
                            .Select(bc => bc.BookContributorTypes));

......或只有作家的书......

context.Books.Select(b => new
                     {
                         Book = b,
                         Writers = b.BookContributors
                                    .Where(bc => bc.BookContributorTypes
                                                   .Any(bct => bct.Type == "artist"))
                     })

同样,后一个查询可以包含在属性中......

    [NotMapped]
    public ICollection<Artist> Artists
    {
        get
        {
            return BookContributors
                     .Where(bc => bc.BookContributorTypes
                                .Any(bct => bct.Type == "artist"))
                     .Select(bc => bc.Contributor).ToList();
        }
    }

......但是,所有上述注意事项。

答案 4 :(得分:0)

你的模特应该是这样的:

    [Table("tblBooks")]
    public class BookTbl
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int BookID { get; set; }
        public string BookName { get; set; }
    }

    [Table("tblContributor")]
    public class ContributorTbl
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int ContID { get; set; }
        public string Contributor { get; set; }
    }

    [Table("tblIntegration")]
    public class IntegrationTbl
    {
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int IntID { get; set; }
        public int BookID { get; set; }
        [ForeignKey("BookID")]
        public BookTbl Book { get; set; }
        public int ContID { get; set; }
        [ForeignKey("ContID")]
        public IntegrationTbl Integration { get; set; } 
    }