1. 程式人生 > >Java進擊C#——應用開發之Linq和EF

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

了吧 -1 擴展 有一點 增刪改 adk 對象 structure mis

本章簡言

上一章筆者對於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