代码优先 Entity Framework 多对多集合
Code-First Entity Framework Multiple Collections with Many to Many
我还有一个 Entity Framework 问题。我有一个名为 Book 的复杂对象,该对象有许多 Contributor 类型的集合,例如 Writer、Letterer、Colorist 等。Contributors 不一定限于特定角色。因此,例如,同一个贡献者(具有相同的 ContributorId)可能既是作家又是调色师。
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 模型。我想避免的是一个模型,其中我为每个贡献者角色都有一个单独的 table,或者在贡献者中有一个单独的行 table 对于贡献者与任何角色的一本书相关联的每个实例.
+ Books
--BookId
+ Contributors
--ContributorId
+ BookContributors
--BookId
--ContributorId
--Discriminator
我和 ADO.NET 一样,我并不真的觉得这太有趣了,但我决心至少精通这个重要的框架。
快速说明:
自从打开这个问题后,我就被工作抽离了,没有时间彻底复习答案并玩弄结果。但我不想让赏金悬而未决,因为我很感激每个人提供的答案。所以我一开始就选择了我最感兴趣的答案。尽管如此,我还是要感谢大家。
我已经研究了一个解决方案来实现您提出的模型,尽管它的工作方式与您预期的有点不同。希望这能回答您的问题。
型号
[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
}
}
}
}
我在 test.polarcomputer.com
写书
如果你有一个书对象,而这个对象有作家、出版商、设计师......任何人,
你只需要 3 个对象:
1.book 对象
2.contributor 对象。
3.integration对象
图书对象有
- bookid
- 书名
贡献者对象有
- 贡献者编号
- 姓名
- typeofcontributor // 0-writer 1-colorist 2-CoverArtists 3-whoever
集成对象有
- bookid
- 贡献者编号
- typeofcontributor // 0-writer 1-colorist 2-CoverArtists 3-whoever
检查这个如果我真的理解它,我可以给你完整的解决方案。
使用 M:N 映射在 Contributor 实体中创建类似的集合,并使用 InverseProperty 属性声明 Contributor class 中的哪个集合对应于 Book class 中的哪个集合。
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.");
}
}
你展示的数据模型没问题,但有一点很清楚。您不能将其映射为纯粹的多对多关联。这只有在结点 table BookContributors
只包含 BookId
和 ContributorId
.
时才有可能
所以你总是需要一个明确的 BookContributor
class,并且获取其中一种贡献者类型的集合总是会采用这种基本形状:
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});
}
(顺便说一句,这里我避免使用 'Discriminator' 一词,因为它暗示 TPH 继承,目前尚不适用)。
现在您可以像这样向 Book
添加属性:
[NotMapped]
public IEnumerable<Contributor> Writers
{
get
{
return BookContributors.Where(bc => bc.Type == "writer")
.Select(bc => bc.Contributor);
}
}
这种方法的缺点是您始终必须确保加载书籍时包含 BookContributors
和 Contributor
,或者延迟加载是可能的。并且您不能在 LINQ 查询中直接使用这些属性。此外,很难获得书籍并且只有他们的独特贡献者(即独特的)。
选项 2:继承 - 本质上相同
您可以使 BookContributor
成为具有多个继承者的抽象基础 class:
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();
}
}
...但是,考虑到上述所有注意事项。
你的模型应该是这样的:
[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; }
}
我还有一个 Entity Framework 问题。我有一个名为 Book 的复杂对象,该对象有许多 Contributor 类型的集合,例如 Writer、Letterer、Colorist 等。Contributors 不一定限于特定角色。因此,例如,同一个贡献者(具有相同的 ContributorId)可能既是作家又是调色师。
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 模型。我想避免的是一个模型,其中我为每个贡献者角色都有一个单独的 table,或者在贡献者中有一个单独的行 table 对于贡献者与任何角色的一本书相关联的每个实例.
+ Books
--BookId
+ Contributors
--ContributorId
+ BookContributors
--BookId
--ContributorId
--Discriminator
我和 ADO.NET 一样,我并不真的觉得这太有趣了,但我决心至少精通这个重要的框架。
快速说明: 自从打开这个问题后,我就被工作抽离了,没有时间彻底复习答案并玩弄结果。但我不想让赏金悬而未决,因为我很感激每个人提供的答案。所以我一开始就选择了我最感兴趣的答案。尽管如此,我还是要感谢大家。
我已经研究了一个解决方案来实现您提出的模型,尽管它的工作方式与您预期的有点不同。希望这能回答您的问题。
型号
[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
}
}
}
}
我在 test.polarcomputer.com
写书
如果你有一个书对象,而这个对象有作家、出版商、设计师......任何人,
你只需要 3 个对象:
1.book 对象
2.contributor 对象。
3.integration对象
图书对象有
- bookid
- 书名
贡献者对象有
- 贡献者编号
- 姓名
- typeofcontributor // 0-writer 1-colorist 2-CoverArtists 3-whoever
集成对象有
- bookid
- 贡献者编号
- typeofcontributor // 0-writer 1-colorist 2-CoverArtists 3-whoever
检查这个如果我真的理解它,我可以给你完整的解决方案。
使用 M:N 映射在 Contributor 实体中创建类似的集合,并使用 InverseProperty 属性声明 Contributor class 中的哪个集合对应于 Book class 中的哪个集合。
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.");
}
}
你展示的数据模型没问题,但有一点很清楚。您不能将其映射为纯粹的多对多关联。这只有在结点 table BookContributors
只包含 BookId
和 ContributorId
.
所以你总是需要一个明确的 BookContributor
class,并且获取其中一种贡献者类型的集合总是会采用这种基本形状:
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});
}
(顺便说一句,这里我避免使用 'Discriminator' 一词,因为它暗示 TPH 继承,目前尚不适用)。
现在您可以像这样向 Book
添加属性:
[NotMapped]
public IEnumerable<Contributor> Writers
{
get
{
return BookContributors.Where(bc => bc.Type == "writer")
.Select(bc => bc.Contributor);
}
}
这种方法的缺点是您始终必须确保加载书籍时包含 BookContributors
和 Contributor
,或者延迟加载是可能的。并且您不能在 LINQ 查询中直接使用这些属性。此外,很难获得书籍并且只有他们的独特贡献者(即独特的)。
选项 2:继承 - 本质上相同
您可以使 BookContributor
成为具有多个继承者的抽象基础 class:
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();
}
}
...但是,考虑到上述所有注意事项。
你的模型应该是这样的:
[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; }
}