1. 快速入門 創建新的控制台項目 dotnet new console -o EFGetStarted cd EFGetStarted 安裝 Entity Framework Core 要安裝 EF Core,請為要作為目標對象的 EF Core 資料庫提供程式安裝程式包。 本教程使用 SQLit ...
創建新的控制台項目
dotnet new console -o EFGetStarted cd EFGetStarted
安裝 Entity Framework Core
要安裝 EF Core,請為要作為目標對象的 EF Core 資料庫提供程式安裝程式包。 本教程使用 SQLite 的原因是,它可在 .NET Core 支持的所有平臺上運行 。
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
創建模型
定義構成模型的上下文類和實體類。
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } public string DbPath { get; } public BloggingContext() { var folder = Environment.SpecialFolder.LocalApplicationData; var path = Environment.GetFolderPath(folder); DbPath = System.IO.Path.Join(path, "blogging.db"); } // 配置SQLLite連接字元串(其實就是提供一個本地文件夾用於放數據而已) protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite($"Data Source={DbPath}"); } public class Blog { public int BlogId { get; set; } public string? Url { get; set; } public List<Post> Posts { get; } = new(); } public class Post { public int PostId { get; set; } public string? Title { get; set; } public string? Content { get; set; } public int BlogId { get; set; } public Blog? Blog { get; set; } }
創建資料庫
dotnet tool install --global dotnet-ef dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0.4 dotnet ef migrations add InitialCreate dotnet ef database update
這會安裝
CURD
打開 Program.cs 並將內容替換為以下代碼
using System; using System.Linq; using var db = new BloggingContext(); Console.WriteLine($"Database path: {db.DbPath}."); // 新增 Console.WriteLine("新增了一條blog"); db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" }); db.SaveChanges(); // 讀取 Console.WriteLine("查詢一條blog"); var blog = db.Blogs .OrderBy(b => b.BlogId) .First(); // 更新 Console.WriteLine("更新 blog 並且 添加一條 post數據"); blog.Url = "https://devblogs.microsoft.com/dotnet"; blog.Posts.Add( new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" }); db.SaveChanges(); // 刪除 Console.WriteLine("刪除 blog"); db.Remove(blog); db.SaveChanges(); // 釋放資源 db.Dispose();
運行:
dotnet run
2. 資料庫上下文
1. DbContext 生命周期
DbContext
的生命周期從創建實例時開始,併在
提示
引用上述鏈接中 Martin Fowler 的話,“工作單元將持續跟蹤在可能影響資料庫的業務事務中執行的所有操作。 當你完成操作後,它將找出更改資料庫作為工作結果時需要執行的所有操作。”
使用 Entity Framework Core (EF Core) 時的典型工作單元包括:
-
創建
DbContext
實例 -
根據上下文跟蹤實體實例。 實體將在以下情況下被跟蹤
-
正在
-
正在
-
-
根據需要對所跟蹤的實體進行更改以實現業務規則
-
調用
-
釋放
DbContext
實例
重要
使用後釋放
EF Core 代碼引發的
2. ASP.NET Core 註入中的 DbContext
在許多 Web 應用程式中,每個 HTTP 請求都對應於單個工作單元。 這使得上下文生存期與請求的生存期相關,成為 Web 應用程式的一個良好預設值。
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddDbContext<ApplicationDbContext>( options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection")); }
此示例將名為 ApplicationDbContext
的 DbContext
子類註冊為 ASP.NET Core 應用程式服務提供程式(也稱為依賴關係註入容器)中的作用域服務。 上下文配置為使用 SQL Server 資料庫提供程式,並將從 ASP.NET Core 配置讀取連接字元串。 在 ConfigureServices
中的何處調用 AddDbContext
通常不重要。
ApplicationDbContext
類必須公開具有 DbContextOptions<ApplicationDbContext>
參數的公共構造函數。 這是將 AddDbContext
的上下文配置傳遞到 DbContext
的方式。 例如:
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
然後,ApplicationDbContext
可以通過構造函數註入在 ASP.NET Core 控制器或其他服務中使用。 例如:
public class MyController { private readonly ApplicationDbContext _context; public MyController(ApplicationDbContext context) { _context = context; } }
最終結果是為每個請求創建一個 ApplicationDbContext
實例,並傳遞給控制器,並且在請求結束後釋放DbContext。
有關配置選項的詳細信息,請進一步閱讀本文。 此外,有關 ASP.NET Core 中的配置和依賴關係註入的詳細信息,請參閱
3. “new”一個 DbContext
可以按照常規的 .NET 方式構造 DbContext
實例,例如,使用 C# 中的 new
。 可以通過重寫 OnConfiguring
方法或通過將選項傳遞給構造函數來執行配置。 例如:
public class ApplicationDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"); } }
通過此模式,還可以輕鬆地通過 DbContext
構造函數傳遞配置(如連接字元串)。 例如:
public class ApplicationDbContext : DbContext { private readonly string _connectionString; public ApplicationDbContext(string connectionString) { _connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(_connectionString); } }
或者,可以使用 DbContextOptionsBuilder
創建 DbContextOptions
對象,然後將該對象傳遞到 DbContext
構造函數。 這使得為依賴關係註入配置的 DbContext
也能顯式構造。 例如,使用上述為 ASP.NET Core 的 Web 應用定義的 ApplicationDbContext
時:
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } } 可以創建 DbContextOptions,並可以顯式調用構造函數: var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>() .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test") .Options; using var context = new ApplicationDbContext(contextOptions);
4. DbContext 工廠
某些應用程式類型(例如
在這些情況下,可以使用
public void ConfigureServices(IServiceCollection services) { services.AddDbContextFactory<ApplicationDbContext>( options => options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test")); }
ApplicationDbContext
類必須公開具有 DbContextOptions<ApplicationDbContext>
參數的公共構造函數。 此模式與上面傳統 ASP.NET Core 部分中使用的模式相同。
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }
然後,可以通過構造函數註入在其他服務中使用 DbContextFactory
工廠。 例如:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory; public MyController(IDbContextFactory<ApplicationDbContext> contextFactory) { _contextFactory = contextFactory; }
然後,可以使用註入的工廠在服務代碼中構造 DbContext 實例。 例如:
public void DoSomething() { using (var context = _contextFactory.CreateDbContext()) { // ... } }
請註意,以這種方式創建的 DbContext
實例並非由應用程式的服務提供程式進行管理,因此必須由應用程式釋放。
5. 配置資料庫提供程式
每個 DbContext
實例都必須配置為使用一個且僅一個資料庫提供程式。 (DbContext
子類型的不同實例可用於不同的資料庫提供程式,但一個實例只能使用一個。)一個資料庫提供程式要使用一個特定的 Use*
調用進行配置。 例如,若要使用 SQL Server 資料庫提供程式:
public class ApplicationDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"); } }
這些 Use*
方法是由資料庫提供程式實現的擴展方法。 這意味著必須先安裝資料庫提供程式 NuGet 包,然後才能使用擴展方法。
提示
EF Core 資料庫提供程式廣泛使用
下表包含常見資料庫提供程式的示例。
資料庫系統 | 配置示例 | NuGet 程式包 |
---|---|---|
SQL Server 或 Azure SQL | .UseSqlServer(connectionString) |
|
Azure Cosmos DB | .UseCosmos(connectionString, databaseName) |
|
SQLite | .UseSqlite(connectionString) |
|
EF Core 記憶體中資料庫 | .UseInMemoryDatabase(databaseName) |
|
PostgreSQL* | .UseNpgsql(connectionString) |
|
MySQL/MariaDB* | .UseMySql(connectionString) |
|
Oracle* | .UseOracle(connectionString) |
警告
EF Core 記憶體中資料庫不是為生產用途設計的。 此外,它可能不是測試的最佳選擇。 有關詳細信息,請參閱
特定於資料庫提供程式的可選配置是在其他特定於提供程式的生成器中執行的。 例如,在連接到 Azure SQL 時,使用
public class ApplicationDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer( @"Server=(localdb)\mssqllocaldb;Database=Test", providerOptions => { providerOptions.EnableRetryOnFailure(); }); } }
提示
同一資料庫提供程式用於 SQL Server 和 Azure SQL。 但是,建議在連接到 SQL Azure 時使用
6. 其他 DbContext 配置
其他 DbContext
配置可以鏈接到 Use*
調用之前或之後(這不會有任何差別)。 例如,若要啟用敏感數據日誌記錄:
public class ApplicationDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .EnableSensitiveDataLogging() .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"); } }
下表包含 DbContextOptionsBuilder
調用的常見方法的示例。
DbContextOptionsBuilder 方法 | 作用 | 瞭解更多 |
---|---|---|
設置查詢的預設跟蹤行為 | ||
獲取 EF Core 日誌的一種簡單方法(EF Core 5.0 及更高版本) | ||
註冊 Microsoft.Extensions.Logging 工廠 |
||
在異常和日誌記錄中包括應用程式數據 | ||
更詳細的查詢錯誤(以性能為代價) | ||
忽略或引發警告和其他事件 | ||
註冊 EF Core 偵聽器 | ||
使用動態代理進行延遲載入 | ||
使用動態代理進行更改跟蹤 | 即將推出... |
7. DbContextOptions
與 DbContextOptions<TContext>
大多數接受 DbContextOptions
的 DbContext
子類應使用
public sealed class SealedApplicationDbContext : DbContext { public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions) : base(contextOptions) { } }
這可確保從依賴關係註入中解析特定 DbContext
子類型的正確選項,即使註冊了多個 DbContext
子類型也是如此。
提示
你的 DbContext 不需要密封,但對於沒有被設計為繼承的類,密封是最佳做法。
但是,如果 DbContext
子類型本身旨在繼承,則它應公開采用非泛型 DbContextOptions
的受保護構造函數。 例如:
public abstract class ApplicationDbContextBase : DbContext { protected ApplicationDbContextBase(DbContextOptions contextOptions) : base(contextOptions) { } }
這允許多個具體子類使用其不同的泛型 DbContextOptions<TContext>
實例來調用此基構造函數。 例如:
public sealed class ApplicationDbContext1 : ApplicationDbContextBase { public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions) : base(contextOptions) { } } public sealed class ApplicationDbContext2 : ApplicationDbContextBase { public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions) : base(contextOptions) { } }
請註意,這與直接從 DbContext
繼承的模式完全相同。 也就是說,出於此原因,DbContext
構造函數本身將接受非泛型 DbContextOptions
。
旨在同時進行實例化和繼承的 DbContext
子類應公開構造函數的兩種形式。 例如:
public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions) : base(contextOptions) { } protected ApplicationDbContext(DbContextOptions contextOptions) : base(contextOptions) { } }
8. 避免 DbContext 線程處理問題
Entity Framework Core 不支持在同一 DbContext
實例上運行多個並行操作。 這包括非同步查詢的並行執行以及從多個線程進行的任何顯式併發使用。 在這個上下文,使用 await 來確保所有的非同步操作完成於另一個方法調用之前。
當 EF Core 檢測到嘗試同時使用 DbContext
實例的情況時,你將看到 InvalidOperationException
,其中包含類似於以下內容的消息:
在同一個上下文,一個非同步操作還沒完成,另一個操作就開始了。。 這通常是由使用同一個 DbContext 實例的不同線程引起的,但不保證實例成員是線程安全的。
檢測不到併發訪問時,可能會導致未定義的行為、應用程式崩潰和數據損壞。
3. 上下文池
DbContext
通常是一個輕型對象:創建和釋放它不涉及資料庫操作,而大多數應用程式都可以這樣做,而不會對性能產生任何明顯的影響。 但是,每個上下文實例確實設置了執行其職責所需的各種內部服務和對象,在高性能方案中,持續執行此操作的開銷可能很大。 對於這些情況,EF Core 可以 共用 上下文實例:釋放上下文時,EF Core 會重置其狀態並將其存儲在內部池中;下一次請求新實例時,將返回該共用實例,而不是設置新的實例。 上下文池允許你在程式啟動時僅支付一次上下文設置成本,而不是連續付費。
請註意,上下文池與資料庫連接池正交,後者在資料庫驅動程式的較低級別進行管理。
依賴註入形式
使用 EF Core 的 ASP.NET Core 應用中的典型模式涉及通過
若要啟用上下文池,AddDbContext
只需將 替換為
builder.Services.AddDbContextPool<WeatherForecastContext>( o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));
參數poolSize
非依賴註入形式
EF Core 6.0 引入無需依賴關係註入的池。
若要在不使用依賴項註入的情況下使用上下文池,請 PooledDbContextFactory
初始化 並從中請求上下文實例:
var options = new DbContextOptionsBuilder<PooledBloggingContext>() .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True") .Options; var factory = new PooledDbContextFactory<PooledBloggingContext>(options); using (var context = factory.CreateDbContext()) { var allPosts = context.Posts.ToList(); }
PooledDbContextFactory
構造函數的 poolSize
參數設置池保留的最大實例數(在 EF Core 6.0 中預設為 1024,在以前的版本中為 128)。 一旦超過 poolSize
,就不會緩存新的上下文實例,EF 會回退到按需創建實例的非池行為。
連接池優化
連接池溢出的問題:
System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached. at System.Data.Common.ADP.ExceptionWithStackTrace(Exception e)
每個 DbContext 實例都會占用一個資料庫連接(SqlConnection),不啟用 DbContextPool 的時候,請求一結束,對應 DbContext 實例就被 Dispose ,資料庫連接就會被放回連接池。而使用 DbContextPool 的時候,請求結束後 DbContext 不會被 Dispose 而是被放回 DbContextPool ,DbContext 被放回屬於自己的池中,就意味它對應的資料庫連接不會被放回它所屬的連接池。DbContextPool 中的每一個 DbContext 都對應一個資料庫連接,DbContextPool 中每多一個 DbContext ,資料庫連接池中就會少一個資料庫連接。當這兩個池的大小不一樣且 DbContextPool 大於資料庫連接池,問題就來了,DbContextPool 根據自家池(假設是128)子的大小暢快地向池中填 DbContext ,渾然不顧資料庫連接池的大小(假設是100),當填到第 101 個 DbContext 時就會出現上面的錯誤。
解決辦法:
可以有兩種方法:
-
將DbContextPool大小設置為比資料庫最大連接數小即可
-
以Mysql為例,查詢當前MySQL最大連接數據
show variables like '%max_connections%';
-
設置DbContextPool池大小
builder.Services.AddDbContextPool<MySqlBlogContext>(p => { p.UseMySql(builder.Configuration.GetConnectionString("MySQL"), new MySqlServerVersion("5.7")); },poolSize:127);
-
-
如果資料庫伺服器性能較好,可以將資料庫最大連接數設置為比DbContextP