【问题标题】:Adding records to database with many-to-many relations in ASP.NET Core app with EF Core使用 EF Core 在 ASP.NET Core 应用程序中将记录添加到具有多对多关系的数据库
【发布时间】:2023-03-17 00:22:01
【问题描述】:

您好,出于学习目的,我正在为电影数据库编写一个 ASP.NET Core Web API 应用程序,其示意图如下所示:
Database diagram img link(抱歉,没有足够的声望来发布图片)
这是 Context 类的样子

public class MovieDbContext : DbContext
    {
        public MovieDbContext(DbContextOptions<MovieDbContext> options)
            :base(options)
        {}

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MovieActor>()
                .HasKey(ma => new { ma.MovieId, ma.ActorId });
            modelBuilder.Entity<MovieGenre>()
                .HasKey(mg => new { mg.MovieId, mg.GenreId});
        }

        public DbSet<Movie> Movies { get; set; }
        public DbSet<Actor> Actors { get; set; }
        public DbSet<Genre> Genres { get; set; }
        public DbSet<MovieActor> MovieActors { get; set; }
        public DbSet<MovieGenre> MovieGenres { get; set; }
    }

我在一些资源和 stackoverflow 帖子中阅读到,将​​ DTO 类用于请求是一种很好的做法,因此我为名为 MovieCreateMovie 实体实现了一个。

    public class Movie
    {
        public int MovieId { get; set; }
        [Required]
        [StringLength(200)]
        public string Name { get; set; }
        [Required]
        public int Year { get; set; }
        [Required]
        [StringLength(4000)]
        public string Description { get; set; }
        [Required]
        [Column(TypeName = "decimal(3, 1)")]
        public decimal Score { get; set; }
        [Required]
        [StringLength(2000)]
        public string ImgUrl { get; set; }

        public IList<MovieActor> MovieActors { get; set; }
        public IList<MovieGenre> MovieGenres { get; set; }
    }
    public class MovieCreate
    {
        public string Name { get; set; }
        public int Year { get; set; }
        public string Description { get; set; }
        public decimal Score { get; set; }
        public string ImgUrl { get; set; }
        public IList<int> ActorIds { get; set; }
        public IList<int> GenreIds { get; set; }
    }

MovieCreate 类和Movie 实体之间的区别在于缺少MovieId 属性,因为我们不知道 PK 的当前值,更重要的是 IDENTITY_INSERT 参数是 OFF,所以没有意义在 DTO 类里面。另一个区别是导航属性的类型从IList&lt;MovieGenre&gt; 更改为IList&lt;int&gt; 也是由于缺少MovieId 信息引起的。

所以我试图找出将数据从 POST 请求存储到数据库的正确方法。我在 DataAccess 项目中有一项服务,负责对数据库进行操作。该服务中的 Create 方法如下所示:

    public async Task<MovieResponse> Create(MovieCreate newMovie)
        {
            Movie movie = newMovie.MapMovie();

            //We save changes to database first so that MovieId is assigned to movie variable
            _context.Movies.Add(movie);
            await _context.SaveChangesAsync();

            foreach (var actor in newMovie.ActorIds)
            {
                movie.MovieActors.Add(new MovieActor { ActorId = actor, MovieId = movie.MovieId });
            }

            foreach (var genre in newMovie.GenreIds)
            {
                movie.MovieGenres.Add(new MovieGenre { GenreId = genre, MovieId = movie.MovieId });
            }

            _context.Movies.Update(movie);
            await _context.SaveChangesAsync();

            return movie.MapMovieResponse();
        }

我想知道这是正确的还是我应该说可以接受的做事方式?同样通过反复试验和一些谷歌搜索,我发现要么明确地将MovieId 分配给 0,要么永远不分配它,这将导致它成为默认整数值(也是 0),EF 会假设我们没有明确指定 PK 值,所以它会在保存时由数据库生成,所以这样的代码也可以:

        public async Task<MovieResponse> Create(MovieCreate newMovie)
        {
            Movie movie = newMovie.MapMovie();
            //technically movie.MovieId == 0 at this point, since no value was assigned to it, so it's default
            foreach (var actor in newMovie.ActorIds)
            {
                movie.MovieActors.Add(new MovieActor { ActorId = actor, MovieId = 0});
            }

            foreach (var genre in newMovie.GenreIds)
            {
                movie.MovieGenres.Add(new MovieGenre { GenreId = genre, MovieId = 0});
            }
            //And now we save changes to the database
            _context.Movies.Add(movie);
            await _context.SaveChangesAsync();

            return movie.MapMovieResponse();
        }

同样在Create 方法的第一个版本中,我可以将数据添加到MovieGenresDbSet,而不是添加例如MovieGenre 数据到导航属性。那么哪种方式是“正确”的方式?
附言如果问得不算太多,我真的很感谢有关更新(PUT)和 PATCH 请求的任何提示,我认为尤其是在 PATCH 上,因为从我在 stackoverflow 上看到的情况来看,它真的很复杂。

【问题讨论】:

    标签: c# .net asp.net-core .net-core entity-framework-core


    【解决方案1】:

    这两个选项显然都有效,所以我不知道“正确”是否是正确的术语。但是从代码和性能的角度来看,第二种选择肯定更有效。代码更易于阅读且更紧凑,您只需调用 SaveChanges 一次。您可以通过删除连接表的“MovieId = 0”来稍微清理第二个选项。它是由 EF 假定的,所以你不需要它。

    就个人而言,对于更新,我只是提取现有记录,然后手动检查那些可以更新的属性,进行比较以查看它们是否更改并在需要时更新(一堆 IF 语句)。通过仅接触实际更新的字段,EF 可以创建更高效​​的 UPDATE 查询。如果没有必要,不要只是自动将传入更新 DTO 中的每个字段复制到现有实体。

    很抱歉,我不太会使用 PATCH,所以我无法与之交谈。

    【讨论】:

    • 感谢您的回答。这也是我一开始所做的,即没有明确地将 MovieId 分配给 0,只是在帖子中的代码中这样做以进行澄清。对于更新部分,这正是我的想法,现在我看到这是要走的路。再次感谢。
    【解决方案2】:

    将 ActorIds 和 GenreIds 更改为 int 数组

        public class MovieCreationDto
        {
            public string Name { get; set; }
            public int Year { get; set; }
            public string Description { get; set; }
            public decimal Score { get; set; }
            public string ImgUrl { get; set; }
            public int[] ActorIds { get; set; }
            public int[] GenreIds { get; set; }
        }
    

    并将您的项目更新到 ASP.NET Core 5 和 EF Core 5,因为在 EF Core 5 中建立多对多关系非常容易,而且对于多对多关系而言,查询更容易。

    使用 LINQ 中的 Select(Projection) 更改将演员和流派添加到电影的方式。

    What's New in EF Core 5.0

    【讨论】:

    • 感谢您的回答。我将很快研究 .NET 5,只是想先弄清楚基础知识,并在理解一切如何运作方面打下坚实的基础。
    猜你喜欢
    • 2021-09-14
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 2021-02-11
    • 1970-01-01
    • 2021-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多