當前位置:
首頁 > 最新 > Entity Framework Core 2.0 入門

Entity Framework Core 2.0 入門

該文章比較基礎, 不多說廢話了, 直接切入正題.

該文分以下幾點:

創建Model和資料庫

使用Model與資料庫交互

查詢和保存關聯數據

此外還即將支持CosmosDB和 Oracle.


查詢:

EF.Functions.Like()

Linq解釋器的改進

全局過濾(按類型)

編譯查詢(Explicitly compiled query)

GroupJoin的SQL優化.

映射:

Type Configuration 配置

Owned Entities (替代EF6的複雜類型)

Scalar UDF映射

分表

性能和其他

DbContext Pooling, 這個很好

Raw SQL插入字元串.

Logging

更容易定製配置


1.創建資料庫和Model

項目結構如圖:

由於我使用的是VSCode, 所以需要使用命令行:

mkdir LearnEf &&cd LearnEfdotnet new sln // 創建解決方案mkdir LearnEf.Domains&&cd LearnEf.Domainsdotnet new classlib // 創建LearnEf.Domains項目cd ..mkdir LearnEf.Data&&cd LearnEf.Datadotnet new classlib // 創建LearnEf.Data項目cd ..mkdir LearnEf.UI&&cd LearnEf.UIdotnet new console // 創建控制台項目cd ..mkdir LearnEf.Tests&&cd LearnEf.Testsdotnet new xunit // 創建測試項目

為解決方案添加項目:

dotnet sln add LearnEf.UI/LearnEf.UI.csprojdotnet sln add LearnEf.Domains/LearnEf.Domains.csprojdotnet sln add LearnEf.Data/LearnEf.Data.csprojdotnet sln add LearnEf.Tests/LearnEf.Tests.csproj

為項目之間添加引用:

LearnEf.Data依賴LearnEf.Domains:

cd LearnEf.Datadotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj

LearnEf.Console依賴LearnEf.Domains和LearnEf.Data:

cd ../LearnEf.UIdotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj ../LearnEf.Data/LearnEf.Data.csproj

LearnEf.Test依賴其它三個項目:

cd ../LearnEf.Testsdotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj ../LearnEf.Data/LearnEf.Data.csproj ../LearnEf.UI/LearnEf.UI.csproj

(可能需要執行dotnet restore)

在Domains項目下直接建立兩個Model, 典型的一對多關係Company和Department:

usingSystem;usingSystem.Collections.Generic;namespaceLearnEf.Domains{publicclassCompany {publicCompany() { Departments=newList(); }publicintId {get;set; }publicstringName {get;set; }publicDateTime StartDate {get;set; }publicList Departments {get;set; } }}

namespaceLearnEf.Domains{publicclassDepartment {publicintId {get;set; }publicintCompanyId {get;set; }publicCompany Company {get;set; } }}


首先Data項目肯定需要安裝這個庫, 而我要使用sql server, 參照官方文檔, 直接在解決方案下執行這個命令:

dotnet add ./LearnEf.Data package Microsoft.EntityFrameworkCore.SqlServer

dotnet restore


在Data項目下創建MyContext.cs:

usingLearnEf.Domains;usingMicrosoft.EntityFrameworkCore;namespaceLearnEf.Data{publicclassMyContext : DbContext {publicDbSet Companies {get;set; }publicDbSet Departments {get;set; } }}

在EFCore里, 必須明確指定Data Provider和Connection String.

可以在Context裡面override這個Onconfiguring方法:

有一個錯誤, 應該是Server=localhost;

(這裡無需調用父類的方法, 因為父類的方法什麼也沒做).

UseSqlServer表示使用Sql Server作為Data Provider. 其參數就是Connection String.

在運行時EfCore第一次實例化MyContext的時候, 就會觸發這個OnConfiguring方法. 此外, Efcore的遷移Api也可以獲得該方法內的信息.


簡單的來說就是Model變化 --> 創建migration文件 --> 應用Migration到資料庫或生成執行腳本.


由於我使用的是VSCode+dotnet cli的方法, 所以需要額外的步驟來使dotnet ef命令可用.

可以先試一下現在的效果:

可以看到, dotnet ef 命令還不可用.

所以參考官方文檔: https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet

可執行項目(Startup project)需要EFCore遷移引擎庫, 所以對LearnEf.UI添加這個庫:

dotnet add ./LearnEf.UI package Microsoft.EntityFrameworkCore.Designdotnet restore

然後打開LearnEf.UI.csproj 添加這段代碼, 這個庫是EF的命令庫:

最後內容如下:

然後再執行dotnet ef命令, 就應該可用了:

現在, 添加第一個遷移:

cd LearnEf.UIdotnet ef migrations add Initial--project=../LearnEf.Data

--project參數是表示需要使用的項目是哪個.

命令執行後, 可以看到Data項目生成了Migrations目錄和一套遷移文件和一個快照文件:


前邊帶時間戳的那兩個文件是遷移文件.

另一個是快照文件, EFCore Migrations用它來跟蹤所有Models的當前狀態. 這個文件非常重要, 因為下次你添加遷移的時候, EFcore將會讀取這個快照並將它和Model的最新版本做比較, 就這樣它就知道哪些地方需要有變化.

這個快照文件解決了老版本Entity Framework的一個頑固的團隊問題.


生成創建資料庫的SQL腳本:

dotnet ef migrations script --project=../LearnEf.Data/LearnEf.Data.csproj

Sql腳本直接列印在了Command Prompt裡面. 也可以通過指定--output參數來輸出到具體的文件.

這裡, 常規的做法是, 針對開發時的資料庫, 可以通過命令直接創建和更新資料庫. 而針對生產環境, 最好是生成sql腳本, 然後由相關人員去執行這個腳本來完成資料庫的創建或者更新.

直接創建資料庫:

dotnet ef database update --project=../LearnEf.Data/LearnEf.Data.csproj --verbose

--verbose表示顯示執行的詳細過程, 其結果差不多這樣:

這裡的執行過程和邏輯是這樣的: 如果資料庫不存在, 那麼efcore會在指定的連接字元串的地方建立該資料庫, 並應用當前的遷移. 如果是生成的sql腳本的話, 那麼這些動作必須由您自己來完成.

然後查看一下生成的表.

不過首先, 如果您也和我一樣, 沒有裝Sql server management studio或者 Visual Studio的話, 請您先安裝VSCode的mssql這個擴展:

重啟後, 建立一個Sql文件夾, 然後建立一個Tables.sql文件, 打開命令面板(windows: Shift+Ctrl+P, mac: Cmd+Shift+P), 選擇MS SQL: Connect.

然後選擇Create Connection Profile:

輸入Sql的伺服器地址:

再輸入資料庫名字:

選擇Sql Login(我使用的是Docker, 如果windows的話, 可能使用Integrated也可以):

輸入用戶名:

密碼:

選擇是否保存密碼:

最後輸入檔案的名字:

隨後VSCode將嘗試連接該資料庫, 成功後右下角會這樣顯示 (我這裡輸入有一個錯誤, 資料庫名字應該是LearnEF):

隨後在該文件中輸入下面這個sql語句來查詢所有的Table:

--Table 列表SELECT*FROMINFORMATION_SCHEMA.TABLESWHERETABLE_TYPE="BASE TABLE";

執行sql的快捷鍵是windows: Shift+Ctrp+E, mac: Cmd+Shift+E, 或者滑鼠右鍵.

結果如圖:

OK表是創建成功了(還有一個遷移歷史表, 這個您應該知道).

接下來我看看錶的定義:

--Companies表:execsp_help"Companies";

其中Name欄位是可空的並且長度是-1也就是nvarchar(Max).

Departments表的Name欄位也是一樣的.

再看看那個MigrationHistory表:

--MigrationHistory:SELECT*FROMdbo.__EFMigrationsHistory;

可以看到, efcore到migration 歷史表裡面只保存了MigrationId.

在老版本到ef里, migration歷史表裡面還保存著當時到遷移的快照, 創建遷移的時候還需要與資料庫打交道. 這就是我上面提到的如果團隊使用ef和源碼管理的話, 就會遇到這個非常令人頭疼的問題.


在解決方案里再建立一個asp.net core mvc項目:

mkdir LearnEf.Web &&cd LearnEf.Webdotnet new mvc

在解決方案里添加該項目:

dotnet sln add ./LearnEf.Web/LearnEf.Web.csproj

為該項目添加必要的引用:

cd LearnEf.Webdotnet add reference ../LearnEf.Domains/LearnEf.Domains.csproj ../LearnEf.Data/LearnEf.Data.csproj

為測試項目添加該項目引用:

cd ../*Testsdotnet add reference ../LearnEf.Web/LearnEf.Web.csproj

操作完之後, 我們可以做以下調整, 去掉MyContext裡面的OnConfiguring方法, 因為asp.net core有內置的依賴注入機制, 我可以把已經構建好的DbContextOptions直接注入到構造函數里:

這樣的話, 我們可以讓asp.net core來決定到底使用哪個Data Provider和Connection String:

這也就意味著, Web項目需要引用EfCore和Sql Provider等, 但是不需要, 因為asp.net core 2.0這個項目模版引用了AspNetCore.All這個megapack, 裡面都有這些東西了.

雖然這個包什麼都有, 也就是說很大, 但是如果您使用Visual Studio Tooling去部署的話, 那麼它只會部署那些項目真正用到的包, 並不是所有的包.

接下來, 在Web項目的Startup添加EfCore相關的配置:

public void ConfigureServices(IServiceCollection services) { services.AddMvc();services.AddDbContext(options=> options.UseSqlServer("Server=localhost; Database=LearnEf; User Id=sa; Password=Bx@steel1;"));}

這句話就是把MyContext註冊到了asp.net core的服務容器中, 可以供注入, 同時在這裡指定了Data Provider和Connection String.

與其把Connection String寫死在這裡, 不如使用appSettings.json文件:

然後使用內置的方法讀取該Connection String:

publicvoidConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddDbContext(options=> options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); }

回到命令行進入Web項目, 使用dotnet ef命令:

說明需要添加上面提到的庫, 這裡就不重複了.

然後, 手動添加一個Migration叫做InitialAspNetCore:

dotnet ef migrations add InitialAspNetCore --project=../LearnEf.Data

看一下遷移文件:

是空的, 因為我之前已經使用UI那個項目進行過遷移更新了. 所以我要把這個遷移刪掉:

dotnet ef migrations remove --project=../LearnEf.Data

然後這兩個遷移文件就刪掉了:


這部分的官方文檔在這: https://docs.microsoft.com/en-us/ef/core/modeling/relationships

對於多對多關係, efcore需要使用一個中間表, 我想基本ef使用者都知道這個了, 我就直接貼代碼吧.

建立一個City.cs:

namespaceLearnEf.Domains{publicclassCity {publicintId {get;set; }publicstringName {get;set; } }}

Company和City是多對多的關係, 所以需要建立一個中間表,叫做 CompanyCity:

namespaceLearnEf.Domains{publicclassCompanyCity {publicintCompanyId {get;set; }publicintCityId {get;set; }publicCompany Company {get;set; }publicCity City {get;set; } }}

修改Company:

修改City:

儘管Efcore可以推斷出來這個多對多關係, 但是我還是使用一下FluentApi來自定義配置一下這個表的主鍵:

MyContext.cs:

usingLearnEf.Domains;usingMicrosoft.EntityFrameworkCore;namespaceLearnEf.Data{publicclassMyContext : DbContext {publicMyContext(DbContextOptionsoptions) :base(options) { }publicDbSet Companies {get;set; }publicDbSet Departments {get;set; }publicDbSet CompanyCities {get;set; }protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder) {modelBuilder.Entity() .HasKey(c=>new{ c.CompanyId, c.CityId });}//protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)//{//optionsBuilder.UseSqlServer("Server=localhost; Database=LearnEf; User Id=sa; Password=Bx@steel1;");//base.OnConfiguring(optionsBuilder);//}}}

完整的寫法應該是:

其中紅框裡面的部分不寫也行.

接下來建立一個一對一關係, 創建Model叫Owner.cs:

namespaceLearnEf.Domains{publicclassOwner {

public int Id { get; set;}publicintCompanyId {get;set; }publicstringName {get;set; }publicCompany Company {get;set; } }}

修改Company:

配置關係:

protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasKey(c=>new{ c.CompanyId, c.CityId }); modelBuilder.Entity().HasOne(x =>x.Company) .WithMany(x=> x.CompanyCities).HasForeignKey(x =>x.CompanyId); modelBuilder.Entity().HasOne(x =>x.City) .WithMany(x=> x.CompanyCities).HasForeignKey(x =>x.CityId);modelBuilder.Entity().HasOne(x => x.Company).WithOne(x =>x.Owner) .HasForeignKey(x =>x.CompanyId);}

這裡面呢, 這個Owner對於Company 來說 是可空的. 而對於Owner來說, Company是必須的. 如果針對Owner想讓Company是可空的, 那麼CompanyId的類型就應該設置成int?.

再添加一個遷移:

dotnet ef migrations add AddRelationships --project=../LearnEf.Data

查看遷移文件:

查看一下快照;

沒問題, 那麼更新資料庫:

dotnet ef database update AddRelationships --project=../LearnEf.Data --verbose

更新成功:


這部分請查看官方文檔吧, 很簡單, 我實驗了幾次, 但是目前還沒有這個需求.


使用Model與資料庫交互

對於asp.net core 2.0項目, 參考官方文檔: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?tabs=aspnetcore2x

實際上, 項目已經配置好Logging部分了, 默認是列印到控制台和Debug窗口的. 源碼: https://github.com/aspnet/MetaPackages/blob/dev/src/Microsoft.AspNetCore/WebHost.cs

而對於console項目, 文檔在這: https://docs.microsoft.com/en-us/ef/core/miscellaneous/logging

需要對LearnEf.Data項目添加這個包:

cd LearnEf.Datadotnet add package Microsoft.Extensions.Logging.Console

dotnet restore

然後為了使用console項目, 需要把MyContext改回來:

這部分首先是使用LoggerFactory創建了一個特殊的Console Logger. .net core的logging可以顯示很多的信息, 這裡我放置了兩個過濾: 第一個表示只顯示Sql命令, 第二個表示細節的顯示程度是Information級別.

最後還要在OnConfiguring方法里告訴modelBuilder使用MyLoggerFactory作為LoggerFactory.

這就配置好了.


這部分很簡單, 打開UI項目的Program.cs:

這裡都懂的, 創建好model之後, 添加到context的DbSet屬性里, 這時context就開始追蹤這個model了.

SaveChanges方法, 會檢查所有被追蹤的models, 讀取他們的狀態. 這裡用到是Add方法, context就會知道這個model的狀態是new, 所以就應該被插入到資料庫. 然後它就根據配置會生成出相應的sql語句, 然後把這個SQL語句執行到資料庫. 如果有返回數據的話, 就取得該數據.

下面就運行一下這個console程序:

dotnet run --project=./LearnEf.UI

看下控制台:

可以看到輸出了sql語句, 而且這個出入動作後, 做了一個查詢把插入數據生成的Id取了回來.

默認情況下log不顯示傳進去的參數, 這是為了安全. 但是可以通過修改配置來顯示參數:

然後控制台就會顯示這些參數了:


可以使用AddRange添加多條數據. 其參數可以是params或者集合.

可以看到這個和之前Add的Sql語句是完全不同的:

這個語句我不是很明白.

批量添加不同類型的數據:

使用context的AddRange或Add方法, DbContext可以推斷出參數的類型, 並執行正確的操作. 上面的方法就是使用了DbContext.AddRange方法, 一次性添加了兩種不同類型的model.

這兩個方法對於寫一些通用方法或者處理複雜的情況是很有用的.

Sql Server對於批量操作的限制是, 一次只能最多處理1000個SQL命令, 多出來的命令將會分批執行.

如果想更改這個限制, 可以這樣配置參數:


針對DbSet, 使用Linq的ToList方法, 會觸發對資料庫對查詢操作:

首先把Company的ToString方法寫上:

這樣方便輸入到控制台.

然後寫查詢方法:

看結果:

EfCore到查詢有兩類語法, 一種是Linq方法, 另一種是Linq查詢語法:

這種是Linq方法:

下面這種是Linq查詢語法:

我基本都是使用第一種方法.

除了ToList(Async)可以觸發查詢以外, 遍歷foreach也可以觸發查詢:

但是這種情況下, 可能會有性能問題. 因為:

在遍歷開始的時候, 資料庫連接打開, 並且會一直保持打開的狀態, 直到遍歷結束.

所以如果這個遍歷很耗時, 那麼可能會發生一些問題.

最好的辦法還是首先執行ToList, 然後再遍歷.


這部分和以前的EF基本沒啥變化.

這個很簡單, 不說了.

這裡列一下可觸發查詢的Linq方法:

還有個兩個方法是DbSet的方法, 也可以觸發查詢動作:

上面這些方法都應該很熟悉, 我就不寫了.

過濾的條件可以直接家在上面的某些方法裡面, 例如:

通過主鍵查詢, 就可以用DbSet的Find方法:

這個方法有個優點, 就是如果這條數據已經在Context裡面追蹤了, 那麼查詢的時候就不查資料庫了, 直接會返回內存中的數據.

EF.Functions.Like這個方法是新方法, 就像是Sql語句裡面的Like一樣, 或者字元串的Contains方法:

這個感覺更像Sql語句, 輸出到Console的Sql語句如下:

這裡還要談的是First/FirstOrDefault/Last/LastOrDefaut方法.

使用這些方法必須先使用OrderBy/OrderByDescending排序. 雖然不使用的話也不會報錯, 但是, 整個過程就會變成這樣, context把整個表的數據家在到內存里, 然後返回第一條/最後一條數據. 如果表的數據比較多的話, 那麼就會有性能問題了.


很簡單, context所追蹤的model屬性變化後, SaveChanges就會更新到資料庫.

當然, 多個更新操作和插入等操作可以批量執行.


就是這種情況, 新的context一開始並沒有追蹤one這個數據. 通過使用Update方法, 追蹤並設置狀態為update. 然後更新到資料庫.

可以看到, 在這種情況下, EfCore會更新該model到所有屬性.

Update同樣也有DbSet的UpdateRange方法, 也有context到Update和UpdateRange方法, 這點和Add是一樣的.

還有一種方法用於更新, 這個以後再說.


DbContext只能刪除它追蹤的model.

非常簡單, 從log可以看到, 刪除動作只用到了主鍵:

如果是刪除的離線model, 那麼Remove方法首先會讓Dbcontext追蹤這個model, 然後設置狀態為Deleted.

刪除同樣有RemoveRange方法.


這部分請看文檔:

命令: DbContext.Database.ExecuteSqlCommand();

查詢: DbSet.FromSql() https://docs.microsoft.com/en-us/ef/core/querying/raw-sql;

這個方法目前還有一些限制, 它只能返回實體的類型, 並且得返回domain model所有的屬性, 而且屬性的名字必須也得一一對應. SQL語句不可以包含關聯的導航屬性, 但是可以配合Include使用以達到該效果(https://docs.microsoft.com/en-us/ef/core/querying/raw-sql#including-related-data).

更多的傳遞參數方式還需要看文檔.


查詢和保存關聯數據.

我之前忘記在Department裡面添加Name欄位了, 現在添加一下, 具體過程就不寫了.

插入關聯數據有幾種情況:

1.直接把要添加的Model的導航屬性附上值就可以了,這裡的Department不需要寫外鍵.

看一下Sql:

這個過程一共分兩步: 1 插入主表, 2,使用剛插入主表數據的Id, 插入子表數據.

2.為資料庫中的數據添加導航屬性.

這時, 因為該數據是被context追蹤的, 所以只需在它的導航屬性添加新記錄, 然後保存即可.

3.離線數據添加導航屬性.

這時候就必須使用外鍵了.


也就是查詢的時候一次性把數據和其導航屬性的數據一同查詢出來.

看看SQL:

這個過程是分兩步實現的, 首先查詢了主表, 然後再查詢的子表. 這樣做的好處就是性能提升.

(FromSql也可以Include).

預載入子表的子表:

可以使用ThenInclude方法, 這個可以老版本ef沒有的.

這裡查詢Department的時候, 將其關聯表Company也查詢了出來, 同時也把Company的關聯表Owner也查詢了出來.


使用Select可以返回匿名類, 裡面可以自定義屬性.

這個匿名類只在方法內有效.

看下SQL:

可以看到SQL中只Select了匿名類裡面需要的欄位.

如果需要在方法外使用該結果, 那麼可以使用dynamic, 或者建立一個對應的struct或者class.


SQL:

這個比較簡單. 看sql一切就明白了.


也會分兩種情況, 被追蹤和離線數據.

被追蹤的情況下比較簡單, 直接修改關聯數據的屬性即可:

看一下SQL:

確實改了.

這種情況下, 刪除關聯資料庫也很簡單:

看下SQL:

刪除了.

下面來看看離線狀態下的操作.

這裡需要使用update, 把該數據添加到context的追蹤範圍內.

看一下SQL:

這個就比較怪異了.

它update了該departmt和它的company以及company下的其他department和company的owner.這些值倒是原來的值.

這是因為, 看上面的代碼, 查詢的時候department的關聯屬性company以及company下的departments和owner一同被載入了.

儘管我只update了一個department, 但是efcore把其他關聯的數據都識別出來了.

從DbContext的ChangeTracker屬性下的StateManger可以看到有多少個變化.

這一點非常的重要.

如何避免這個陷阱呢?

可以這樣做: 直接設置dbContext.Entry().State的值

這時, 再看看SQL:

嗯. 沒錯, 只更新了需要更新的對象.

2.1版本將於2018年上半年發布, 請查看官網的路線圖: https://github.com/aspnet/EntityFrameworkCore/wiki/roadmap

完.


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 草根專欄 的精彩文章:

TAG:草根專欄 |