首頁 > 軟體

Entity Framework使用Code First模式管理資料庫

2022-03-05 13:00:36

一、管理資料庫連線

1、使用組態檔管理連線之約定

在資料庫上下文類中,如果我們只繼承了無引數的DbContext,並且在組態檔中建立了和資料庫上下文類同名的連線字串,那麼EF會使用該連線字串自動計算出資料庫的位置和資料庫名。比如,我們的資料庫上下文定義如下:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConventionConfigure.EF
{
    /// <summary>
    /// 繼承無引數的DbContext
    /// </summary>
    public class SampleDbEntities :DbContext
    {
        public SampleDbEntities()
        {
            // 資料庫不存在時建立資料庫
            Database.CreateIfNotExists();
        }
    }
}

在組態檔中定義的連線字串如下:

<connectionStrings>
    <add name="SampleDbEntities" connectionString="Data Source=.;Initial Catalog=TestDb;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
</connectionStrings>

定義的連線字串中name的value值和建立的資料庫上下文類的類名相同,這樣EF會使用該連線字串執行資料庫操作,究竟會發生什麼呢?

執行程式,Program類定義如下:

using ConventionConfigure.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConventionConfigure
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new SampleDbEntities())
            { }

            Console.WriteLine("建立成功");
            Console.ReadKey();
        }
    }
}

當執行應用程式時,EF會尋找我們的資料庫上下文類,即“SampleDbEntities”,並在組態檔中尋找和它同名的連線字串,然後它會使用該連線字串計算出應該使用哪個資料庫provider,之後檢查資料庫位置,之後會在指定的位置建立一個名為TestDb.mdf的資料庫檔案,同時根據連線字串的Initial Catalog屬性建立了一個名為TestDb的資料庫。建立的資料庫結構如下:

檢視建立後的資料庫,會發現只有一張遷移記錄表。

2、使用已經存在的ConnectionString

如果我們已經有了一個定義資料庫位置和名稱的ConnectionString,並且我們想在資料庫上下文類中使用這個連線字串,連線字串如下:

<connectionStrings>
    <add name="AppConnection" connectionString="Data Source=.;Initial Catalog=TestDb;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
</connectionStrings>

以上面建立的資料庫TestDb作為已經存在的資料庫,新新增實體類Student,使用已經存在的ConnectionString查詢資料庫的Student表,Student實體類定義如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExistsConnectionString.Model
{
    [Table("Student")]
    public class Student
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public string Sex { get; set; }

        public int Age { get; set; }
    }
}

我們將該連線字串的名字傳入資料庫上下文DbContext的有參建構函式中,資料庫上下文類定義如下:

using ExistsConnectionString.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExistsConnectionString.EF
{
    public class SampleDbEntities : DbContext
    {
        public SampleDbEntities()
            : base("name=AppConnection")
        {

        }

        // 新增到資料上下文中
        public virtual DbSet<Student> Students { get; set; }
    }
}

上面的程式碼將連線字串的名字傳給了DbContext類的有參建構函式,這樣一來,我們的資料庫上下文就會開始使用該連線字串了,在Program類中輸出Name和Age欄位的值:

using ExistsConnectionString.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExistsConnectionString
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new SampleDbEntities())
            {
                foreach (var item in context.Students)
                {
                    Console.WriteLine("姓名:"+item.Name+" "+"年齡:"+item.Age);
                }
            }
        }
    }
}

執行程式,發現會報下面的錯誤:

出現上面報錯的原因是因為資料庫上下文發生了改變,與現有資料庫不匹配。解決方案:

把資料庫裡面的遷移記錄表刪掉或者重新命名即可。

重新執行程式,結果如下:

注意:如果在組態檔中還有一個和資料庫上下文類名同名的ConnectionString,那麼就會使用這個同名的連線字串。無論我們對傳入的連線字串名稱如何改變,都是無濟於事的,也就是說和資料庫上下文類名同名的連線字串優先權更大。(即約定大於設定)

3、使用已經存在的連線

通常在一些老專案中,我們只會在專案中的某個部分使用EF Code First,同時,我們想對資料上下文類使用已經存在的資料庫連線,如果要實現這個,可將連線物件傳給DbContext類別建構函式,資料上下文定義如下:

using ExistsDbConnection.Model;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExistsDbConnection.EF
{
    public class SampleDbEntities :DbContext
    {
        public SampleDbEntities(DbConnection con)
            : base(con, contextOwnsConnection: false)
        {

        }

        public virtual DbSet<Student> Students { get; set; }
    }
}

這裡要注意一下contextOwnsConnection引數,之所以將它作為false傳入到上下文,是因為它是從外部傳入的,當上下文超出了範圍時,可能會有人想要使用該連線。如果傳入true的話,那麼一旦上下文出了範圍,資料庫連線就會立即關閉。

Program類定義如下:

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using ExistsDbConnection.EF;

namespace ExistsDbConnection
{
    class Program
    {
        static void Main(string[] args)
        {
            // 讀取連線字串
            string conn = ConfigurationManager.ConnectionStrings["AppConnection"].ConnectionString;
            // DbConnection是抽象類,不能直接範例化,宣告子類指向父類別物件
            DbConnection con = new SqlConnection(conn);
            using (var context = new SampleDbEntities(con))
            {
                foreach (var item in context.Students)
                {
                    Console.WriteLine("姓名:" + item.Name + " " + "年齡:" + item.Age);
                }
            }

            Console.WriteLine("讀取完成");
            Console.ReadKey();
        }
    }
}

執行程式,結果如下:

二、管理資料庫建立

首次執行EF Code First應用時,EF會做下面的這些事情:
1、檢查正在使用的DbContext類。
2、找到該上下文類使用的connectionString。
3、找到領域實體並提取模式相關的資訊。
4、建立資料庫。
5、將資料插入系統。

一旦模式資訊提取出來,EF會使用資料庫初始化器將該模式資訊推播給資料庫。資料庫初始化器有很多可能的策略,EF預設的策略是如果資料庫不存在,那麼就重新建立;如果存在的話就使用當前存在的資料庫。當然,我們有時也可能需要覆蓋預設的策略,可能用到的資料庫初始化策略如下:

CreateDatabaseIfNotExists:CreateDatabaseIfNotExists:顧名思義,如果資料庫不存在,那麼就重新建立,否則就使用現有的資料庫。如果從領域模型中提取到的模式資訊和實際的資料庫模式不匹配,那麼就會丟擲異常。

DropCreateDatabaseAlways:如果使用了該策略,那麼每次執行程式時,資料庫都會被銷燬。這在開發週期的早期階段通常很有用(比如設計領域實體時),從單元測試的角度也很有用。

DropCreateDatabaseIfModelChanges:這個策略的意思就是說,如果領域模型發生了變化(具體而言,從領域實體提取出來的模式資訊和實際的資料庫模式資訊失配時),就會銷燬以前的資料庫(如果存在的話),並建立新的資料庫。

MigrateDatabaseToLatestVersion:如果使用了該初始化器,那麼無論什麼時候更新實體模型,EF都會自動地更新資料庫模式。這裡很重要的一點是:這種策略更新資料庫模式不會丟失資料,或者是在已有的資料庫中更新已存在的資料庫物件。MigrateDatabaseToLatestVersion初始化器只有從EF4.3才可用。

1、設定初始化策略

EF預設使用CreateDatabaseIfNotExists作為預設初始化器,如果要覆蓋這個策略,那麼需要在DbContext類中的建構函式中使用Database.SetInitializer方法,下面的例子使用DropCreateDatabaseIfModelChanges策略覆蓋預設的策略。資料庫上下文類定義如下:

using InitializationStrategy.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationStrategy.EF
{
    public class SampleDbEntities : DbContext
    {
        public SampleDbEntities()
            : base("name=AppConnection")
        {
            // 使用DropCreateDatabaseIfModelChanges策略覆蓋預設的策略
            Database.SetInitializer<SampleDbEntities>(new DropCreateDatabaseIfModelChanges<SampleDbEntities>());
        }

        // 新增到資料上下文中
        public virtual DbSet<Student> Students { get; set; }
    }
}

這樣一來,無論什麼時候建立上下文類,Database.SetInitializer()方法都會被呼叫,並且將資料庫初始化策略設定為DropCreateDatabaseIfModelChanges。

Student領域實體類新增加Email和Address兩個屬性:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationStrategy.Model
{
    [Table("Student")]
    public class Student
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public string Sex { get; set; }

        public int Age { get; set; }

        public string Email { get; set; }

        public string Address { get; set; }
    }
}

Program類定義如下:

using InitializationStrategy.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationStrategy
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new SampleDbEntities())
            {
                foreach (var item in context.Students)
                {

                }
            }

            Console.WriteLine("建立成功");
            Console.ReadKey();
        }
    }
}

執行程式後,資料庫表結構如下:

注意:如果處於生產環境,那麼我們肯定不想丟失已經存在的資料。這時我們就需要關閉該初始化器,只需要將null傳給Database.SetInitlalizer()方法,如下所示:

public SampleDbEntities(): base("name=AppConnection")
{
Database.SetInitializer<SampleDbEntities>(null);
}

2、填充種子資料

到目前為止,無論我們選擇哪種策略初始化資料庫,生成的資料庫都是一個空的資料庫。但是許多情況下我們總想在資料庫建立之後、首次使用之前就插入一些資料。此外,開發階段可能想以admin的資格為其填充一些資料,或者為了測試應用在特定的場景中表現如何,想要偽造一些資料。

當我們使用DropCreateDatabaseAlways和DropCreateDatabaseIfModelChanges初始化策略時,插入種子資料非常重要,因為每次執行應用時,資料庫都要重新建立,每次資料庫建立之後在手動插入資料非常乏味。接下來我們看一下當資料庫建立之後如何使用EF來插入種子資料。

為了向資料庫插入一些初始化資料,我們需要建立滿足下列條件的資料庫初始化器類:

1、從已存在的資料庫初始化器類中派生資料。
2、在資料庫建立期間種子化。

下面演示如何初始化種子資料

1、定義領域實體類

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationSeed.Model
{
    [Table("Employee")]
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

    }
}

2、建立資料庫上下文

使用EF的Code First方式對上面的模型建立資料庫上下文:

public class SampleDbEntities : DbContext
{
    public virtual DbSet<Employee> Employees { get; set; }
}

3、建立資料庫初始化器類

假設我們使用的是DropCreateDatabaseAlways資料庫初始化策略,那麼初始化器類就要從該泛型類繼承,並傳入資料庫上下文作為型別引數。接下來,要種子化資料庫就要重寫DropCreateDatabaseAlways類的Seed()方法,而Seed()方法拿到了資料庫上下文,因此我們可以使用它來將資料插入資料庫:

using InitializationSeed.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationSeed.EF
{

    /// <summary>
    /// 資料庫初始化器類
    /// </summary>
    public class SeedingDataInitializer : DropCreateDatabaseAlways<SampleDbEntities>
    {
        /// <summary>
        /// 重寫DropCreateDatabaseAlways的Seed方法
        /// </summary>
        /// <param name="context"></param>
        protected override void Seed(SampleDbEntities context)
        {
            for (int i = 0; i < 6; i++)
            {
                var employee = new Employee
                {
                  FirstName="測試"+(i+1),
                  LastName="工程師"
                };

                context.Employees.Add(employee);

            }
                base.Seed(context);
        }
    }
}

上面的程式碼通過for迴圈建立了6個Employee物件,並將它們新增給資料庫上下文類的Employees集合屬性。這裡值得注意的是我們並沒有呼叫DbContext.SaveChanges()方法,因為它會在基礎類別中自動呼叫。

4、將資料庫初始化器類用於資料庫上下問類

using InitializationSeed.Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationSeed.EF
{
    public class SampleDbEntities :DbContext
    {
        public SampleDbEntities()
            : base("name=AppConnection")
        {
            // 型別傳SeedingDataInitializer
            Database.SetInitializer<SampleDbEntities>(new SeedingDataInitializer());
        }

        // 領域實體新增到資料上下文中
        public virtual DbSet<Employee> Employees { get; set; }
    }
}

5、Main方法中存取資料庫

using InitializationSeed.EF;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InitializationSeed
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new SampleDbEntities())
            {
                foreach (var item in context.Employees)
                {
                    Console.WriteLine("FirstName:"+item.FirstName+" "+"LastName:"+item.LastName);
                }
            }

            Console.WriteLine("讀取完成");
            Console.ReadKey();
        }
    }
}

6、執行程式,檢視結果

檢視資料庫

種子資料填充完成。

7、使用資料遷移的方式填充種子資料

使用資料遷移的方式會生成Configuration類,Configuration類定義如下:

namespace DataMigration.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<DataMigration.SampleDbEntities>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(DataMigration.SampleDbEntities context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.
        }
    }
}

重寫Configuration類的Seed()方法也可以實現插入種子資料,重寫Seed()方法:

namespace DataMigration.Migrations
{
    using DataMigration.Model;
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<DataMigration.SampleDbEntities>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(DataMigration.SampleDbEntities context)
        {
            //  This method will be called after migrating to the latest version.

            //  You can use the DbSet<T>.AddOrUpdate() helper extension method
            //  to avoid creating duplicate seed data.

            context.Employees.AddOrUpdate(
                 new Employee { FirstName = "測試1", LastName = "工程師" },
                 new Employee { FirstName = "測試2", LastName = "工程師" }

                );
        }
    }
}

使用資料遷移,然後檢視資料庫結果:

發現使用資料遷移的方式也將種子資料插入到了資料庫中。

程式碼下載地址:點此下載

到此這篇關於Entity Framework使用Code First模式管理資料庫的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com