8天掌握EF的Code First開發系列之2 簡單的CRUD操作


本文出自8天掌握EF的Code First開發系列,經過自己的實踐整理出來。

本篇目錄

  • 創建控制台項目
  • 根據.Net中的類來創建數據庫
  • 簡單的CRUD操作
  • 數據庫模式更改介紹
  • 本章小結

本人的實驗環境是VS 2012,windows 7,MSSQL Server 2008 R2。

創建控制台項目

1. 新建控制台應用項目

 

 

2. 通過NuGet安裝Entity Framework 6

 

 

根據.Net中的類來創建數據庫

上面的步驟之后,我們就可以開始寫代碼了。在寫代碼之前,你要始終記得,每個類就是相應的數據表中的一行數據,該類的屬性對應的是這行數據的列。為了表示贊助者們對我的支持,我決定用他們的數據進行舉例。

 

1. 創建實體數據模型

我們現在創建一個捐贈者類Donator:

namespace EFCodeFirst
{
    public class Donator
    {
        public int DonatorId { get; set; }

        public string Name { get; set; }

        public decimal Amount { get; set; }

        public DateTime DonatorDate { get; set; }
    }
}

我們需要定義和期望的數據庫類型相匹配的屬性。上面的例子中,.Net中的int類型會映射到SQL Server中的int類型,string類型會映射到Nvarchar(max)類型,decimal和Datetime也和SQL Server中的一樣。大多數時候,我們不需要關心這些細節,我們只需要編寫能夠表示數據的模型類就行了,然后使用標准的.Net類型定義屬性,其他的就讓EF自己計算出保存數據所需要的RDBMS類型。

 

2. 創建數據庫上下文

接下來我們創建數據庫上下文,它是數據庫的抽象。目前,我們只有一張表Donator,因而要給該數據庫上下文定義一個屬性來代表這張表。再者,一張表中一般肯定不止一條數據行,所以我們必須定義一個集合屬性,EF使用DbSet來實現這個目的。

namespace EFCodeFirst
{
    public class Context : DbContext
    {
        public Context()
            :base("name = EFCodeFirst")
        {

        }

        public DbSet<Donator> Donators { get; set; }
    }
}

在這里,DbContext 是所有基於 EF 的上下文基類,通過它可以訪問到數據庫中的所有表。上面的代碼中調用了父類的構造函數,並且傳入了一個鍵值對,鍵是 name ,值是 EFCodeFrist ,這個鍵值對是定義在應用程序的配置文件中的,取決於你的應用程序類型,可能是 app.config 或者 web.config 。在我們的控制台應用程序中就是 app.config。

在 app.config 文件的 configuration 的節點下(不要在第一個節點下,否則報錯)添加:

 <connectionStrings>
    <add name="EFCodeFirst" 
         connectionString="server=DTSZOPAULHUANG\SQLEXPRESS;Database=EFCodeFirst;Integrated Security=SSPI" 
         providerName="System.Data.SqlClient"/>
  </connectionStrings>

接下來就應該是創建數據庫了,創建數據庫的方式有兩種:

  1. 在后面的博客中我們會通過數據庫遷移來實現數據庫的創建,原理就是數據庫會在第一次查詢,更新或插入操作時創建。
  2. 通過EF數據庫的API來創建。

這里我們先通過第二種方法來創建數據庫。首先我們必須確保數據庫中沒有和我們現在要創建的數據庫同名的數據庫存在,否則會提示錯誤。當然,我們也可以通過EF的API訪問數據庫來創建數據庫。

namespace EFCodeFirst
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new Context())
            {
                context.Database.CreateIfNotExists();
            }

            Console.WriteLine("Database 創建成功");

            Console.ReadKey();
        }
    }
}

最后,我們只需要確保我們的連接字符串沒有問題。現在,運行程序,打開SSMS(當然也可以在VS的服務器資源管理器查看)進行確認即可。

可以很清楚地看到,數據表名是自定義數據庫上下文的屬性,而表中的列是數據模型的屬性。此外,注意一下列的類型。EF默認將DonatorId作為了主鍵,string類型的Name在數據庫中的類型是nvarchar(max),這些都是在使用EF時必須注意的命名規范(或者約定)。

簡單的CRUD操作

首先,大腦中時刻要有這張圖,這張圖也是很重要的概念:

 

 

1. 創建記錄——Create

你可以這樣認為,將對象添加到集合中就相當於將數據行插入到數據庫的相應的表中。我們使用 DbSet 的 Add 方法來實現新數據的添加,而 DbContext 類的 SaveChanges 方法會將未處理的更改提交到數據庫,這是通過檢測上下文中所有的對象的狀態來完成的。所有的對象都駐留在上下文類的 DbSet 屬性中,比如,我們的例子只有一個 Donators 屬性,那么所有的捐贈人的數據都會存儲到這個泛型集合屬性中。數據庫上下文會跟蹤 DbSet 屬性中的所有對象的狀態,這些狀態有這么幾種:DeletedAddedModified 和 Unchanged 。如果你想在一個表中插入多行數據,那么只需要添加該表對應的類的多個對象的實例即可,然后就使用 SaveChanges 方法將更改提交到數據庫,該方法是以單事務執行的。最終,所有的數據庫更改都會以單個工作單元持久化。既然是事務的,那么這就允許將批量相關的更改作為單個操作提交,這樣就保證了事務一致性和數據完整性。

修改Main方法如下:

    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new Context())
            {
                context.Database.CreateIfNotExists();

                var donators = new List<Donator>
                {
                    new Donator { Name   = "陳志康", Amount = 50, DonatorDate = new DateTime(2016, 4, 7) },
                    new Donator { Name = "海風",  Amount = 5, DonatorDate = new DateTime(2016, 4, 8) },
                    new Donator { Name = "醉千秋", Amount = 18.8m, DonatorDate = new DateTime(2016, 4, 15) }
                };

                context.Donators.AddRange(donators);
                context.SaveChanges();

            }

            Console.ReadKey();
        }
    }

這里需要注意兩點:

  1. 不需要給 DonatorId 屬性賦值,因為它對應到 SQL Server 表中的主鍵列,它的值是自動生成的,當 SaveChanges 執行之后,打個斷點就能看到返回的 DonatorId 已經有值了。
  2. Context 的實例用了 using 語句包裝起來,這是因為 DbContext 實現了 IDisposabl e接口。Dbcontext 還包含了 DbConnection 的實例,該實例指向了具有特定連接字符串的數據庫。在EF中合適地釋放數據庫連接和 ADO.NET 中同等重要。

執行結果與在VS中查看數據是否生成:

 

 

2. 查詢記錄——Retrieve

查詢時也是直接通過DbSet進行查詢的。

        private static void Search()
        {
            using (var context = new Context())
            {
                var donators = context.Donators;

                Console.WriteLine("Id\t\t姓名\t\t金額\t\t贊助日期");

                foreach (var donator in donators)
                {
                    Console.WriteLine("{0}\t\t{1}\t\t{2}\t\t{3}",
                        donator.DonatorId, donator.Name, donator.Amount, donator.DonatorDate.ToShortDateString());
                }
            }
        }

如果像下面那樣打一個斷點,你會看到一個結果視圖,點擊那個類似刷新的圖標會看到查詢的結果,這個東西道出了 EF 中很重要的一個概念: 延遲查詢。但是此時還沒有真正查詢數據庫,只有當LINQ的查詢結果被訪問或者枚舉時才會將查詢命令發送到數據庫。EF 是基於 Dbset 實現的 IQueryable 接口來處理延遲查詢的。

 

最后,我使用了一個foreach循環將結果枚舉出來,這樣就執行了SQL,結果如下:

 

 

3. 更新記錄——Update

在 SQL 中,更新需要使用 Update 命令。而在 EF 中,我們要找到 DbSet 集合中要更新的對象,然后更改其屬性,最后調用 SaveChanges 方法即可。下面將贊助者的名稱進行修改。

        private static void Update()
        {
            using (var context = new Context())
            {
                var donator = context.Donators.FirstOrDefault(item => item.Name.Equals("醉千秋"));

                if (donator != null)
                {
                    donator.Name = "醉、千秋";

                    context.SaveChanges();
                }
            }
        }

這里我們使用了 FirstOrDefault() 擴展方法來判斷序列中是否存在特定的元素,如果存在則修改目標對象的 Name 屬性,之后調用 SaveChanges 方法。當然這里也可以使用 First、Single 或者 SingleOrDefault 方法都可以。這四個方法之間的區別請參考這里

最后檢測數據庫操作成功的結果:

 

 

4. 刪除記錄——Delete

接下來要刪除記錄,先把剩下的打賞者數據全部放到數據庫,然后再在最后加入一條測試數據,如下:

INSERT dbo.Donators VALUES  ( N'雪茄', 10, '2016-04-08')
INSERT dbo.Donators VALUES  ( N'王小乙', 10, '2016-04-09')
INSERT dbo.Donators VALUES  ( N'鍵盤里的鼠標', 12, '2016-04-13')
INSERT dbo.Donators VALUES  ( N'smallpig', 10, '2016-04-13')
INSERT dbo.Donators VALUES  ( N'Darren', 5, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'jeffrey', 10, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'危楊益', 6.66, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'Mr.Lan', 10, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'周旭龍', 5, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'403', 10.24, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'cuibty', 8.88, '2016-04-15')
INSERT dbo.Donators VALUES  ( N'dennylo', 10.24, '2016-04-17')
INSERT dbo.Donators VALUES  ( N'lee', 5, '2016-04-18')
INSERT dbo.Donators VALUES  ( N'利平', 18.8, '2016-04-18')
INSERT dbo.Donators VALUES  ( N'聽海船說', 20, '2016-04-19')
INSERT dbo.Donators VALUES  ( N'喝前搖一搖', 5, '2016-04-19')
INSERT dbo.Donators VALUES  ( N'黃大仙', 50, '2016-04-19')
INSERT dbo.Donators VALUES  ( N'夜未眠', 10, '2016-04-19')
INSERT dbo.Donators VALUES  ( N'A.L', 8.88, '2016-04-19')
INSERT dbo.Donators VALUES  ( N'transient', 5, '2016-04-19')
INSERT dbo.Donators VALUES  ( N'曉東', 6.66, '2016-04-20')
INSERT dbo.Donators VALUES  ( N'待打賞', 10, '2016-04-20')

要刪除一條數據,就先要找到這條數據,刪除代碼如下:

        private static void Delete()
        {
            using (var context = new Context())
            {
                //根據Name找到要刪除的測試數據
                var toBeDeletedDonator = context.Donators.Single(d => d.Name == "待打賞");

                if (toBeDeletedDonator != null)
                {
                    //如果滿足條件,就將該對象使用Remove方法標記為Deleted
                    context.Donators.Remove(toBeDeletedDonator);
                    //最后持久化到數據庫
                    context.SaveChanges();

                }
            }
        }

注意:上面使用了 First 方法來查找待刪除的數據,但是如果在數據庫中沒有滿足條件的記錄存在,則會拋出異常,所以使用 First 方法時要注意。

 

數據庫模式更改介紹

如果你修改了Donator類或者又添加了新的DbSet屬性(即添加了新表),在操作的過程中你可能會遇到一些異常。現在,我想再添加一張表 PayWays ,用來存儲打賞者的打賞方式,比如微信,支付寶,QQ紅包等。

定義 PayWay 類, 包含兩個屬性,以后可能會增加屬性和 Donator 表關聯:

    public class PayWay
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }

修改 Context 類,使之包含 PayWays 數據集合。

 public class Context : DbContext
    {
        public Context()
            :base("name = EFCodeFirst")
        {

        }

        public DbSet<Donator> Donators { get; set; }

        public DbSet<PayWay> PayWays { get; set; }
    }

我們這里更加明顯地可以看到,數據庫上下文代表了整個數據庫,它包含多個表,每張表都成為了Context類的一個屬性。

現在,如果我們運行程序,並循環枚舉支付方式會出現什么情況呢?

意思是:自從數據庫創建以來,模型背后的數據庫上下文‘Context’已經發生了變化,也就是當初的數據庫和現在的上下文對不上了。呵呵!當然對不上了,數據庫上下文被我更改了,而數據庫沒改啊!

在后面我會介紹數據庫遷移和對已存在的數據庫進行遷移,但是我們現在也要解決這個當前問題。這就引入了初始化器(Initializer的概念。初始化器會在首次實例化過程期間或者EF首次訪問數據庫時運行。EF中需要關心的初始化器有三種:

  • CreateDatabaseIfNotExists<TContext>
  • DropCreateDatabaseIfModelChanges<TContext>
  • DropCreateDatabaseAlways<TContext>

這三種初始化器看其名字都很好理解,CreateDatabaseIfNotExists<TContext> 指如果數據庫不存在則創建,DropCreateDatabaseIfModelChanges<TContext> 指如果模型改變了(包括模型類的更改以及上下文中集合屬性的添加和移除)就銷毀之前的數據庫再創建數據庫,DropCreateDatabaseAlways<TContext> 總是銷毀再重新創建數據庫,如果沒有指定的話默認使用第一個初始化器。
現在我們使用第二個初始化器,要使用該初始化器,我們應該在實例化數據庫上下文之前就要讓 EF 知道。我們可以使用 EF 的 API 中 Database 類的 SetInitialize r靜態方法。在我們的控制台應用中,我們應該將它放在Main方法的第一行:

        static void Main(string[] args)
        {
            using (var context = new Context())
            {
                Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());

                var payWays = context.PayWays;

                Console.WriteLine("姓名\t\t");

                foreach (var payWay in payWays)
                {
                    Console.WriteLine("{0}\t\t", payWay.Name);
                }
            }

            Console.ReadKey();
        }

運行程序,但是很不幸,還是有異常拋出。

這是因為數據庫在其他的應用程序中是打開的,如SQL Server Management Studio。此時,只要關閉其他的應用程序就可以了。關閉 SQL Server Management Studio 再運行程序。

程序運行正確且數據庫結構發生了變化

需要注意的是,因為上面的初始化器會銷毀之前的數據庫,因此之前累積的所有數據也都丟失了。很顯然,這種用法不適合生產環境,但是我們學習EF或者項目早期是很方便的。另外一個有趣的功能是,初始化器允許我們在目標數據庫創建之后運行其他代碼,可以通過重寫Seed方法即可。該方法需要一個數據庫上下文的實例參數:

Database.SetInitializer(new DatabaseInitializer());
public class DatabaseInitializer : DropCreateDatabaseIfModelChanges<Context>
    {
        protected override void Seed(Context context)
        {
            context.PayWays.AddRange(new List<PayWay>
            {
                new PayWay{Name = "支付寶"},
                new PayWay{Name = "微信"},
                new PayWay{Name = "QQ紅包"}
            });

        }
    }

可以看到,在Seed方法中,我們不需要調用SaveChanges方法。此外,要更新生產數據庫,我們可以使用EF Migrations。運行后,Donators表中的數據都銷毀了,而Seed方法給PayWays表中添加了數據。

本章小結

這篇博客中,我們創建了第一個基於 EF Code First 的控制台應用程序。我們使用 Nuget 添加了 EF 的引用。然后我們確定要將贊助樓主的數據存儲在數據庫中,然后創建了一個 Donator 類映射到數據庫中的 Donators 表。然后創建了數據庫抽象 Context 類,它繼承自 DbContext ,並在它的構造函數中指定了想要的連接字符串,同時把該連接字符串添加到應用的配置文件中。然后,我們給數據庫上下文添加了一個屬性 Donators ,它是 Donator 的集合,即 DbSet 。之后,讓程序跑了起來。然后我們發現數據庫中產生了和該屬性同名的數據表。數據庫的創建過程使用了很多約定,包括表名和主鍵的確定。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM