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

时间:2018-02-02 18:54:05

标签: entity-framework-core asp.net-core-2.0 ef-core-2.0

我是Asp.Net Core的新手(甚至是Asp.Net和网络)。我正在使用Asp.Net Core 2和MySQL,使用Pomelo.EntityFrameWorkCore.MySql(2.0.1)驱动程序。我刚刚创建了一个带有Courses和Enrollments表的自定义dbcontext,以及默认创建的ApplicationDbContext。注册的主键是一个复合键,包含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中的configure服务方法如下:

    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中我有一个非复合主键,即使这样它也能正常工作。 (我刚尝试了另一个例子)

  3. 有人可以说明为什么会出现这个错误?这是处理这种情况的预期方式吗?

    提前致谢。

1 个答案:

答案 0 :(得分:2)

这是因为Enrollments通过ApplicationDbContext导航属性发现了ApplicationUser.Enrollments实体。 EF Core文档的Including & Excluding Types - 约定部分对此进行了说明:

  

按照惯例,在您的上下文的DbSet属性中公开的类型包含在您的模型中。此外,还包括OnModelCreating方法中提到的类型。 最后,通过递归浏览已发现类型的导航属性找到的任何类型也包含在模型中。

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

请注意,这同样适用于其他方向。 ApplicationUser及其引用的所有内容都会被发现并包含在CustomDbContext中,而不是其流畅的配置。

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