-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add extension methods to enable simpler adding and configuration of EF Core providers #25192
Comments
You could also avoid code inside class ApplicationDbContext : DbContext
{
// ...
protected override void OnConfiguring(DbContextOptionsBuilder options)
// NB: This is a ✨magic✨ connection string that reads from IConfiguration
=> options.UseSqlite("Name=DefaultConnection");
} builder.Services.AddDbContext<ApplicationDbContext>(); |
But connection strings........ OH WAIT that works. |
A few thoughts/considerations after discussing this with the team:
|
For further context, the app being built in the kind of tutorial we're talking about would look like the following at the point it supports creating and retrieving todos (even the absence of var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSqlite<TodoDb>("Data Source=todos.db");
var app = builder.Build();
app.MapGet("/", () => "Hello World");
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync());
app.MapPost("/todos", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return CreatedAt($"/todos/{todo.Id}", todo);
});
app.Run();
class Todo
{
public int Id { get; set; }
public string? Title { get; set; }
public bool IsComplete { get; set; }
}
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
} |
|
I don't think it's about the number of characters, its the concept count and the happy path through the learning arc. You want to the code to look intuitive before you begin to learn more deeply about what it does. |
The user still needs to learn about DbContext, so the concept count remains the same. |
I agree being exposed to However one might imagine the class definition for the context can be smoothed over by simply instructing folks to paste that block at the end of their file where it can be discussed in more depth later. Dealing with services however isn't so clean as it's located amongst the rest of the app code. So in that context, we're comparing these two blocks: var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSqlite<TodoDb>("Data Source=todos.db"); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(options =>
options.UseSqlite("Data Source=todos.db")); I can see that it might seem like a trivial difference, but I'd argue the first is far simpler to explain and absorb, even to folks not familiar with C#. We call a generic method named for the place we want to store our data and pass a string, as opposed to calling a generic method named for the complex code we just pasted that we're not yet going to explain, then pass it a delegate as a lambda that will be called to configure the complex thing in which we call a method named for where we want to store our data and pass a string. |
Time to start working on an active record library for .NET? |
While I don’t hate the proposed sugar, I still fail to see any weight behind this statement. You use lambdas to define HTTP requests, why is using them to configure services so hard to explain? |
We basically did the same with the new host. It's not that lambdas are themselves complex but all lambda usage isn't equivalent so I think it's not the right argument to make. It's more about the 80/20 rule, the main case is pick a provider for your DbContext and connection string. Turns out EF understood this and also supports the name=connection string name idea so this isn't too far off. |
Something (possibly unrelated) that I keep thinking about: Generally, we try to have good, sensible defaults so you don't have to think about things unless you want to change them. In EF 4.1, we decided that SQL Server Express was a sensible default--you didn't even have to figure out a connection string! In EF Core, we decided that this was a terrible idea. MySQL, PostgreSQL, SQL Server, SQLite, (even DB2 and Oracle if you're into that sorta thing), are all very sensible defaults. We intentionally chose to make it a decision point for the user. Is this just a case where we've been able to hide everything else in the default templates because there are sensible defaults, so we're just struggling with how to present the configuration? What does the code look like if I want to configure Logging to log to a file? Shouldn't configuring the database provider use the same pattern for configuring a service? |
Here's what the Todo app looks like in preview7 https://github.com/davidfowl/CommunityStandUpMinimalAPI/tree/davidfowl/preview7 @DamianEdwards also has an app where he's added these extensions:
I think this is a stretch. I don't expect we'll try to make everything simple. As we get more experience with what the "main" things people configure we'll make more changes. There's absolutely a cliff of complexity, but EF is in the mainline (for good reasons) and we want to make sure the experience is as smooth as possible on the learning arc. |
Hey all. Great discussion! I basically agree with everything Brice and Andriy have said. However, I don't feel strongly that we shouldn't do this. In other words, I think it's slightly worse than what we currently have, but not massively worse. So if Damian and David feel this is overall better for the high-level experience, then I'm okay with this going in. However, we must do it for all providers that we own, not just SQLite, and we should notify other providers (Pomelo, Oracle/MySQL, IBM, PostgreSQL, Firebird) so that they are also consistent. (As Brice said, there is nothing special about SQLite other than it's currently the getting-started flavor of choice. That has changed in the past, and may change again in the future, especially depending what happens with SQL Server.) |
Imagine if there was a developer Azure SQL SKU! |
@ajcvickers that's good to hear, the PR draft is here #25220. Take a look and let us know what you think. |
…ds (#25220) * Add IServiceCollection.UseSqlite<DbContext> extension methods Contributes to #25192 * Add IServiceCollection.UseSqlServer<DbContext> extension methods Contributes to #25192 Co-authored-by: John Luo <johluo@microsoft.com> Co-authored-by: Andriy Svyryd <AndriySvyryd@users.noreply.github.com>
The PR has been merged. Do we need to keep this issue open to track notifying other providers? |
I've already pinged Pomelo MySQL and Npgsql maintainers from the PR. Only Oracle/MySQL, IBM and Firebird left. |
I'll reassign this to you to follow up with the providers then. Thanks @AndriySvyryd for helping me get the change in! |
Npgsql issue: npgsql/efcore.pg#1934 |
@lauxjpn Do we need to open an issue for Pomelo? |
@mguinness Thanks for the ping. We could, but it's also alright if we don't. I have this on my internal list already after recently looking through the commits that @roji pushed to Npgsql and then remembered seeing the discussion between the ASP.NET and the EF Core guys about it somewhere (or maybe reading this issue or something; don't remember). |
Today, adding and configuring Entity Framework Core in a project that uses
Microsoft.Extensions.Hosting
(e.g. an ASP.NET Core project) requires calling multiple layers of extension methods to first add EF Core, and then add and configure the desired provider, e.g. SQLite. In simple cases where the project has no need for the utility provided byDbContextOptionsBuilder
or in tutorials this can result in extra concepts being forced upon the user that aren't required at this stage.I propose we add higher-level extension methods to enable simpler setup of EF Core with a specific provider. In each provider package a new set of
IServiceCollection
extension methods would be declared in theMicrosoft.Extensions.DependencyInjection
namespace, e.g.:We can compare the common case today, vs. what would be possible with such extension methods:
Before
After
Potential benefits:
DbContext
. This can be useful in learning scenarios where the arc isn't ready to explain the concepts related to EF Core and its provider model (e.g. ORMs, providers, option delegates, etc.)Microsoft.EntityFrameworkCore
namespace to get to the "can read/write to/from a database" stage@davidfowl @LadyNaggaga
The text was updated successfully, but these errors were encountered: