【ASP.NET Core】EF Core 模型與數據庫的建立

你們好,歡迎收看由土星衛視直播的大型綜藝節目——老周吹逼逼。html

今天我們吹一下 EF Core 有關的話題。先說說模型和數據庫是怎麼建起來的,說裝逼一點,就是咱們常說的 「code first」。就是你先建立了數據模型,而後再根據模型來建立數據庫。這種作法的一個好處是讓面向對象的邏輯更好地表現出來。之前,我們一般是先建立數據庫的。sql

像 EF 這麼嗨的東西,ASP.NET Core 中天然也是少不了的,即 EF Core。數據庫

好了,以上就是理論部分,比較乏味,是吧。那好,下面我們乾點正事。服務器

 

構建模型

創建模型很簡單,就是定義一個類(爲了好理解,老周暫且不說關係模型)。來,看看,就像下面這個類,假設它表示的是某工廠生產的山寨產品信息。app

    public class Product
    {
        public int ProdID { get; set; }
        public string ProdName { get; set; }
        public DateTime FinishDate { get; set; }
        public double Weight { get; set; }
    }

有人會問:完事了?嗯,完事了,這就是一個模型了,但仍是不能建立數據庫的。ide

 

繼承 DBContext

雖然我們有了山寨產品的模型類,但你還得實現一個數據上下文。一般呢,數據上下文是映射到某個數據庫的。上下文的定義是從 DbContext 類派生出一個類,而後,把它與模型類關聯起來。函數

    public class MyDBContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
    }

DbSet 會映射到數據庫中的一個表。工具

爲了實現依賴注入,以及可以在 Startup 類中進行配置,你能夠在本身實現的 DBContext 子類中公開構造函數,而且接收一個 DbContextOptions<TContext> 類型的參數注入,TContext 就是我們本身定義的從 DBContext 類派生的類。post

    public class MyDBContext : DbContext
    {
        public MyDBContext(DbContextOptions<MyDBContext> options)
            : base(options)
        {
            // 暫無其餘代碼
        }

        public DbSet<Product> Products { get; set; }
    }

 

註冊服務

有了模型和數據上下文,接下來我們要在 Startup 類中註冊一下相關的服務,而且配置一下像鏈接字符串之類的參數。性能

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<MyDBContext>(option => { option.UseSqlServer("server=(localdb)\\MSSQLLocalDB;database=DemoDB"); });         }

 

建立數據庫「遷移」

建立遷移的好處是靈活,若是你的模型後面修改了(好比添加了一個屬性),那麼你能夠在原有的遷移基礎上再添加新的遷移,這些數據遷移會不斷疊加,因此,你不須要刪除過去的遷移版本,由於後面添加的不會重複,只會包含更新數據模型的代碼。

建立遷移有多種方式:一、dotnet 命令行;二、VS 中的 nuget 控制檯;三、直接用代碼。

dotnet cli 即便用 dotnet 命令行工具來對數據模型進行遷移,其命令爲 dotnet ef <...>。這裏老周演示的是用 VS 中的 nuget 控制檯來處理,dotnet cli 的方法相似,你能夠輸入 dotnet ef --help 來查看幫助。

在 VS 中,打開 【工具】-【NuGet 包管理器】-【程序包管理器】菜單項,隨後就能打開控制檯窗口。你能夠輸入如下命令查看幫助文檔。

get-help about_EntityFrameworkCore

你要是以爲名字太長了,能夠這樣輸入

get-help about_*core

星號是通配符,它會查找全部以 about_ 開頭,以 Core 結尾的說明文檔。

好,下面我們爲前面已定義好的 MyDBContext 生成數據遷移代碼,使用的命令是 Add-Migration。用法以下。

Add-Migration [-Name] <String> [-OutputDir <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] 

其實後面還有個參數列表的,但用不上,就不列出來了。注意,只有位於第一個位置的 -Name 參數名能夠省略,後面的都不能省略參數名。即對於遷移點的命名,你能夠輸入

Add-Migration -Name "demo001"

也能夠輸入

Add-Migration "demo001"

-OutputDir 指的是生成的代碼放在哪一個目錄下面,默認叫 Migrations。注意它是相對於項目目錄的路徑。-Context 指定的是你本身定義的 DBContext 的子類的名稱,包含命名空間名稱,若是是當前項目,能夠不寫。

-Project 和 -StartupProject 通常不用刻意指定,若是解決方案中有多個項目,能夠指定一下,生成的遷移屬於哪一個項目。-StartupProject 能夠不指定,讓它選擇與解決方案配置一致的啓動項目。

好,下面我們爲剛剛定義的 MyDBContext 生成數據遷移。輸入

add-migration "demo001" -Context "MyDBContext" -OutputDir "CustMigrations"

執行後就呵呵了,出現一個警告和一個異常。

警告信息只是 SDK 與運行時版本沒統一而已,這個能夠不鳥它,不影響命令執行。最大的問題是發生異常,這會致使命令不能執行。發生異常是由於咱們上面定義的那個 Product 類,沒有聲明主鍵。

因而,咱們就讓 ProdID 做爲主鍵,方法有兩種。第一,經過「數據批註」,就像這樣。

    public class Product
    {
        [Key] public int ProdID { get; set; } public string ProdName { get; set; }
        public DateTime FinishDate { get; set; }
        public double Weight { get; set; }
    }

若是你認爲用特性來批註很難看,那就用第二種方法,在繼承 DBContext 的類中重寫 OnModelCreating 方法。

    public class MyDBContext : DbContext
    {
        ……

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Product>().HasKey(p => p.ProdID);         }
    }

如今再執行一次 Add-Migration 命令,就順利建立數據遷移了。

假如如今我以爲模型要修改,新增一個 Remark 屬性。

    public class Product
    {
        public int ProdID { get; set; }
        public string ProdName { get; set; }
        public DateTime FinishDate { get; set; }
        public double Weight { get; set; }
        public string Remark { get; set; }     }

此時,你不用刪除前面建立的遷移,你只須要再加一個遷移便可,它會自動累積的。

add-migration "demo002" -Context "MyDBContext" -OutputDir "CustMigrations"

你能看到,demo002 遷移生成的代碼,僅僅是添加了 Remark 列。

    public partial class demo002 : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<string>( name: "Remark", table: "Products", nullable: true);         }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropColumn( name: "Remark", table: "Products");         }
    }

因此,看得出來,它不會重複生成表結構的。Up 方法表示的是當前的狀態,Down 方法是在執行 Remove-Migration 時進行回退,回退時刪除 Remark 列。

 

建立數據庫

有了上面的步聚,如今能夠建立數據庫了。這裏老周以 SQLLocalDB 爲例,在 CMD 中啓動默認的 MSSQLLocalDB 實例。

sqllocaldb start mssqllocaldb

回到 VS 中,執行 Update-Database 命令。

update-database

無參數的狀況下,執行全部遷移中的內容,爲了使建立的數據庫結構完整,應該執行全部遷移。

SQLLocalDB 建立的數據庫默認存放在你的用戶目錄下,即 C:\\Users\\Your name\\ 下面,路徑變量是 %userprofile%。

當你想刪除數據庫時,能夠輸入如下命令。

drop-database -Context "MyDBContext"

這時候,它會問你,真的要刪庫跑路嗎?

此時你心意已決,刪庫跑路,輸入 Y 或 A,確認。

 

測試數據庫

 好了,如今,模型也建好了,數據庫也有了,能夠來測一下了。

先建立個控制器。

    public class DemoController : Controller
    {
        MyDBContext _dbcontext;
        public DemoController(MyDBContext context)
        {
            _dbcontext = context;
        }

        [HttpGet]
        public ActionResult Products()
        {
            return View(_dbcontext.Products.ToList());
        }

        [HttpPost]
        public ActionResult Products(Product p)
        {
            if (ModelState.IsValid)
            {
                _dbcontext.Products.Add(p);
                _dbcontext.SaveChanges();
            }
            return View(_dbcontext.Products.ToList());
        }
    }

db context 能夠在構造函數能過依賴注入來獲取,由於前面咱們已經在 Startup.ConfigureServices 方法中註冊了相關服務。添加新記錄時直接把方法參數接收到的 Product 實例 Add 到 DbSet 中便可,但要記得調用 SaveChanges 方法,由於調用方法後數據纔會真正寫入數據庫。

控制器中包含了兩個 Products 的 action 方法,使用如下路由規則,能夠匹配出兩個方法。

            app.UseMvc(route =>
            {
                route.MapRoute("test", "{controller=Demo}/{action=Products}");
            });

解決方法就是,無參數的 Products 方法以 GET 方式訪問,而帶參數的 Products 方法以 POST 方式訪問。

建立一個與 Products 方法同名的視圖。在視圖中用 @model 指令定義 Model 的類型爲 List<Product>,由於上面控制器中,調用 View 方法時,傳遞給視圖的是 List<Product> 類型的 Model。

視圖代碼以下。

@using Web7362
@model List<Product>
@addTagHelper  *,Microsoft.AspNetCore.Mvc.TagHelpers
<html>
<body>
    <div>
        <form method="post">
            <table>
                <tr>
                    <td>
                        產品名稱:
                    </td>
                    <td><input type="text" name="ProdName" /></td>
                </tr>
                <tr>
                    <td>完成日期:</td>
                    <td><input name="FinishDate" type="date"/></td>
                </tr>
                <tr>
                    <td>產品重量:</td>
                    <td>
                        <input name="Weight"/>
                    </td>
                </tr>
                <tr>
                    <td>產品備註:</td>
                    <td><input name="Remark" type="text" /></td>
                </tr>
                <tr>
                    <td colspan="2">
                        <input type="submit" value="新增" />
                    </td>
                </tr>
            </table>
        </form>
    </div>
    <div>
        <table border="1">
            @foreach(var p in Model)
            {
                <tr>
                    <td>@p.ProdID</td>
                    <td>@p.ProdName</td>
                    <td>@p.FinishDate</td>
                    <td>@p.Weight</td>
                    <td>@p.Remark</td>
                </tr>
            }
        </table>
    </div>
</body>
</html>

 第一個 div 中的 form 用於提交新的山寨產品記錄,第二個 div 用來顯示產品列表。

當提交時,如何把 form 中輸入的內容傳遞給 Product 新對象,你可能會想到使用 asp-for 標籤幫助器。但此處不能使用 asp-for 幫助器,由於 Model 的類型是 List<Product> ,不是 Product 類型。

 那咋辦呢,能夠利用 input 元素的 name 值,將 name 值設置爲與  Product 類的各屬性名稱相同的值便可。

     <input type="text" name="ProdName" />
     <input name="FinishDate" type="date"/>
     <input name="Weight"/>
     <input name="Remark" type="text" />

這樣設置後,在提交時 Model Binder 就能夠自動識別並填充 Product 實例的各個屬性了。

你也會問了,爲啥沒有爲 ProdID 屬性弄個 input 元素?由於這個屬性是主鍵,其值由數據庫生成,沒必要手動輸入。

來來來,看看效果。

IDesignTimeDbContextFactory<out TContext> 接口

這個接口有兩種狀況下,你能夠考慮使用。

一、默認項目模板生成的 Main 方法被你修改了。準確地說,是你刪除了 CreateWebHostBuilder 方法。默認生成的 Main 是這樣的。

        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();

而後,你嫌它生成的代碼很差看,也以爲日誌太多影響性能,因此改成這樣。

        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseStartup<Startup>()
                .UseKestrel()
                .UseUrls("http://localhost:7676")
                .UseEnvironment(EnvironmentName.Development)
                .UseSetting(WebHostDefaults.ApplicationKey, "大飛俠充值系統")
                .Build();
            host.Run();
        }

這樣一來,你想執行 Add-Migration 命令,就會收到這條錯誤。

二、設計時須要。有時候,你用來開發測試的數據庫服務器和正式投入使用的不是同一個服務器。這時候,你能夠實現 IDesignTimeDbContextFactory<out TContext> 接口,建立用於測試的數據上下文(尤爲是鏈接字符串)。

 

下面用另外一個示例來演示一下。

先建立一個模型類。

    public class Charge
    {
        public int ID { get; set; }
        public DateTime Time { get; set; }
        public decimal Money { get; set; }
        public string PhoneNo { get; set; }
    }

而後是實現數據上下文。

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

        public DbSet<Charge> Charges { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Charge>().HasKey(o => o.ID);
        }
    }

 

因爲默認的 Main 函數被修改了,執行 Add-Migration 命令,會發生錯誤。

其實,錯誤信息中已經告訴你解決方法了,就是實現 IDesignTimeDbContextFactory<out TContext> 接口。因此,就實現一下唄。

    public class CustDesigntimeContext : IDesignTimeDbContextFactory<DemoDBContext>
    {
        public DemoDBContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<DemoDBContext>();
            // 設置鏈接字符串
            optionsBuilder.UseSqlServer("server=(localdb)\\mssqllocaldb;database=test_db");
            // 建立上下文實例
            return new DemoDBContext(optionsBuilder.Options);
        }
    }

如今,再執行 Add-Migration 命令就正常了。

add-migration "check01" -outputdir "MgChecks" -context "DemoDBContext"

而後能夠建立數據庫。

 update-database

 

接下來用 Web API 來測一下。先在 Startup.ConfigureServices 方法中註冊一下相關服務。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddDbContext<DemoDBContext>(opt =>
            {
                opt.UseSqlServer("server=(localdb)\\mssqllocaldb;database=test_db");
            });
        }

實現 IDesignTimeDbContextFactory 接口只用於設計階段,不用於應用程序運行階段,因此,相關的配置仍是要作的。

定義控制器。

    [Route("charger/[action]")]
    public class ChargerController : Controller
    {
        private readonly DemoDBContext _dbcontext;
        public ChargerController(DemoDBContext cxt)
        {
            _dbcontext = cxt;
            // 初始化一些數據
            if (!_dbcontext.Charges.Any())
            {
                Charge c1 = new Charge
                {
                    PhoneNo = "13325236411",
                    Money = 50.00M,
                    Time = new DateTime(2018, 10, 9, 20, 16, 0)
                };
                Charge c2 = new Charge
                {
                    PhoneNo = "15900254200",
                    Money = 100.00M,
                    Time = new DateTime(2018, 6, 22, 19, 0, 0)
                };
                Charge c3 = new Charge
                {
                    PhoneNo = "13500001122",
                    Money = 30.1M,
                    Time = new DateTime(2018, 10, 13, 15, 20, 10)
                };
                _dbcontext.Charges.AddRange(c1, c2, c3);
                _dbcontext.SaveChanges();
            }
        }

        public ActionResult Index()
        {
            return Json(_dbcontext.Charges);
        }
    }

運行結果以下。

 

好了,今天的內容就到這裏了,文中示例的源代碼能夠拼命點 這裏 下載。

相關文章
相關標籤/搜索