如何在双方拥有相同父级的多对多实现EF连接表

时间:2018-03-08 00:59:47

标签: 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>();
    }

    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; }
}

Database Diagram

问题: 艺术家创作各种类型的作品,因此艺术家有很多类型。同样,许多艺术家使用一种类型。所以在这里我需要一个多对多的关系,我可以通过EFCore明确定义一个连接实体 - ArtistGenre。更新了以下代码:

public class Artist
{
    public Artist()
    {
        ArtistGenres = new List<ArtistGenre>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public Museum Museum { get; set; }
    public int MuseumId { get; set; }
    public List<ArtistGenre> ArtistGenres { get; set; }
}

public class Genre
{
    public Genre()
    {
        ArtistGenres = new List<ArtistGenre>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public Museum Museum { get; set; }
    public int MuseumId { get; set; }
    public List<ArtistGenre> ArtistGenres { get; set; }
}

public class ArtistGenre
{
    public Artist Artist { get; set; }
    public int ArtistId { get; set; }
    public Genre Genre { get; set; }
    public int GenreId { get; set; }
}

在我的DbContext中OnModelCreating中的强制性modelBuilder.Entity<ArtistGenre>().HasKey(a => new { a.ArtistId, a.GenreId });。 但是这导致存在多个到同一实体的级联删除路径的情况:

  

介绍FOREIGN KEY约束&#39; FK_ArtistGenre_Genres_GenreId&#39;在桌子上&#39; ArtistGenre&#39;可能会导致循环或多个级联路径。指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束。       无法创建约束或索引。查看以前的错误。

让这个有用吗?我设计错了吗?最后,我希望能够删除一个博物馆并让所有相关的流派和艺术家(和ArtistGenres)自动删除。

更新

我一直在尝试不同的配置,我发现如果我删除ArtistGenre中的级联删除,就像这样:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ArtistGenre>()
        .HasOne(a => a.Artist)
        .WithMany(m => m.ArtistGenres)
        .OnDelete(DeleteBehavior.Restrict);

    modelBuilder.Entity<ArtistGenre>()
        .HasOne(a => a.Genre)
        .WithMany(g => g.ArtistGenres)
        .OnDelete(DeleteBehavior.Restrict);
}

然后我可以使用联结表更新数据库,但是我没有获得所需的级联删除效果。 如果我将一个Musuem和MuseumId属性添加到ArtistGenre,就像这样:

public class ArtistGenre
{
    public Museum Museum { get; set; }
    public int MuseumId { get; set; }
    public Artist Artist { get; set; }
    public int ArtistId { get; set; }
    public Genre Genre { get; set; }
    public int GenreId { get; set; }
}

我可以删除博物馆并自动删除所有依赖实体(Artist,Genre和ArtistGenre)。但是,如果没有先删除其ID出现在ArtistGenre中的任何记录,我仍然无法删除艺术家或流派。

1 个答案:

答案 0 :(得分:1)

关于级联的报道不是由艺术家和流派之间的多对多关系引起的,而是由一对多的关系博物馆 - 艺术家和博物馆 - 类型引起的。

错误意味着如果您要移除博物馆,它将自动删除其艺术家及其类型。在删除一个类型时,所有在此类型中表演的艺术家都应该进行调整,但是由于删除了博物馆,您也正在删除艺术家,因此我们将删除一个圆圈。

通过从DbContext中删除博物馆并在艺术家和流派之间创建标准的多对多关系,您可以看到与博物馆的一对多关系是个问题。这没有任何属性或流畅的API工作正常。

所以你必须向模型建造者承诺,你将只删除没有艺术家或流派的博物馆。在移除博物馆之前,您必须删除博物馆中展出的艺术家和类型。

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

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

如果你坚持entity framework code first conventions,实体框架完全能够通过查看类的属性来确定表的主键和外键以及关系。

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

我使用以下类测试了这一切:

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; }
}

艺术家:

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; }
}

流派:

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; }
}

最后是DbContext

class MuseumContext : DbContext
{
    public DbSet<Museum> Museums { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Genre> Genres { get; set; }
}

因为我坚持使用代码优先约定,所以这是配置主键和外键以及一对多关系所需的实体框架。它甚至可以为您创建一个额外的联结表,以表示艺术家和流派之间的多对多关系。

我们唯一需要做的就是告诉模型构建器不要删除Cascade:

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);
}

这意味着:每个博物馆都有一个属性类型。 Genre中的每个元素都有一个非零博物馆,这意味着每个类型都只附加到一个博物馆(也不是零,而不是两个)。此附件由外键MuseumId完成。如果您打算删除博物馆,则数据库不应删除其所有类型

因此,Genre.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();
}

TODO:看看你是否可以删除仍有艺术家和/或类型的博物馆 TODO:检查如果您从博物馆中删除了所有艺术家和所有类型,可以移除博物馆。

最后:你确定艺术家只在一个博物馆展出吗?是否只有一个博物馆可以看到伦勃朗?

同样:只有一个博物馆才能在一个博物馆展出,是否只有一个展示印象派的博物馆?

考虑将这些关系更改为多对多关系。您甚至可能不再需要使用CascadeOnDelete了。此外,如果你觉得顽皮,你将能够摧毁仍然有艺术家的博物馆; - )