8

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?

9
  • That is because 1. You have a navigation property at each class to the other class 2. You are using "include". That is the only thing you can do. You cannot access the not mentioned object in the context without the mentioned one Commented Jan 24, 2018 at 21:57
  • @DanHunex thanks for the comment, but You're actually wrong. Include is not necessary. I edited my question Commented Jan 24, 2018 at 22:06
  • @DanHunex was correct that the navigation property causes the related tables to be created Commented Jan 24, 2018 at 22:09
  • @James but, it's not include, correct? Does it mean, that if your tables are connected via ForeignKey attribute, you need to declare ONLY ONE table from that chain in DbContext? Commented Jan 24, 2018 at 22:10
  • 2
    Yes, as long as you have a navigation property you do not need to list it. @DanHunex was correct about navigation property, not the include. Commented Jan 24, 2018 at 22:14

4 Answers 4

7

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.

Sign up to request clarification or add additional context in comments.

3 Comments

It's EF 6 actually. Do I need ForeigKey attributes? You didn't include them in your example
don't know how in EF 6, but in EF Core you don't need this attribute. According convention it will be automatically if property name is OtherClassNameID.
This is only one specific case why some DbSet properties aren't necessary, but there is more to it.
4

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.

Comments

1

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

2 Comments

Well you need at least one DbSet per interesting entry point to the data you want, right? Otherwise how do you access it?
You can access an entity directly from the DbContext using: DbContext.Set<TEntityType>( ) - So, no, you dont "need" to have a DbSet explicitly on your DbContext
0

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.

1 Comment

This is the "accepted" way of working (it appears in all the Microsoft tutorials/examples) but its not essential - see my comments above

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.