Skip to content

Commit 1d91534

Browse files
authored
Document new EF/Npgsql configuration experience (#353)
Original commit: 1a33d7c
1 parent 8ba1a57 commit 1d91534

File tree

4 files changed

+139
-41
lines changed

4 files changed

+139
-41
lines changed

efcore/index.html

+106-35
Original file line numberDiff line numberDiff line change
@@ -97,57 +97,128 @@ <h2 id="configuring-the-project-file">Configuring the project file</h2>
9797
<p>Below is a <code>.csproj</code> file for a console application that uses the Npgsql EF Core provider:</p>
9898
<pre><code class="lang-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt;
9999
&lt;PropertyGroup&gt;
100-
&lt;TargetFramework&gt;netcoreapp3.0&lt;/TargetFramework&gt;
100+
&lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;
101101
&lt;/PropertyGroup&gt;
102102
&lt;ItemGroup&gt;
103-
&lt;PackageReference Include=&quot;Npgsql.EntityFrameworkCore.PostgreSQL&quot; Version=&quot;3.1.3&quot; /&gt;
103+
&lt;PackageReference Include=&quot;Npgsql.EntityFrameworkCore.PostgreSQL&quot; Version=&quot;8.0.4&quot; /&gt;
104104
&lt;/ItemGroup&gt;
105105
&lt;/Project&gt;
106106
</code></pre>
107-
<h2 id="defining-a-dbcontext">Defining a <code>DbContext</code></h2>
108-
<pre><code class="lang-c#">using System.Collections.Generic;
109-
using Microsoft.EntityFrameworkCore;
110-
111-
namespace ConsoleApp.PostgreSQL
107+
<h2 id="defining-a-model-and-a-dbcontext">Defining a model and a <code>DbContext</code></h2>
108+
<p>Let's say you want to store blogs and their posts in their database; you can model these as .NET types as follows:</p>
109+
<pre><code class="lang-c#">public class Blog
112110
{
113-
public class BloggingContext : DbContext
114-
{
115-
public DbSet&lt;Blog&gt; Blogs { get; set; }
116-
public DbSet&lt;Post&gt; Posts { get; set; }
111+
public int BlogId { get; set; }
112+
public string Url { get; set; }
117113

118-
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
119-
=&gt; optionsBuilder.UseNpgsql(&quot;Host=my_host;Database=my_db;Username=my_user;Password=my_pw&quot;);
120-
}
114+
public List&lt;Post&gt; Posts { get; set; }
115+
}
121116

122-
public class Blog
123-
{
124-
public int BlogId { get; set; }
125-
public string Url { get; set; }
117+
public class Post
118+
{
119+
public int PostId { get; set; }
120+
public string Title { get; set; }
121+
public string Content { get; set; }
126122

127-
public List&lt;Post&gt; Posts { get; set; }
128-
}
123+
public int BlogId { get; set; }
124+
public Blog Blog { get; set; }
125+
}
126+
</code></pre>
127+
<p>You then define a <code>DbContext</code> type which you'll use to interact with the database:</p>
128+
<div class="tabGroup" id="tabgroup_bHGHmlrG6S">
129+
<ul role="tablist">
130+
<li role="presentation">
131+
<a href="#tabpanel_bHGHmlrG6S_onconfiguring" role="tab" aria-controls="tabpanel_bHGHmlrG6S_onconfiguring" data-tab="onconfiguring" tabindex="0" aria-selected="true">OnConfiguring</a>
132+
</li>
133+
<li role="presentation">
134+
<a href="#tabpanel_bHGHmlrG6S_context-pooling" role="tab" aria-controls="tabpanel_bHGHmlrG6S_context-pooling" data-tab="context-pooling" tabindex="-1">DbContext pooling</a>
135+
</li>
136+
<li role="presentation">
137+
<a href="#tabpanel_bHGHmlrG6S_aspnet" role="tab" aria-controls="tabpanel_bHGHmlrG6S_aspnet" data-tab="aspnet" tabindex="-1">ASP.NET / DI</a>
138+
</li>
139+
</ul>
140+
<section id="tabpanel_bHGHmlrG6S_onconfiguring" role="tabpanel" data-tab="onconfiguring">
129141

130-
public class Post
131-
{
132-
public int PostId { get; set; }
133-
public string Title { get; set; }
134-
public string Content { get; set; }
142+
<p>Using <code>OnConfiguring()</code> to configure your context is the easiest way to get started, but is discouraged for most production applications:</p>
143+
<pre><code class="lang-c#">public class BloggingContext : DbContext
144+
{
145+
public DbSet&lt;Blog&gt; Blogs { get; set; }
146+
public DbSet&lt;Post&gt; Posts { get; set; }
135147

136-
public int BlogId { get; set; }
137-
public Blog Blog { get; set; }
138-
}
148+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
149+
=&gt; optionsBuilder.UseNpgsql(&quot;&lt;connection string&gt;&quot;);
139150
}
151+
152+
// At the point where you need to perform a database operation:
153+
using var context = new BloggingContext();
154+
// Use the context...
140155
</code></pre>
141-
<h2 id="additional-configuration-for-aspnet-core-applications">Additional configuration for ASP.NET Core applications</h2>
142-
<p>Consult <a href="https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/intro">this tutorial</a> for general information on how to make ASP.NET work with EF Core. For Npgsql specifically, simply place the following in your <code>ConfigureServices</code> method in <code>Startup.cs</code>:</p>
143-
<pre><code class="lang-c#">public void ConfigureServices(IServiceCollection services)
144-
{
145-
// Other DI initializations
156+
</section>
157+
<section id="tabpanel_bHGHmlrG6S_context-pooling" role="tabpanel" data-tab="context-pooling" aria-hidden="true" hidden="hidden">
146158

147-
services.AddDbContext&lt;BloggingContext&gt;(options =&gt;
148-
options.UseNpgsql(Configuration.GetConnectionString(&quot;BloggingContext&quot;)));
159+
<pre><code class="lang-c#">var dbContextFactory = new PooledDbContextFactory&lt;BloggingContext&gt;(
160+
new DbContextOptionsBuilder&lt;BloggingContext&gt;()
161+
.UseNpgsql(&quot;&lt;connection string&gt;&quot;)
162+
.Options);
163+
164+
// At the point where you need to perform a database operation:
165+
using var context = dbContextFactory.CreateDbContext();
166+
// Use the context...
167+
</code></pre>
168+
</section>
169+
<section id="tabpanel_bHGHmlrG6S_aspnet" role="tabpanel" data-tab="aspnet" aria-hidden="true" hidden="hidden">
170+
171+
<p>When using ASP.NET - or any application with dependency injection - the context instance will be injected into your code. Use the following to configure EF with your DI container:</p>
172+
<pre><code class="lang-c#">var builder = WebApplication.CreateBuilder(args);
173+
174+
builder.Services.AddDbContextPool&lt;BloggingContext&gt;(opt =&gt;
175+
opt.UseNpgsql(builder.Configuration.GetConnectionString(&quot;BloggingContext&quot;)));
176+
177+
public class BloggingContext(DbContextOptions&lt;BloggingContext&gt; options) : DbContext(options)
178+
{
179+
public DbSet&lt;Blog&gt; Blogs { get; set; }
180+
public DbSet&lt;Post&gt; Posts { get; set; }
149181
}
150182
</code></pre>
183+
</section>
184+
</div>
185+
186+
<p>For more information on getting started with EF, consult the <a href="https://learn.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli">EF getting started documentation</a>.</p>
187+
<h2 id="additional-npgsql-configuration">Additional Npgsql configuration</h2>
188+
<p>The Npgsql EF provider is built on top of the lower-level Npgsql ADO.NET provider (<a href="https://www.npgsql.org/doc/index.html">docs</a>); these two separate components support various options you may want to configure.</p>
189+
<p>If you're using EF 9.0 or above, the <code>UseNpgsql()</code> is a single point where you can configure everything related to Npgsql. For example:</p>
190+
<pre><code class="lang-c#">builder.Services.AddDbContextPool&lt;BloggingContext&gt;(opt =&gt;
191+
opt.UseNpgsql(
192+
builder.Configuration.GetConnectionString(&quot;BloggingContext&quot;),
193+
o =&gt; o
194+
.SetPostgresVersion(13, 0)
195+
.UseNodaTime()
196+
.MapEnum&lt;Mood&gt;(&quot;mood&quot;)));
197+
</code></pre>
198+
<p>The above configures the EF provider to produce SQL for PostgreSQL version 13 (avoiding newer incompatible features), adds a plugin allowing use of NodaTime for date/time type mapping, and maps a .NET enum type. Note that the last two also require configuration at the lower-level ADO.NET layer, which the code above does for you automatically.</p>
199+
<p>If you need to configure something at the lower-level ADO.NET layer, use <code>ConfigureDataSource()</code> as follows:</p>
200+
<pre><code class="lang-c#">builder.Services.AddDbContextPool&lt;BloggingContext&gt;(opt =&gt;
201+
opt.UseNpgsql(
202+
builder.Configuration.GetConnectionString(&quot;BloggingContext&quot;),
203+
o =&gt; o.ConfigureDataSource(dataSourceBuilder =&gt; dataSourceBuilder.UseClientCertificate(certificate))));
204+
</code></pre>
205+
<p><code>ConfigureDataSource()</code> provides access to a lower-level <a href="../doc/basic-usage.html#data-source"><code>NpgsqlDataSourceBuilder</code></a> which you can use to configure all aspects of the Npgsql ADO.NET provider.</p>
206+
<div class="WARNING">
207+
<h5>Warning</h5>
208+
<p>The EF provider internally creates an NpgsqlDataSource and uses that; for most configuration (e.g. connection string), the provider knows to switch between NpgsqlDataSources automatically.
209+
However, it's not possible to detect configuration differences within the <code>ConfigureDataSource()</code>; as a result, avoid performing varying configuration inside <code>ConfigureDataSource()</code>, since you may
210+
get the wrong NpgsqlDataSource. If you find yourself needing to vary Npgsql ADO.NET configuration, create an external NpgsqlDataSource yourself with the desired configuration and pass that to
211+
<code>UseNpgsql()</code> as described below.</p>
212+
</div>
213+
<h3 id="using-an-external-npgsqldatasource">Using an external NpgsqlDataSource</h3>
214+
<p>If you're using a version of EF prior to 9.0, the above configuration methods aren't available. You can still create an <code>NpgsqlDataSource</code> yourself, and then pass it EF's <code>UseNpgsql()</code>:</p>
215+
<pre><code class="lang-c#">var dataSourceBuilder = new NpgsqlDataSourceBuilder(builder.Configuration.GetConnectionString(&quot;BloggingContext&quot;));
216+
dataSourceBuilder.MapEnum&lt;Mood&gt;();
217+
dataSourceBuilder.UseNodaTime();
218+
var dataSource = dataSourceBuilder.Build();
219+
220+
builder.Services.AddDbContextPool&lt;BloggingContext&gt;(opt =&gt; opt.UseNpgsql(dataSource));
221+
</code></pre>
151222
<h2 id="using-an-existing-database-database-first">Using an Existing Database (Database-First)</h2>
152223
<p>The Npgsql EF Core provider also supports reverse-engineering a code model from an existing PostgreSQL database (&quot;database-first&quot;). To do so, use dotnet CLI to execute the following:</p>
153224
<pre><code class="lang-bash">dotnet ef dbcontext scaffold &quot;Host=my_host;Database=my_db;Username=my_user;Password=my_pw&quot; Npgsql.EntityFrameworkCore.PostgreSQL

efcore/release-notes/9.0.html

+23-2
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,30 @@ <h1 id="90-release-notes">9.0 Release Notes</h1>
9090
<h5>Note</h5>
9191
<p>The following release notes and breaking changes are partial. More will be added nearer to the 9.0 final release.</p>
9292
</div>
93-
<h2 id="improved-configuration-for-enums-and-plugins">Improved configuration for enums and plugins</h2>
93+
<h2 id="improved-unified-configuration-experience">Improved, unified configuration experience</h2>
94+
<p>The Npgsql EF provider is built on top of the lower-level Npgsql ADO.NET provider; the configuration interface between these two layers was less than ideal, and configuration been more difficult than it should have been. For version 9.0, the configuration experience has been considerably improved.</p>
95+
<p>Since version 7, the Npgsql ADO.NET provider has been moving to <a href="../../doc/basic-usage.html#data-source">NpgsqlDataSource</a> as the preferred way of configuration connections and obtaining them. At the EF level, it has been possible to pass an NpgsqlDataSource instance to <code>UseNpgsql()</code>; but this required that the user separately configure a data source and manage it. In addition, features such as plugins and enums require support from both the EF and ADO.NET layers, forcing users to perform multiple setup actions at the different layers.</p>
96+
<p>With version 9, <code>UseNpgsql()</code> becomes a single point for configuration, for both the EF and ADO.NET levels. EF can now internally set up an NpgsqlDataSource, automatically applying all the necessary configuration to it, and also exposes an API to allow users to apply arbitrary configuration to it as well:</p>
97+
<pre><code class="lang-c#">builder.Services.AddDbContextPool&lt;BloggingContext&gt;(opt =&gt;
98+
opt.UseNpgsql(
99+
builder.Configuration.GetConnectionString(&quot;BloggingContext&quot;),
100+
o =&gt; o
101+
.SetPostgresVersion(13, 0)
102+
.UseNodaTime()
103+
.MapEnum&lt;Mood&gt;(&quot;mood&quot;)
104+
.ConfigureDataSource(dataSourceBuilder =&gt; dataSourceBuilder.UseClientCertificate(certificate))));
105+
</code></pre>
106+
<p>In the above code, the following configuration gestures are performed:</p>
107+
<ol>
108+
<li><code>SetPostgresVersion()</code> is an EF-only option to produce SQL for PostgreSQL version 13 (avoiding newer incompatible features)</li>
109+
<li><code>UseNodaTime()</code>, adds a plugin allowing use of NodaTime for date/time type mapping. This also requires an ADO.NET NodaTime plugin which needed to be configured separately, but this is now done automatically.</li>
110+
<li><code>MapEnum()</code> maps a .NET enum type. Like <code>UseNodaTime()</code>, this also used to require a separate ADO.NET configuration gesture, but is now done automatically. As an added bonus, doing this now also adds the enum to the model, causing the enum to be created in the database via EF's migrations.</li>
111+
<li><code>ConfigureDataSource()</code> exposes an NpgsqlDataSourceBuilder, which you can use to configure arbitrary ADO.NET options. In this example, the certificate is defined for the TLS authentication process.</li>
112+
</ol>
113+
<p>For more information, see the <a href="../index.html">getting started docs</a>.</p>
114+
<h3 id="improved-configuration-for-enums-and-plugins">Improved configuration for enums and plugins</h3>
94115
<p>Previously, configuration around enums and plugins (NodaTime, NetTopologySuite) was complicated, requiring multiple setup actions at both the EF and the lower-level Npgsql layers. EF 9.0 improves the configuration story, allowing you to configure enums and plugins via a single EF gesture:</p>
95-
<pre><code class="lang-c#">builder.Services.AddDbContext&lt;MyContext&gt;(options =&gt; options.UseNpgsql(
116+
<pre><code class="lang-c#">builder.Services.AddPooledDbContext&lt;MyContext&gt;(options =&gt; options.UseNpgsql(
96117
&quot;&lt;connection string&gt;&quot;,
97118
o =&gt; o.MapEnum&lt;Mood&gt;(&quot;mood&quot;)));
98119
</code></pre>

0 commit comments

Comments
 (0)