Marshall
Marshall

Reputation: 275

When do I need to specify DbSet in EF's DbContext?

I'm a little bit confused. Until today I thought that every table (used by EF) must be specified in DbContext class. But it looks like I need ONLY one! really?

Let me explain, Here's my DbContext:

public class MyDbContext : DbContext
{
    public MyDbContext()
        : base("name=MyDbContext")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer<MyDbContext>(null);
        base.OnModelCreating(modelBuilder);
    }    

    public DbSet<Table1> Table1 { get; set; }
    public DbSet<Table2> Table2 { get; set; }
    public DbSet<Table3> Table3 { get; set; }
    public DbSet<Table4> Table4 { get; set; }
    public DbSet<Table5> Table5 { get; set; }
}

Here are two sample tables, connected 1:many

[Table("Table1")]
public class Table1
{
    [Key]
    [Column("Table1Id", TypeName = "uniqueidentifier")]
    public int Table1Id { get; set; }

    [Column("Table2Id", TypeName = "int")]
    public int Table2Id { get; set; }

    [ForeignKey("Table2Id")]
    public Table2 Table2 { get; set; }
}

[Table("Table2")]
public class Table2
{
    public Table2()
    {
        this.Table1s = new HashSet<Table1>();
    }

    [Key]
    [Column("Table2Id", TypeName = "int")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Table2Id { get; set; }

    public ICollection<Table1> Table1s { get; set; }
} 

Easy. Now, I want to query all Table2s with corresponding Table1s. I do:

var tables2 = fni.Set<Table2>()
    .Include(i => i.Table1s)
    .Where(t => t.Table2Id == 123456).ToList();

It all works, but I was shocked, when I discovered by accident, that it works even with this DbContext:

public class MyDbContext : DbContext
{
    public MyDbContext()
        : base("name=MyDbContext")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer<MyDbContext>(null);
        base.OnModelCreating(modelBuilder);
    }    


    public DbSet<Table1> Table1 { get; set; }
}

or this..

public class MyDbContext : DbContext
{
    public MyDbContext()
        : base("name=MyDbContext")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer<MyDbContext>(null);
        base.OnModelCreating(modelBuilder);
    }    


    public DbSet<Table2> Table2 { get; set; }
}

Can you explain to me, why does it work?

EDIT. It's not include. I was able to do:

var tables2 = fni.Set<Table2>()
    .Where(t => t.Table2Id == 123456).ToList();

having only this: public DbSet<Table1> Table1 { get; set; } in DbContext. It's not even Table2! They are connected via FK (definitions didn't change). So that would mean, that you must have only one table from one "chain" of tables. Is that correct?

Upvotes: 8

Views: 4870

Answers (4)

Gert Arnold
Gert Arnold

Reputation: 109099

Until today I thought that every table (used by EF) must be specified in DbContext class. But it looks like I need ONLY one! really?

The existing answers only add confusion, none of them tell the whole story comprehensively. The only correct answer to these two questions (which assumes that specifying means: adding a DbSet property) is: no and no.

The actual question is: when does EF know that my class is part of the mapped class model? Because, when it's mapped it can always be accessed by context.Set<T> without ever adding one single DbSet<T> property to the context.

So when does EF add a class to its mapped class model?

Basically there are four ways to do this, either of which is sufficient to make EF map the class to a database table through its built-in conventions:

  1. Add a DbSet<T> property to the context:

    public DbSet<Table1> Table1 { get; set; }
    
  2. Call modelBuilder.Entity<T>():

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Table1>();
    }    
    

    Of course this call normally serves add to more configuration by EF's fluent API (for example, to define a primary key), but this call always registers the class as mapped.

  3. Add an entity configuration to the model:

    class Table1Config : IEntityTypeConfiguration<Table1>
    {
        public void Configure(EntityTypeBuilder<Table1> builder)
        {
        }
    }
    

    In the context:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new EventConig());
    }
    

    Again, this normally serves to add more configuration by EF's fluent API, but it would be enough to map a class (albeit a bit cumbersome to use it for only that).

  4. Add a navigation property to a class that is registered by any of the previous methods. When Table1 is registered,

    public class Table1
    {
        public int Table1Id { get; set; }
        public Table2 Table2 { get; set; }
    }
    

    ...also registers Table2 as mapped: context.Set<Table2>() can be used.

Upvotes: 3

Fabian Madurai
Fabian Madurai

Reputation: 355

After some tests, I see that you need at least One DBSet in your context that is related to the table that you want to create.
In other words, this would be the table that is referenced by the table you want created. Secondly you also need to have a navigation property in the lookup table that references the table you want created.

So for example, you have a Students table. And then you have a Enrollment table that looks up Students table.

Hence Students table MUST at least be a DBSet. Inside the Students class, you must have a navigation property to Enrollment like :

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

That way, EntityFramework , finds the Student DBSet, then goes to the Student model and finds the Enrollment navigation property and creates the table.

Upvotes: 0

Robert Perry
Robert Perry

Reputation: 1956

The reason is purely symantics. If you want to use the DbContext directly and access your entities, you use DbSets.. If you use a UnitOfWork/Repository pattern to abstract this out, you potentially dont need dbSets at all. They just provide an easy route to access your entities via a DbContext

Upvotes: 1

Alexan
Alexan

Reputation: 8635

Let's look at documentation:

We have the following models:

public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }

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

public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }

public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

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

And DB Context:

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

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
    }

But:

You could've omitted the DbSet<Enrollment> and DbSet<Course> statements and it would work the same. The Entity Framework would include them implicitly because the Student entity references the Enrollment entity and the Enrollment entity references the Course entity.

PS. Sorry, I just noticed that question not regarding EF Core. But I think it should be true anyway.

Upvotes: 7

Related Questions