一步一步學Entity Framework 4(2)


上一期博客末尾給出了一個靜態類,四個靜態方法。這有什么用呢?在繼續進行其他內容前,我們先利用這些靜態方法為數據庫生成基礎數據。按照ADO.NET的常規方法,需要先建立連接,創建DataSet或DataReader對象,構造SQL語句,然后執行SQLCommand命令進行作業。現在有了EF,這些步驟全部省掉,EF的內部已經自動實現了這些步驟,我們所要做的只是去調用剛剛定義的幾個方法。

在項目上新建一個文件夾,命名為Presenter,用來包含一些與用戶交互操作的方法,以便Main函數進行調用。在Presenter文件中下新建一個類,命名為AddDataView.cs,該類就包含一個方法,用以向數據內插入數據:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProductEFDemo.Business;

namespace ProductEFDemo.Presenter
{
   public static class AddDataView
    {
       public static void AddAllData()
       {
           
           try
           {
               //添加單位數據
               InsertData.AddProudctUnit();
               Console.WriteLine("產品單位測試數據插入成功!");
               //添加產品大類數據
               InsertData.AddProductBigType();
               Console.WriteLine("產品大類測試數據插入成功!");

               //添加產品小類數據
               foreach (var productBigType in SearchData.GetAllProductBigType())
               {
                   InsertData.AddProductSmallType(productBigType.ProductBigTypeId);
               }
               Console.WriteLine("產品小類測試數據插入成功!");

               //添加產品數據
               foreach (var productSmallType in SearchData.GetAllProductSmallType())
               {
                   foreach (var productUnit in SearchData.GetAllProductUnit())
                   {
                       InsertData.AddProduct(productUnit.ProductUnitId, productSmallType.ProductSmallTypeId);
                   }
               }
               Console.WriteLine("添加產品測試數據成功!");


           }
           catch (System.Exception ex)
           {
               Console.WriteLine(String.Format("發生錯誤,錯誤信息為:{0}",ex.Message) ;              
           }
       }
    }
}

該類中調用的三個方法:GetAllProductBigType(),GetAllProductUnit(),GetAllProductSmallType()代碼如下,由於這些是查詢的內容,相關信息后面再詳細介紹。

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProductEFDemo.Models;

namespace ProductEFDemo.Business
{
  public static  class SearchData
    {
      public static List<ProductBigType> GetAllProductBigType()
      {
          using (var ctx = new ProductsEntities())
          {
              return ctx.ProductBigType.ToList();
          }
      }

      public static List<ProductUnit> GetAllProductUnit()
      {
          using (var ctx = new ProductsEntities())
          {
              return ctx.ProductUnit.ToList();
          }

      }

      public static List<ProductSmallType> GetAllProductSmallType()
      {
          using (var ctx = new ProductsEntities())
          {
              return ctx.ProductSmallType.ToList();
          }
      }
    }
}

代碼沒有什么可介紹的,注意有關命名空間的引用。

現在開始調用,在項目的Program.cs的Main方法里調用剛剛定義的AddAllData方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProductEFDemo.Presenter;

namespace ProductEFDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            AddDataView.AddAllData();
            Console.ReadKey();
        }
    }
}

注意引用ProductEFDemo.Presenter命名空間。

生成並運行程序,

image

打開SqlSever Management Studio,展開表,發現數據已經全部插入了:

image

你猜猜使用上述代碼一共生成了多少條Product數據?到目前為止,一句SQL代碼都沒有寫,寫的代碼都是純粹的C#代碼,而且還有額外的IDE代碼提示,是不是很爽?強烈建議不要拷貝代碼,而是一行行輸入,這樣才能感受到書寫代碼的快樂。

5、數據查詢

現在有了數據,就可以隨心所欲地進行查詢了。這里先對前面調用的GetAllProductBigType(),GetAllProductUnit(),GetAllProductSmallType()作一個解釋:

前面三個方法是獲取集合性質數據的典型方法,使用的是ToList()擴展方法(關於擴展方法請自已去MSDN查去,我覺得內置的擴展方法就夠用了,暫時還不想去自定義!),就可以將實體類的數據進行序列化,成為List范型類,List范型類很好用,可以直接綁定為很多集合類控件的數據源,不管你是用WinForm,還是.Net MVC或是WPF,SIlverlight等,都會廣泛地用到,但這超出了我想表達內容的范圍,就不贅述了。下面就Linq查詢作一介紹。原來我對Linq很抵觸,在《C#入門經典》讀到這一章的時候,我選擇的是跳過,不去看它。后來有時間再翻閱書的時候,驚訝地發現我錯過了多么美麗的風景!太簡潔,太優雅了!真心感謝微軟,推出了這么好用的工具和語法。。。好了,不吹捧微軟了,我們還是先來了解一下Linq的基本知識:

所有的的Linq都是引用自System.Linq名稱空間的類,因此在使用前要檢查是否引用該名稱空間,實際上VS在創建類的模板里已經默認添加了這個引用,如果沒有請自行添加。

Linq用於查詢數據的語法有兩種,一種稱為查詢語法,有點類似於Sql語句,與Sql語句不同的地方是from在前,select在后,from,select,where,distinct,join(注意全部為小寫)現在已經成為C#的關鍵字,專門用於Linq查詢,這種方法一般是首選方法,因為它一般更容易理解,但是可惜的是,Linq的全部功能不能由查詢語法實現(比如前面的ToList方法在LInq里沒有對應關鍵字),需要借助於另一種語法,稱為方法語法,相比而言,方法語法使用起來稍微難理解一點,但是方法語法的優點是比較簡潔,代碼更優雅。下面兩種方法都介紹,可以先用查詢語法,熟練以后可以試試方法語法,其實也挺好的!

1)簡單查詢:場景是這樣的,需要從ProductBigType類中查詢出包含數字“7”的記錄,可以這樣寫:

using(var ctx=new ProductsEntities())
{
    var result=from c in ctx.ProductBigType
                         where c.ProductBigTypeName.Contains("7")
                         select c;
              return result.ToList();
}
如果采用方法語法則可以簡化為(為代碼簡潔起見,后面不再引用using字句):
return ctx.ProductBigType.Where(c => c.ProductBigTypeName.Contains("7")).ToList();

返回的記錄是一樣的,只不過方法更優雅;

2)數據排序

查詢語法使用orderby關鍵字,如果采用降序,可以添加descending關鍵字;下面示例的場景是這樣的,需要從查詢出的所有的ProductBigType中查詢出首個單詞為“產品”的記錄(其實就是全部記錄),按降序排序,代碼如下:

查詢語法:

var result=from c in ctx.ProductBigType 
                         where c.ProductBigTypeName .StartsWith("產品")
                         orderby c.ProductBigTypeName descending
                         select c;
              return result.ToList();
方法語法:
return ctx.ProductBigType.Where(c => c.ProductBigTypeName.StartsWith("產品")).OrderByDescending(c => c.ProductBigTypeName).ToList();

方法語法這種長串的語法稱為鏈式語句,意思像一條長鏈一樣,這是因為所用的方法是擴展方法,都具有相同的類型(實現了IQueryable或IOrderable范型接口),所以可以連接起來;

3)聚合運算符:用於統計數據,與SQL語句中的相關聚合運算符類似,不同之處這些運算符都是擴展方法,首字母必須大寫,常用的聚合擴展方法如下:

Count(),表示結果的個數;

Min(),表示結果中的最小值

Max(),表示最大值;

Average(),表示數據結果的平均值;

Sum(),表示求和;

現在我們想要查詢出Product表中ProductBaseValue中最大的數據,就可以這樣寫:

public decimal MaxProductBasePrice()
      {
          using (var ctx = new ProductsEntities())
          {
              var result= from c in ctx.Product
                          select c.ProductBasePrice;
              return result.Max();
          }
      }
或者采用方法語法:
return ctx.Product.Max(c => c.ProductBasePrice);

4)使用導航屬性:

昨天博客發表以后,有園友問為什么要用Entity Framework4.x POCO Generator,除了節省力氣以外,很重要的一個原因是自已手工寫的類經常會忘記導航屬性,導致在使用Linq無法導航到所需要的數據,不得不借助join來完成。實際上,Sql的內聯Join關鍵字完全可以少用或者不用(雖然Linq同樣提供了jion關鍵字和Join擴展方法),而采用一個新擴展方法Include來解決內聯的問題,可以順利地完成導航。在使用導航屬性進行內聯接之前,我們先來看看POCO生成的類:

    public partial class Product
    {
        #region Primitive Properties
    
        public virtual System.Guid ProductId
        {
            get;
            set;
        }
    
        public virtual string ProductName
        {
            get;
            set;
        }
    
        public virtual System.Guid ProductSmallTypeId
        {
            get { return _productSmallTypeId; }
            set
            {
                if (_productSmallTypeId != value)
                {
                    if (ProductSmallType != null && ProductSmallType.ProductSmallTypeId != value)
                    {
                        ProductSmallType = null;
                    }
                    _productSmallTypeId = value;
                }
            }
        }
        private System.Guid _productSmallTypeId;
    
        public virtual System.Guid ProductUnitId
        {
            get { return _productUnitId; }
            set
            {
                if (_productUnitId != value)
                {
                    if (ProductUnit != null && ProductUnit.ProductUnitId != value)
                    {
                        ProductUnit = null;
                    }
                    _productUnitId = value;
                }
            }
        }
        private System.Guid _productUnitId;
    
        public virtual decimal ProductBasePrice
        {
            get;
            set;
        }
    
        public virtual decimal ProductLosePrice
        {
            get;
            set;
        }

        #endregion
        #region Navigation Properties
    
        public virtual ProductSmallType ProductSmallType
        {
            get { return _productSmallType; }
            set
            {
                if (!ReferenceEquals(_productSmallType, value))
                {
                    var previousValue = _productSmallType;
                    _productSmallType = value;
                    FixupProductSmallType(previousValue);
                }
            }
        }
        private ProductSmallType _productSmallType;
    
        public virtual ProductUnit ProductUnit
        {
            get { return _productUnit; }
            set
            {
                if (!ReferenceEquals(_productUnit, value))
                {
                    var previousValue = _productUnit;
                    _productUnit = value;
                    FixupProductUnit(previousValue);
                }
            }
        }
        private ProductUnit _productUnit;

        #endregion
        #region Association Fixup
    
        private void FixupProductSmallType(ProductSmallType previousValue)
        {
            if (previousValue != null && previousValue.Product.Contains(this))
            {
                previousValue.Product.Remove(this);
            }
    
            if (ProductSmallType != null)
            {
                if (!ProductSmallType.Product.Contains(this))
                {
                    ProductSmallType.Product.Add(this);
                }
                if (ProductSmallTypeId != ProductSmallType.ProductSmallTypeId)
                {
                    ProductSmallTypeId = ProductSmallType.ProductSmallTypeId;
                }
            }
        }
    
        private void FixupProductUnit(ProductUnit previousValue)
        {
            if (previousValue != null && previousValue.Product.Contains(this))
            {
                previousValue.Product.Remove(this);
            }
    
            if (ProductUnit != null)
            {
                if (!ProductUnit.Product.Contains(this))
                {
                    ProductUnit.Product.Add(this);
                }
                if (ProductUnitId != ProductUnit.ProductUnitId)
                {
                    ProductUnitId = ProductUnit.ProductUnitId;
                }
            }
        }

        #endregion
    }

其中ProductUnit和ProductSmallType兩個屬性就是導航屬性,這兩個屬性就可以在實際使用中進行內連接;

比如我們想在控制台中輸出若干條Product記錄(這里的搜索條件為ProductBasePrice介於49-50之間),要求顯示格式為:產品名稱:XXXX;產品大類:XXX;產品小類:XXX;單位:XXX;基本價格:XXX;

就需要通過內聯從其他表中獲取數據,就要用到前面所說的導航屬性。注意到Product表只導航到了ProductSmallType,還需要借助ProductSmallType類導航到ProductBigType。(如果您不明白,還請再回頭看有關數據庫表的說明)。

這樣代碼就可以這樣寫:

public static List<Product> GetProductByPrice()
      {
          using (var ctx = new ProductsEntities())
          {
              return ctx.Product.Include("ProductUnit").
                  Include("ProductSmallType").Include("ProductSmallType.ProductBigType")
                  .Where(c => (c.ProductBasePrice <= 50 && c.ProductBasePrice >= 49)).ToList();
  
          }
      }

為了實現前面所說的輸出,需要在Presenter文件夾中新建一個類,類中有一個方法對相關數據進行處理:

 

 public static class ProductDeatilView
    {
       public static void ShowProductDeatil()
       {
           foreach (var product in SearchData.GetProductByPrice())
           {
               Console.WriteLine(String.Format("產品名稱:{0};產品大類:{1};產品小類:{2};單位:{3};基本價格:{4};",
                   product.ProductName, product.ProductSmallType.ProductBigType.ProductBigTypeName,
                   product.ProductSmallType.ProductSmallTypeName, product.ProductUnit.ProductUnitName, product.ProductBasePrice));
           }
       }
    }

ShowProductDeatile方法只有一個語句,在調用時就使用了Product類的導航屬性,只要你想,可以導航到任何數據,但前提是已經將數據都包容進來了。

現在在Main方法中調用ShowProductDeatile方法,輸出結果如下:

image

這個例子使用查詢語法也簡單不了多少,因為並沒有include關鍵字可用,也必須要使用Include擴展方法。在此就不舉例了。需要注意的是關於Include擴展方法的參數問題,這個參數是導航的路徑,必須是一個字母不錯的導航屬性名,否則會因為查詢不到數據而引發異常。如果需要通過導航屬性再導航到其他相關的導航屬性上,則需要使用點式語法,如上例中對ProductBigType導航屬性的引用,就需要使用Include("ProductSmallType.ProductBigType")這樣的語法;你也可以試試去掉某一個Include語句,試試會發生什么問題。

這個例子還有一種相對麻煩的實現方法,就是新建一個ProductDepository類,將需要展示出的字段放進去,然后使用Linq的join關鍵字來進行查詢,新建的ProductDepository類:

public  class ProductDepository
    {
        public string ProductName { get; set; }
        public string ProductBigTypeName { get; set; }
        public string ProductSmallTypeName { get; set; }
        public string ProductUntName { get; set; }
        public decimal ProductBasePrice { get; set; }
    }

而查詢語句如下:

 public static List<ProductDepository> GetProductDepositoryByPrice()
      {
          using (var ctx = new ProductsEntities())
          {
              var result = from c in ctx.Product
                           join d in ctx.ProductSmallType on c.ProductSmallTypeId equals d.ProductSmallTypeId
                           join e in ctx.ProductBigType on d.ProductBigTypeId equals e.ProductBigTypeId
                           join f in ctx.ProductUnit on c.ProductUnitId equals f.ProductUnitId
                           where c.ProductBasePrice<=50 && c.ProductBasePrice >=49
                           select new ProductDepository
                           {
                               ProductName = c.ProductName,
                               ProductBigTypeName = e.ProductBigTypeName,
                               ProductSmallTypeName = d.ProductSmallTypeName,
                               ProductUntName = f.ProductUnitName

                           };
              return result.ToList();
          }
      }

調用數據的方法則變為:

 public static void ShowProductDeatil()
       {
           foreach (var productDepository in SearchData.GetProductDepositoryByPrice())
           {
                       Console.WriteLine(String.Format("產品名稱:{0};產品大類:{1};產品小類:{2};單位:{3};基本價格:{4};",
                   productDepository.ProductName, productDepository.ProductBigTypeName,
                   productDepository.ProductSmallTypeName, productDepository.ProductUntName,
                   productDepository.ProductBasePrice));
                   
           }
       }

輸出的效果是一樣的。這種方法雖然比較麻煩,但是當類的關第比較復雜,需要重新梳理時,這不失為一種有效的解決方案,由於類可以無限制擴展,在進行類似WPF或SilverLIght的MVVM模式開發中,可以很方便地將模型與類本身進行分離。但是一般來說,使用導航屬性方便的時候,大可不必這么大動干戈,具體怎樣取舍,取決於程序的復雜程度。

另外,查詢語法的select字句不支持多個字段的語法,因此要想使用多個字段,就必須在查詢中創建新對象,可以顯示地創建類,也可以創建無名稱的對象,術語將這種情況稱為投影(projection);使用方法語法也可以進行實現投影,代碼如下:

var result=product.Select(c=>new{c.ProductName,c.ProductBasePrice});

 

 

聲明:本文系本人原創,版權歸屬作者和博客園共同所有,任何組織或個人不得隨意轉載,修改。需要轉載請與本人聯系:qouoww@163.com。


免責聲明!

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



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