Java進擊C#——應用開發之Linq和EF

來源:互聯網
上載者:User

標籤:ransient   開發環境   更新   query   const   自己   參數   package   http   

本章簡言

上一章筆者對於WinForm開發過程用到的幾個知識點做了講解。筆者們可以以此為開端進行學習。而本章我們來講一個跟ORM思想有關的知識點。在講之前讓我們想一下關於JAVA的hibernate知識點。hibernate也是ORM架構。記得hibernate裡面有一個叫HQL。先不管HQL的好與壞。主要是明白HQL的目地是什麼。ORM的思想就是為了讓使用者在操作資料的時候用上物件導向的思想來看,而不是二維資料了。所以HQL筆者認為就是一個物件導向思想的SQL語句。那麼為什麼筆者要講到HQL呢?事實上筆者認為Linq有一點跟他類似。如果項目架構是三層的話,就是讓業務層的開發人員不用在看二維資料了。就連SQL語句都是物件導向思想形式來操作了。而EF(Entity Framework)可以說就是hibernate。即是可以理解為Linq的資料來源。但是HQL要在hibernate上面才能有效果。Linq卻可以不用EF。

Linq文法

.NET對於Linq知識的分類讓筆者有時候覺得很無力。為什麼呢?最早的時候筆者認為Linq知識點分三大塊。分別為Linq to SQL、Linq to Entity、Linq to Database。隨著對Linq使用的增加卻發現還有Linq to Xml 、Linq to Excel等。筆者想讀者們是不是看出門道來了。可以說.NET在設計Linq的時候,應該是有充分的想過將來擴充的問題。當然這不是本章的目標。筆者在開發過程中最常用的就是Linq to SQL和 Linq to Entity。另外還有一個叫Linq to Object.對於Linq to Object筆者一直認為就是Linq to Entity。筆者的意思是指他們的知識該應放在一塊。好了。先筆者講一下關於Linq to SQL。

對於Linq to SQL來講,只要學習SQL文法的人都不用擔心很容易就上手。記得筆者學習的時候,一看我去不就HQL的另一種形態嗎?當然 HQL可不是Linq還是要學習一下的。講那麼多沒有用。用列子才是最好的。

一、建立EF環境。先建一個項目,然後通過NUGET來獲得對應的EF的DLL。對於NUGET是什麼。相信看過《Java進擊C#——項目開發環境》的人應該可以瞭解到。選擇“引用”右擊》管理Nuget程式包。

相信看了上面的圖片的時候,我們已經發現了EntityFramework了吧。點擊“安裝”就可以了。這個時候項目就會多出一個叫packages.config檔案。這裡面記錄著當前安裝的dll資訊。同時物理目錄裡面會多出一個檔案夾packages來存在這些dll。

我們看到引用裡面多出了關於EF的引用dll。這個時候我們就可以做EF的事情了。

二、建立EF上下文。EF有一個很重要的類。可以說是學習EF的核心點。這個類就是DbContext。筆者建立一類叫AomiContext繼承他。如下

public class AomiContext : DbContext{}

DbContext類有幾個建構函式。筆者這裡講一個常用的吧。如下

 public DbContext(string nameOrConnectionString);

就是個建構函式意思就是傳一個連接字串或是設定檔的串連字元的配置名。記得上一節中講的App.config了吧。沒有錯就是要用到他。看一下筆者寫的內容吧。

<?xml version="1.0" encoding="utf-8"?><configuration>  <configSections>    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />  </configSections>  <connectionStrings>    <add name="aomi" connectionString="Data Source=.;Initial Catalog=Ado;Persist Security Info=True;User ID=sa;Password=123" providerName="System.Data.SqlClient"/>  </connectionStrings>  <entityFramework>    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">      <parameters>        <parameter value="v11.0" />      </parameters>    </defaultConnectionFactory>    <providers>      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />    </providers>  </entityFramework></configuration>

上面的connectionStrings部分是筆者自己寫的。其他是自動產生的。.NET自己有一個配置連接字串的節點。我們就是在這個節點上寫入自己的串連就可以了。

<add name="aomi" connectionString="Data Source=.;Initial Catalog=Ado;Persist Security Info=True;User ID=sa;Password=123" providerName="System.Data.SqlClient"/>

好了。接下來就是把AomiContext類修改一下。讓他跟對應的連接字串的配置發生關係。如下

 public class AomiContext : DbContext    {        public AomiContext()            : base("Aomi")        { }    }

看到紅色部分的代碼了吧。把Aomi就是對應上面配置add節點的name的值。這個時候EF會自己去設定檔裡面去找。

三、建立表和類的映射。

對應資料庫的表:

CREATE TABLE [dbo].[Catalogs](    [ID] [int] NOT NULL,    [CatalogName] [nvarchar](50) NULL,    [CatalogCode] [nvarchar](50) NULL, CONSTRAINT [PK_Catalogs] PRIMARY KEY CLUSTERED (    [ID] ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]) ON [PRIMARY]

對應資料庫的資料

INSERT [dbo].[Catalogs] ([ID], [CatalogName], [CatalogCode]) VALUES (1, N‘小吃‘, N‘c0001‘)INSERT [dbo].[Catalogs] ([ID], [CatalogName], [CatalogCode]) VALUES (2, N‘電腦‘, N‘c0002‘)

筆者建一個類用於跟資料庫裡面的表相對應。這個時候要記得屬性要跟表裡裡面的列名一樣子才行。代碼如下

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace LinqExample{    public class Catalogs    {        public int ID { set; get; }        public string CatalogName { set; get; }          public string CatalogCode { set; get; }    }}

有了對應的類之後,還不行。我們還要修改一下AomiContext類。這樣子就可以通過AomiContext類來訪問對應的類對象了。即是資料了。如下

    public class AomiContext : DbContext    {        public AomiContext()            : base("Aomi")        { }        public IDbSet<Catalogs> Catalogs { set; get; }    }

四、執行EF。

 class Program    {        static void Main(string[] args)        {            using (AomiContext ac = new AomiContext())            {                IQueryable<Catalogs> queryable = from c in ac.Catalogs select c;                List<Catalogs> catalogList = queryable.ToList();                foreach (Catalogs catalog in catalogList)                {                    Console.WriteLine(string.Format("ID:{0} CatalogName:{1}", catalog.ID, catalog.CatalogName));                }            }            Console.ReadKey();        }    }

執行結果:

從上面的例子我們可以看到用了from c in ac.Catalogs select c;來獲得對應的資料。這便是linq to sql。簡章講他是一個物件導向的SQL語句。SQL語句是以select開頭,結尾不確定。而linq to sql一般是以from開頭,以select結尾。表示從哪一個資料來源開始,最後要以什麼樣子返回。可是我們可以看到他返回是一個IQueryable<T>類型。事實這個時候他並沒有去執行獲得資料。可以解理為他現在只是去組裝SQL語句。只到queryable.ToList();才是去執行獲得資料。為了學習上的方便筆者又加幾個關鍵字讓大家看一下。

using (AomiContext ac = new AomiContext())            {                IQueryable<Catalogs> queryable = from c in ac.Catalogs where c.CatalogName.Contains("吃") orderby c.CatalogCode ascending select c;                List<Catalogs> catalogList = queryable.ToList();                foreach (Catalogs catalog in catalogList)                {                    Console.WriteLine(string.Format("ID:{0} CatalogName:{1}", catalog.ID, catalog.CatalogName));                }            }

看完了linq to sql之後,讓我看一下關於linq to entity又是什麼東東呢?可是這樣子講linq的文法都相像。只是用法和寫法不一樣子而以。把上面的例子變一變吧。

 using (AomiContext ac = new AomiContext())            {                //IQueryable<Catalogs> queryable = from c in ac.Catalogs where c.CatalogName.Contains("吃") orderby c.CatalogCode ascending select c;                IQueryable<Catalogs> queryable = ac.Catalogs.Where(t => t.CatalogName.Contains("吃")).OrderBy(t => t.CatalogCode).Select(c=> c);                List<Catalogs> catalogList = queryable.ToList();                foreach (Catalogs catalog in catalogList)                {                    Console.WriteLine(string.Format("ID:{0} CatalogName:{1}", catalog.ID, catalog.CatalogName));                }            }

看樣子筆者不用多說也明白。就是變成了對應的關鍵字方法而以。沒有錯。就是這樣子。這個時候筆者就可以這樣子認為不管是Linq to entity還是Linq to sql都必須要有對應的資料來源。這裡EF就是為他們提供資料來源的。他們倆個對應都是返回IQueryable<T>類型。只是Linq to entity是用方法。而Linq to sql更多像SQL語句。

好了。讓我們看一下關於linq to object吧。可以這樣了講吧——不管是Linq to sql還是linq to entity他們倆個都離不開linq to object。linq to object是專對記憶體中的資料進行處理。我們可以看到上面例子中有出現一段queryable.ToList()。如果筆者說ToList()是linq to object會不會有人噴我。為什麼筆者說他是linq to object呢?主要是ToList()是對於IEnumerable<T>進行靜態擴充的。IEnumerable<T>一般都是用於數組和集合。位於記憶體中的。而上面都是專對於IQueryable<T>類型的。好了。如果你實在覺得筆者分的不對的話,那就是不要分了。都為Linq文法就行了。linq還提供了一些比較常用的方法。

First:返回第一個資料。沒有資料就出生異常。同時也可以傳入第一個資料的條件作為參數。如queryable.First(t => t.CatalogName.Contains("吃"));。

FirstOrDefault:同樣子返回第一個資料,沒有資料的話,就返回NULL。同時也可以傳入第一個資料的條件作為參數。

Last:同理獲得最後一方法。用法同上一樣子。

LastOrDefault:同上一樣子。跟FirstOrDefault用法一樣子。

Skip:給定一個數字,那麼數字前面都不會取出來,後以的才取出來。一般都跟Take方法一起用來作分頁功能。

Take:表示要返回的數量。你可以理解為SQL語句中的TOP關鍵字。

讓筆者舉個linq to object的列子吧。

List<string> src = new List<string>();src.Add("a1");src.Add("b2");src.Add("c4");src.Add("d5");string value = src.First(t => t.StartsWith("a"));Console.WriteLine(value);

注意:筆者是這樣子分的。一般靜態擴充IQueryable<TSource> 的方法屬於linq to entity。而靜態擴充IEnumerable<TSource>則為linq to object。倆者很像。只是linq to entity必須要有資料來源。linq to object一般是處理記憶體資料。

Entity Framework

Entity Framework做為ORM架構之一。所以ORM架構必須有的東西他多有。學習Entity Framework就必須知道他有什麼知識點。Entity Framework根據開發模式的不同分為Code First、Model First和Database First。讓筆者用土一點的說法來講吧。

Code First模式:就是通過寫代碼來產生對應的資料庫和表。

Model First模式:事實上跟Code First有一點像。只是他用了.NET的一個叫xxx.edmx的檔案來操作而以。通過他來產生對應的資料庫和表。

Database First模式:卻跟前面倆個相反。先建資料庫和表在產生對的類。即是代碼。

我們現在要學習Entity Framework。筆者個人意見讀者們最好選擇Code First模式來學習。為什麼。不管是Model First模式還是Database First模式大部分都是軟體工具幫你產生對應的代碼。所以很多東西我們根本看不到。而Code First模式就是要開發人員手把手的寫了。記得筆者在使用hibernate的時候。並沒有說只做一邊的事情。一般都是資料庫的表建完之後。還是要去寫對應的映射設定檔(xxx.hbm.xml)。好一點就自己寫一個代碼產生器。Entity Framework意圖就是幫開發人員做掉一邊的工作。不過這也是筆者不喜歡的。正因為這樣子Entity Framework多出了一個知識點那就是資料移轉。我們都知道在開發的過程中。可能會因為當初表沒有設計好。突然發現需要增加一個欄位。這樣個時候Entity Framework就要做很多事情。假設我們用的是Code First吧。我們在代碼中的類增加一個屬性。這個時候Entity Framework就是要去判斷哪些屬性是舊的。哪些屬性是修改的。哪些屬性是新增加的。然後Entity Framework在更新資料庫。就是Entity Framework的資料移轉。

從上面的講解中我們知道EF想幫我做了另一半的事情,所以就必須對資料庫操作才行。那麼就是存在對資料庫設定,對錶設定,對資料操作。以下全是在在Code First模式下的講說。

1、EF對資料庫的設定。執行代碼的時候,EF會去判斷是否存在對應的資料庫。而對資料庫進行操作。是建立還是刪除在建立。還是更新呢?主要看你設定對應的資料庫操作的模式。那麼EF為我們提供了三個。當然我們可以自己寫一個。三個類都在System.Data.Entity命名空間下。分別是CreateDatabaseIfNotExists、DropCreateDatabaseAlways、DropCreateDatabaseIfModelChanges。文英好的人都能看得懂是什麼一會事。

 public AomiContext()            : base("Aomi")        {            Database.SetInitializer<AomiContext>(new CreateDatabaseIfNotExists<AomiContext>());        }

注意:關於Database.SetInitializer方法的賦值是可以放在別的地方。但一定要執行EF之前。

2、EF對錶的設定。這些設定大部是關於表和表與表之間的關係如何體現在類和類與類之間的關係。下面筆者做了一個簡單的映射例子。

 public class AomiContext : DbContext    {        public AomiContext()            : base("Aomi")        { }        public IDbSet<Catalogs> Catalogs { set; get; }        protected override void OnModelCreating(DbModelBuilder modelBuilder)        {            base.OnModelCreating(modelBuilder);            modelBuilder.Configurations.Add(new CatalogsMap());        }    }

下面CatalogsMap類就相當於映射設定檔(xxx.hbm.xml)。其中包括一對一,一對多,多對多之類的關係也是在這裡配置。有一點要注意的是上面紅色代碼。即是把映射關係加入EF配置裡面

 public class CatalogsMap : EntityTypeConfiguration<Catalogs>    {        public CatalogsMap()        {            this.HasKey(t => t.ID);            this.Property(t => t.CatalogName).HasColumnName("CatalogName");            this.Property(t => t.CatalogCode).HasColumnName("CatalogCode");        }    }

3、對資料的操作。對資料的操作一般就是增刪改查了。

增加:

using (AomiContext ac = new AomiContext()){      Catalogs catalogs = new Catalogs();      catalogs.ID = 6;      catalogs.CatalogName = "商品";      catalogs.CatalogCode = "s0001";      ac.Catalogs.Add(catalogs);                      ac.SaveChanges();}

事實上面的代碼是沒有問題。可是執行的時候卻會發生錯誤。為什麼呢?筆者也不是清楚什麼原因。尋找沒有問題。可是在增加卻會出問題。讓我看一下異常吧。這裡筆者只複製出一部分。

       InnerException: System.Data.SqlClient.SqlException            _HResult=-2146232060            _message=不能將值 NULL 插入列 ‘ID‘,表 ‘Ado.dbo.Catalogs‘;列不允許有 Null 值。INSERT 失敗。語句已終止。            HResult=-2146232060            IsTransient=false            Message=不能將值 NULL 插入列 ‘ID‘,表 ‘Ado.dbo.Catalogs‘;列不允許有 Null 值。INSERT 失敗。語句已終止。            Source=.Net SqlClient Data Provider            ErrorCode=-2146232060            _doNotReconnect=false

他說我的ID沒有設定值,可是我設定了。筆者想你們一定會認為是沒有設定標識。也就是自動成長。不是這樣子的。筆者本來就是沒有想過要自動成長啊。那麼為什麼會錯呢?事實筆者也是以這樣子的角度去想的。會不會EF預設就認為ID是自動成長。因為ID是int類型的。又是主鍵。所以我就在映射配置裡面加了一段代碼。如下

 public class CatalogsMap : EntityTypeConfiguration<Catalogs>    {        public CatalogsMap()        {            this.HasKey(t => t.ID);            this.Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);            this.Property(t => t.CatalogName).HasColumnName("CatalogName");            this.Property(t => t.CatalogCode).HasColumnName("CatalogCode");        }    }

紅色部分就是增加的代碼。這個時候就不會出錯了。那麼這段代碼是什麼意思。就是告訴EF這裡的ID只是普通的。沒有別的設定。

修改:

using (AomiContext ac = new AomiContext()){     Catalogs updateCatalogs = ac.Catalogs.FirstOrDefault(t => t.ID == 4);     updateCatalogs.CatalogName = "紙類";                     ac.SaveChanges();}

刪除:

using (AomiContext ac = new AomiContext()){ Catalogs deleteCatalogs = ac.Catalogs.FirstOrDefault(t => t.ID == 1); ac.Catalogs.Remove(deleteCatalogs);                 ac.SaveChanges();}

筆者在做EF的增刪改的時候,我心裏面一直在想倆個問題?

第一:EF並沒有像Hibernate那樣子。有增加的方法和更新的方法。他只有一種概念那就是資料有沒有發生改變。根據改變資料來更新資料庫的資料。

第二:Hibernate在處理對象的時候。會用到對象的三種狀態。這三種狀態在不同的書裡面有不同的叫法。筆者一般喜歡叫他們為普通狀、持久狀、游離狀。可是如果把Hibernate這個知識放在EF這邊來的話,也不是說不可以。只是覺得這個時候有一點怪。EF這邊並沒有類似相關的說明。可是筆者還是覺得有必要用他放在EF這邊。為什麼呢?先讓我們看一下情況吧。如果我們把上面的ID變成了標識。即為自動成長。在增加的時候就沒有必要去設定這個值。那麼增加成功之後我們要如果去獲得對應的ID值呢?難道在擷取一遍嗎?顯然不是。如下。

class Program    {        static void Main(string[] args)        {            using (AomiContext ac = new AomiContext())            {                Catalogs catalogs = new Catalogs();                catalogs.ID = 0;                catalogs.CatalogName = "商品";                catalogs.CatalogCode = "s0001";                ac.Catalogs.Add(catalogs);                ac.SaveChanges();                Console.WriteLine("ID:" + catalogs.ID);            }            Console.ReadKey();        }    }

筆者把上面的資料全部刪除掉。並且把Catalogs表的列ID修改為標識。即是自動成長。在建立Catalogs對象的時候把ID設定為0。然後我們在看一下增加成功之後ID是不是還是為0。但是記得把CatalogsMap類裡面的映射配置修改一下。修改如下。

 this.Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

好了,讓我們看一下結果值是多少。如下。我們看到是7。為什麼不是0。筆者的SQL Server的標識是從6開始的。所以這邊是7。如果你們一開始的話。是1。但決對不是0。為什嗎?就是因為對象變成了持久狀了。

普通狀:就是正常用關鍵字new來建立。

持久狀:就是通過EF之後,比如增加。這個時候對象跟資料庫同步。

游離狀:關閉EF之後的對象。不過筆者認為EF沒有這種狀態。因為Hibernate有喚醒這個功能。

本章總結

本章主要講到關於Linq和EF的知識點。Linq的一些入門用法和EF的基本知識點。當然,有關EF的類與類之間的關係和資料移轉筆者筆者必沒有說。希望讀者們自行查看。

Java進擊C#——應用開發之Linq和EF

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.