InvalidOperationException: 实体类型 'Enrollments' 需要定义主键

InvalidOperationException: The entity type 'Enrollments' requires a primary key to be defined

我是 Asp.Net Core 的新手(甚至是 Asp.Net 和网络)。我正在使用 Asp.Net Core 2 和 MySQL,使用 Pomelo.EntityFrameWorkCore.MySql (2.0.1) 驱动程序。我刚刚使用 Courses 和 Enrollments table 创建了一个自定义 dbcontext,以及默认创建的 ApplicationDbContext。 Enrollments 的主键是一个组合键,由 UserId 和 CourseId 组成。下面是代码:

public class CustomDbContext : DbContext
{
    public virtual DbSet<Courses> Courses { get; set; }
    public virtual DbSet<Enrollments> Enrollments { get; set; }

    public CustomDbContext(DbContextOptions<CustomDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Courses>(entity =>
        {
            entity.ToTable("courses");

            entity.HasIndex(e => e.Name)
                .HasName("Coursescol_UNIQUE")
                .IsUnique();

            entity.Property(e => e.Id).HasColumnType("int(11)");

            entity.Property(e => e.Duration).HasColumnType("time");

            entity.Property(e => e.Name).HasMaxLength(45);
        });

        modelBuilder.Entity<Enrollments>(entity =>
        {
            entity.HasKey(e => new { e.UserId, e.CourseId });

            entity.ToTable("enrollments");

            entity.HasIndex(e => e.CourseId)
                .HasName("fk_Courses_Enrollments_CourseId_idx");

            entity.HasIndex(e => e.UserId)
                .HasName("fk_Users_Enrollments_CourseId_idx");

            entity.HasIndex(e => new { e.UserId, e.CourseId })
                .HasName("UniqueEnrollment")
                .IsUnique();

            entity.Property(e => e.CourseId).HasColumnType("int(11)");

            entity.HasOne(d => d.Course)
                .WithMany(p => p.Enrollments)
                .HasForeignKey(d => d.CourseId)
                .OnDelete(DeleteBehavior.ClientSetNull)
                .HasConstraintName("fk_Courses_Enrollments_CourseId");

            entity.HasOne(d => d.User)
                .WithMany(p => p.Enrollments)
                .HasForeignKey(d => d.UserId)
                .OnDelete(DeleteBehavior.ClientSetNull)
                .HasConstraintName("fk_Users_Enrollments_UserId");
        });
    }

}

Program.cs 是这样的:

    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            try
            {
                var context = services.GetRequiredService<CustomDbContext>();
                context.Database.EnsureCreated();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred while seeding the database.");
            }
        }

        host.Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

Startup.cs 中的配置服务方法如下:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));

        services.AddDbContext<CustomDbContext>(options =>
        options.UseMySql(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc();
    }

课程模型如下:

public partial class Courses
{
    public Courses()
    {
        Enrollments = new HashSet<Enrollments>();
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public TimeSpan? Duration { get; set; }

    public ICollection<Enrollments> Enrollments { get; set; }
}

注册模型如下:

public partial class Enrollments
{
    public string UserId { get; set; }
    public int CourseId { get; set; }

    public Courses Course { get; set; }
    public ApplicationUser User { get; set; }
}

applicationUser 模型如下:

    public ApplicationUser()
    {
        Enrollments = new HashSet<Enrollments>();
    }

    public ICollection<Enrollments> Enrollments { get; set; }

现在,这是我到目前为止尝试过的方法:

  1. 如果我将课程和注册模型添加到 ApplicationDBContext,那么一切正常。

  2. 如果在 CustomDBContext 中我有一个非复合主键,即使那样它也能正常工作。 (我刚试了另一个例子)

有人能解释一下为什么会出现这个错误吗?这是处理这种情况的预期方式吗?

提前致谢。

这是因为 Enrollments 实体已被 ApplicationDbContext 通过 ApplicationUser.Enrollments 导航 属性 发现。这在 EF Core 文档的 Including & Excluding Types - Conventions 部分进行了解释:

By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.

我猜你现在明白问题所在了。 Enrollments 被发现并包含在 ApplicationDbContext 中,但那里没有针对该实体的流畅配置,因此 EF 仅使用默认约定和数据注释。当然复合PK需要流畅的配置。即使没有复合PK,忽略现有的流畅配置也是不正确的。请注意,通过上述递归过程(通过 Enrollments.Courses 导航 属性),Courses 也包含在 ApplicationDbContext 中。等其他引用 类.

请注意,同样适用于另一个方向。 ApplicationUser 以及从中引用的所有内容都被发现并包含在 CustomDbContext w/o 它们的流畅配置中。

结论 - 不要使用包含相互关联实体的单独上下文。在您的情况下,将所有实体放入 ApplicationDBContext.