C# 如何在双方都有相同父级的多对多中实现EF联接表

C# 如何在双方都有相同父级的多对多中实现EF联接表,c#,sql-server,entity-framework,entity-framework-core,ef-core-2.0,C#,Sql Server,Entity Framework,Entity Framework Core,Ef Core 2.0,上下文: 我正在为博物馆构建一个藏品管理应用程序。每个实体都有一个博物馆财产,将该实体与相应的博物馆联系起来。来自特定博物馆的用户应该能够通过应用程序维护诸如流派之类的词汇。同样,博物馆可以保存艺术家名单。相关类的简化版本和数据库的图表如下: public class Museum { public Museum() { Artists = new List<Artist>(); Genres = new List<Genre&g

上下文: 我正在为博物馆构建一个藏品管理应用程序。每个实体都有一个博物馆财产,将该实体与相应的博物馆联系起来。来自特定博物馆的用户应该能够通过应用程序维护诸如流派之类的词汇。同样,博物馆可以保存艺术家名单。相关类的简化版本和数据库的图表如下:

public class Museum
{
    public Museum()
    {
        Artists = new List<Artist>();
        Genres = new List<Genre>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public List<Artist> Artists { get; set; }
    public List<Genre> Genres { get; set; }
}

public class Artist
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Museum Museum { get; set; }
    public int MuseumId { get; set; }
}

public class Genre
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Museum Museum { get; set; }
    public int MuseumId { get; set; }
}

我可以删除博物馆并自动删除所有从属实体(艺术家、流派和艺术家流派)。但是,如果不先删除艺术家或流派ID出现在ArtistGene中的任何记录,我仍然无法删除艺术家或流派。

关于层叠的报告不是由艺术家和流派之间的多对多关系引起的,而是由博物馆-艺术家和博物馆-流派之间的一对多关系引起的

这个错误意味着,如果你要删除一个博物馆,它会自动删除它的艺术家和流派。在删除某个流派时,所有在该流派中表演的艺术家都应该进行调整,但您也删除了艺术家,因为博物馆被拆除了,所以我们将在一个圆圈中删除

你可以看到,与博物馆的一对多关系是一个问题,将博物馆从你的背景中移除,并在艺术家和流派之间创建一个标准的多对多关系。这在没有任何属性或fluentapi的情况下工作良好

因此,你必须向模型构建者承诺,你只会移除那些不再有艺术家或流派的博物馆。您必须先删除博物馆中展出的艺术家和流派,然后才能删除博物馆

我还看到了其他一些使用实体框架不必要的奇怪事情。我不确定是否需要它们,因为
ef-core

例如,您的博物馆有一个
艺术家的
列表
,您确定
艺术家[17]
是一项有意义的活动吗?为什么要在构造函数中创建这些列表?如果从数据库中获取艺术家,这些列表将由构造函数创建,并立即替换为从数据库中检索到的数据。第三:这些列表真的是列表吗,或者它们仅仅是从数据库获取的一些数据的接口?是博物馆的艺术家和流派的ICollection功能是您将要使用的所有功能,还是您真的打算使用IList提供的功能

如果您坚持使用实体框架,则只需查看类的属性,就完全能够确定主键和外键以及表之间的关系

实体框架无法自动检测到的唯一一件事是,您承诺只删除没有流派和艺术家的博物馆

我已使用以下类对其进行了测试:

class Museum
{
    public int Id { get; set; }
    public string Name { get; set; }

    // every Museum has zero or more Artists (one-to-many)
    public virtual ICollection<Artist> Artists { get; set; }

    // every Museum has zero or more Genres (one-to-many)
    public virtual ICollection<Genre> Genres { get; set; }
}
班级博物馆
{
公共int Id{get;set;}
公共字符串名称{get;set;}
//每个博物馆都没有或有更多的艺术家(一对多)
公共虚拟ICollection艺术家{get;set;}
//每个博物馆都有零种或多种类型(一对多)
公共虚拟ICollection类型{get;set;}
}
艺术家:

class Artist
{
    public int Id { get; set; }

    // every Artist belongs to one Museum using foreign key:
    public int MuseumId { get; set; }
    public virtual Museum Museum { get; set; }

    // every Artist creates work in zero or more Genres (many-to-many)
    public virtual ICollection<Genre> Genres { get; set; }

    public string Name { get; set; }
}
职业艺术家
{
公共int Id{get;set;}
//每个艺术家都属于一个使用外键的博物馆:
公共int musumid{get;set;}
公共虚拟博物馆{get;set;}
//每个艺术家都以零种或多种风格(多对多)创作作品
公共虚拟ICollection类型{get;set;}
公共字符串名称{get;set;}
}
类型:

class Genre
{
    public int Id { get; set; }

    // every Genre belongs to only one Museum using foreign key
    public int MuseumId { get; set; }
    public virtual Museum Museum { get; set; }

    // every Genre is performed by zero or more Artists (many-to-many)
    public virtual ICollection<Artist> Artists { get; set; }

    public string Name { get; set; }
}
类体裁
{
公共int Id{get;set;}
//每一种类型都只属于一个使用外键的博物馆
公共int musumid{get;set;}
公共虚拟博物馆{get;set;}
//每种流派都由零名或多名艺术家(多对多)表演
公共虚拟ICollection艺术家{get;set;}
公共字符串名称{get;set;}
}
最后是DbContext

class MuseumContext : DbContext
{
    public DbSet<Museum> Museums { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Genre> Genres { get; set; }
}
class MuseumContext:DbContext
{
公共数据库集博物馆{get;set;}
公共数据库集艺术家{get;set;}
公共数据库集类型{get;set;}
}
因为我坚持代码优先的约定,这就是实体框架配置主键和外键以及一对多关系所需要知道的全部内容。它甚至为您创建了一个额外的连接表来表示艺术家和流派之间的多对多关系

我们需要做的唯一一件事是告诉modelBuilder不要在删除时级联:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    var museumEntity = modelBuilder.Entity<Museum>();

    museumEntity.HasMany(museum => museum.Genres)
        .WithRequired(genre => genre.Museum)
        .HasForeignKey(genre => genre.MuseumId)
        .WillCascadeOnDelete(false);

    museumEntity.HasMany(museum => museum.Artists)
        .WithRequired(artist => artist.Museum)
        .HasForeignKey(artist => artist.MuseumId)
        .WillCascadeOnDelete(false);

    base.OnModelCreating(modelBuilder);
}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
var museumEntity=modelBuilder.Entity();
museumEntity.HasMany(museum=>museum.tyres)
.WithRequired(流派=>流派.博物馆)
.HasForeignKey(流派=>流派.MuseumId)
.WillCascadeOnDelete(假);
museumEntity.HasMany(museum=>museum.Artists)
.WithRequired(艺术家=>艺术家博物馆)
.HasForeignKey(artist=>artist.MuseumId)
.WillCascadeOnDelete(假);
基于模型创建(modelBuilder);
}
这意味着:每个博物馆都有一个财产类型。流派中的每一个元素都有一个非空的博物馆,这意味着每一个流派都只附属于一个博物馆(不是零个,不是两个)。此附件由外键musumid完成。如果您计划删除博物馆,则数据库不应删除其所有类型

因此,Genre.MuseumId可能不为零,也不能在仍有MuseumId指向此博物馆的流派时删除博物馆

我测试如下:

Database.SetInitializer<MuseumContext>(new DropCreateDatabaseIfModelChanges<MuseumContext>());

using (var dbContext = new MuseumContext())
{
    Genre goldenAge = dbContext.Genres.Add(new Genre() { Name = "Dutch Golden Age Painting" });
    Genre renaissance = dbContext.Genres.Add(new Genre() { Name = "Early Renaissance" });
    Genre portrets = dbContext.Genres.Add(new Genre() { Name = "Portrets" });
    Genre popArt = dbContext.Genres.Add(new Genre() { Name = "Pop Art" });

    // The RijksMuseum Amsterdam has a Collection of several genres,
    Museum rijksMuseum = dbContext.Museums.Add(new Museum()
    {
        Name = "RijksMuseum Amsterdam",
        Genres = new List<Genre>()
        {
            goldenAge,
            renaissance,
            portrets,
        },
    });

    // Rembrandt van Rijn can be seen in the Rijksmuseum:
    Artist artist = dbContext.Artists.Add(new Artist()
    {
        Name = "Rembrandt van Rijn",
        Museum = rijksMuseum,

        // he painted in several Genres
        Genres = new List() {goldenAge, portrets},
    });

    dbContext.SaveChanges();
}
Database.SetInitializer(新的DropCreateDatabaseIfModelChanges());
使用(var dbContext=new MuseumContext())
{
Genre goldenAge=dbContext.Genres.Add(新流派(){Name=“荷兰黄金时代绘画”});
流派复兴=dbContext.Genres.Add(新流派(){Name=“Early renaissance”});
流派portrets=dbContext.Genres.Add(新流派(){Name=“portrets”});
流派popArt=dbCon
class MuseumContext : DbContext
{
    public DbSet<Museum> Museums { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Genre> Genres { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    var museumEntity = modelBuilder.Entity<Museum>();

    museumEntity.HasMany(museum => museum.Genres)
        .WithRequired(genre => genre.Museum)
        .HasForeignKey(genre => genre.MuseumId)
        .WillCascadeOnDelete(false);

    museumEntity.HasMany(museum => museum.Artists)
        .WithRequired(artist => artist.Museum)
        .HasForeignKey(artist => artist.MuseumId)
        .WillCascadeOnDelete(false);

    base.OnModelCreating(modelBuilder);
}
Database.SetInitializer<MuseumContext>(new DropCreateDatabaseIfModelChanges<MuseumContext>());

using (var dbContext = new MuseumContext())
{
    Genre goldenAge = dbContext.Genres.Add(new Genre() { Name = "Dutch Golden Age Painting" });
    Genre renaissance = dbContext.Genres.Add(new Genre() { Name = "Early Renaissance" });
    Genre portrets = dbContext.Genres.Add(new Genre() { Name = "Portrets" });
    Genre popArt = dbContext.Genres.Add(new Genre() { Name = "Pop Art" });

    // The RijksMuseum Amsterdam has a Collection of several genres,
    Museum rijksMuseum = dbContext.Museums.Add(new Museum()
    {
        Name = "RijksMuseum Amsterdam",
        Genres = new List<Genre>()
        {
            goldenAge,
            renaissance,
            portrets,
        },
    });

    // Rembrandt van Rijn can be seen in the Rijksmuseum:
    Artist artist = dbContext.Artists.Add(new Artist()
    {
        Name = "Rembrandt van Rijn",
        Museum = rijksMuseum,

        // he painted in several Genres
        Genres = new List() {goldenAge, portrets},
    });

    dbContext.SaveChanges();
}