2

I'm currently following a C# tutorial, and it has this ApplicationDbContext class file:

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

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

Where Category is defined as:

public class Category
{
    [Key]
    public int ID { get; set; }

    [Required]
    public string Name { get; set; }

    public int DisplayOrder { get; set; }
}

Which is then later referenced like so:

    private readonly ApplicationDbContext _db;

    public IEnumerable<Category> Categories { get; set; }

    public IndexModel(ApplicationDbContext db)
    {
        _db = db;
    }

    public void OnGet()
    {
        Categories = _db.Category;
    }

I also have this code in the Program.cs file:

  builder.Services.AddDbContext<ApplicationDbContext>(options => 
  options.UseSqlServer(
    builder.Configuration.GetConnectionString("DefaultConnection")
  ));

The final piece of info is that I did a migration to create a database table called Category.

My confusion is around how everything is declared here; the line in the DbContext file with DbSet doesn't seem to instantiate anything and instead just defines Category as a public variable with type DbSet<Category>, but then the IEnumerable<Category> seems to treat Category as if it's going through the table, as does calling _db.Category.

In this case, looking through the code, the Category class is what gets referenced and not the Category database table. How does everything tie together here?

2 Answers 2

2

The ApplicationDbContext is just the definition of the data context. This effectively defines the tables in your database. Each DbSet<T> represents a table in your data schema.

  • DbContext is the base class of this database schema definition.

Category is a data Model, on it's own, it is just another class that represents something... But when you use it in the DbContext via the DbSet<Cateogry> type it becomes a table definition. When you read this line:

public DbSet<CategoryType> CategoryName { get; set; } 

Interpret it as this: Table called CategoryName that the structure of the CategoryType class.

So far so good,

This next bit is related to a concept called Dependency Injection. the idea is that we can define at a global level a set of instructions that describe how to instantiate objects that we need to use a lot throughout the application without putting that instantiation logic everywhere. It is a way to define this logic once and is inline with the DRY principal.

  • You might think that the contructor of a class is an appropriate place for this type of information and before DI it was a common option, but for compex types this often requires a lot of external domain knowledge that is specific to the current application, which makes it hard to re-use your classes on other applications or contexts. DI is an elegant solution to this that forces you to totally divorce the definition of your types and the implementation.

  • After contructors (and before DI), the next common implementation was to use the Factory Pattern to create objects using domain specific logic. Think of DI is a specific implementation of the Factory Pattern.

So in Program.cs, the following code is registering the ApplicationDbContext type for DI, using the AddDbContext factory.

builder.Services.AddDbContext<ApplicationDbContext>(options => 
options.UseSqlServer(
  builder.Configuration.GetConnectionString("DefaultConnection")
));
  • Part of this registration is to specify the connection string, in this case it is telling the underlying factory method to get the connection string from the configuration file.

The magic occurs now in the rest of your DI stack, in this case with your Razor Pages. The Razor Page Controller IndexModel is instantiated via DI, you will find this registration later in the Program.cs:

builder.Services.AddRazorPages().AddRazorRuntimeCompilation();

When the IndexModel PageModel controller is needed, DI will call the constructor on that class.

public class IndexModel : PageModel
{
    public IndexModel(ApplicationDbContext db) { ... }
}

As it requires an instance of ApplicationDbContext and DI knows how to create an instance of that type, it uses the AddDbContext factory to create a new instance of ApplicationDbContext and pass it through as an argument to the IndexModel constructor.

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

2 Comments

In regards to IndexModel, it's actually a razor page that inherits PageModel. I'm assuming then that builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); would be the dependency injection for razor pages, which then when IndexModel calls for an ApplicationDbContext object in its constructor that calls the DbContext injection?
That is correct! I'll update my post with a bit of clarity around that.
1

the line DbContext file with DbSet doesn't seem to instantiate anything and instead just defines Category as a public variable with type DbSet

Correct

then the IEnumerable seems to treat Category as if it's going through the table

Also correct. the code in Program.cs ties the two together. First, it enables Dependency Injection of _db by calling AddDbContext<ApplicationDbContext>(); and second, it directs _db to retrieve data from the source (usually, database) defined in DefaultConnection. You should have this definition in appsettings.json - for example,

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=servername;Initial Catalog=dbname;User ID=username;password=password;"
  }
}

2 Comments

So when is db as an object instantiated then? It seems like it's feeding an already existing object into IndexModel(ApplicationDbContext db)
@Dragnipur - the beauty of dependency injection is that you don't need to worry about it. It will be instantiated some time after you specified how to instantiate it, and before you need it in your controller. My experience tells me that it is creating DB connection first time you need it - but it's buried in Entity Framework

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.