C# - LINQ 語言集成查詢


LINQ(Language Integrated Query) 

LINQ語言集成查詢是一組用於C#語言的擴展。它允許編寫C#代碼對數據集進行查詢,比如查詢內存中的對象或查詢遠程數據庫的表。利用linq,程序員不必掌握數據庫查詢語句而是使用Linq就能完成相同的查詢任務。而傳統數據查詢的弱點很多,比如執行簡單查詢也需要冗長的操作代碼,查詢語句是字符串格式,無法讓編譯器執行檢查錯誤及早提示,查詢不是強類型,查詢參數容易寫錯,查詢結果沒有真正面向對象,每次查詢取結果還得事先知道列名或列索引,不使用抽象工廠的前提下,查詢語句是定死的,只能應用於一種數據庫。 即使使用抽象工廠,代碼量也巨大。 Enumerable類為IEnumerable<T>擴展了用於查詢內存中的對象的linq查詢方法。而Queryable為IQueryable <T>擴展了對遠程數據源進行linq查詢的方法。 而在System.Collections命名空間下的集合類雖然不能使用那些從IEnumerable<T>上擴展的方法,但可以通過調用非泛型集合的Cast<T>()將其轉換為泛型集合,從而可以使用Enumerable類提供的擴展方法。
 

 

Lambda表達式

格式

Lambda表達式比函數有着更為簡潔的語法格式。在某些情況下使用Lamnbda就可以替代函數,讓代碼變得更簡潔易讀。而定義Lambda表達式與定義函數其實差別不大,而且還做到了簡化。 

=> e.Name == "sam" 
定義函數你得聲明函數的權限修飾符、形態修飾符和參數列表、返回類型等,而Lambda就這么一段代碼就做完了函數的工作
e:參數
=>:操作符,可描述執行后面的邏輯……操作符后面的則是單個表達式,也可以是語句塊。即函數的方法體。

示例

//有{}代碼塊的稱為語句Lambda,無{}代碼塊的稱為Lambda表達式
(string e) => e.Name == "sam"; // 顯示聲明了參數 操作符后面是求值表達式 也即返回一個求值的結果
e => e.Name == "sam"; // 隱式聲明了參數 操作符后面是求值表達式
e => { return e.Name == "sam"; }  // 隱式聲明了參數 操作符后面是求值語句塊
(string e, string x)=>{ return e.Name == "sam" && e.Age = 32; } //顯示聲明了多參數 操作符后面是求值語句塊
(e, x) => { return e.Name == "sam" && e.Age = 32; } //隱式聲明了多參數 操作符后面是求值語句塊
()=>Console.WriteLine(); //無參數 操作符后面是執行語句 返回void

Lambda與委托

Lambda就是一個匿名的函數,所以你可以將Lambda當成委托實例作為參數進行傳遞,如果要這樣做,則你定義的Lambda表達式的簽名和返回類型必須符合委托的簽名和返回類型

static void ShowTime(Func<DateTime> d)
{
    Console.WriteLine(d().ToString());
}
static void Main(string[] args)
{
    Func<DateTime> getDateTime = () => DateTime.Now; //將匹配Func<T>委托簽名和返回類型的lambda表達式當做委托使用
    ShowTime(getDateTime);
}

 

LINQ延遲查詢機制

LINQ查詢操作符並不會立即執行,它總是在查詢結果集真正被使用的時候才會開啟查詢。 而且每次只將迭代的當前項存入內存中處理,這就降低了內存占用率。那些需要一次性統計所有集合的操作符如:OrderBy、Reverse、ToList、ToArray、ToDictionary,它們都會破壞延遲查詢,也即調用這些操作符則會立即開啟查詢。

int[] numbers = new int[] { 0, 1, 2, 100 };
var result = numbers.Where(n => n <= 2); //不做任何操作,等待后續調用時才開始執行查詢

foreach (var item in result) //開始linq查詢
    Console.WriteLine(item); // print 0,1,2

for (int i = 0; i < 10; ++i) //更改numbers的每個元素,取相反數
    numbers[i] = -numbers[i];

foreach (var item in result) //開始linq查詢
    Console.WriteLine(item); // print 0,-1,-2,-100

//在這個例子中,如果Where查詢是立即執行的,那么result只會存儲0,1,2,第二次foreach時,也應該輸出0,1,2,但實際情況卻不是這樣
//為什么為linq使用延遲查詢?
//因為上面例子中的numbers只調用了Where方法,如果它不立即開啟查詢,直到你要用到查詢結果的時候才去執行查詢
//那么這樣做的好處是顯而易見,因為在你還未使用查詢結果之前,你可能還會繼續在numbers上調用其它的linq擴展方法
//那么這些擴展方法就形成了一個鏈式的操作
//而這個鏈式操作只返回最終一個結果,省去了每調用一次擴展方法就開啟查詢,無形中就增加了內存或數據庫的查詢壓力

 

LINQ查詢表達式

LINQ查詢表達式類似於SQL查詢語句,但個人不喜歡它的書寫格式。它是由from、join group by等句子組成的LINQ查詢表達式,而LINQ查詢表達式與直接使用LINQ擴展方法執行查詢在語法結構上是不一樣的,我認為后者更易於閱讀,所以此處略過。

 

LINQ操作符

幾乎每個Linq操作符都接收一個委托實例(Lambda),操作符對集合進行迭代,每迭代一次會自動調用Lambda,根據lambda來返回結果集。

1.過濾

根據參數提供的lambda表達式指定的邏輯過濾集合中的元素,將計算的結果存入結果集返回。可以按條件、按索引、按開頭或結尾、按唯一的單個項進行篩選。

Where lambda )
//根據lambda的邏輯篩選符合條件的元素
//此方法所要求的委托還可以有第三個參數:Index,表示當前對象在集合中的索引號

ElementAtint index )
//根據參數索引查找處於該索引處的項

Single( [ lambda ] )
//lambda:可選的lambda表達式,用於提供過濾的條件
//返回集合中的唯一的項
//類似的有FirstOrDefault方法,該方法保證無唯一的項時安全返回一個default<T>
//如果集合存在多個項則會引發異常

First( [ lambda ] )
//lambda:可選的lambda表達式,用於提供過濾的條件
//類似的有FirstOrDefault,該方法保證無項時安全返回一個default<T>
//返回集合中的第一個項,或根據過濾獲取指定的項


Last( [ lambda ] )
//lambda:可選的lambda表達式,用於提供過濾的條件
//類似的有LastOrDefault,該方法保證無項時安全返回一個default<T>
//返回集合中的最后一個項,或根據過濾獲取指定的項

2.投影

根據參數提供的lambda表達式返回的項,將項存入結果集返回

Selectlambda )
//根據lambda表達式的計算邏輯,將計算的結果存入結果集返回

//示例1:
int[] a = { 1, 2, 3 };
var s = a.Select(i => i > 2); //計算i>2的結果,將結果存入結果集返回
foreach (var item in s)
{
    Console.WriteLine(item); //print flse,false,true
}

//示例2:
public class Book
{
    public string Title { get; set; }
    public double Price { get; set; }
    public List<Author> Authors { get; set; }
}

public class Author
{
    public string Name { get; set; }
}

public class Programe
{
    static void Main(string[] args)
    {
        List<Book> books = new List<Book>
        {
            new Book
            {
                Title="寂靜的春天",
                Price=19.8,
                Authors = new List<Author>
                {
                    new Author{  Name="sam" },
                    new Author{  Name="leo" }
                }
            },
            new Book
            {
                Title="萬有引力之虹",
                Price=20.7,
                Authors=new List<Author>
                {
                    new Author{ Name="korn" },
                    new Author{ Name="Tim" }
                }
            },
            new Book
            {
                Title="卡拉馬佐夫兄弟",
                Price=30.5,
                Authors=new List<Author>
                {
                    new Author{ Name="lily" },
                    new Author{ Name="lvis" }
                }
            }
        };

        IEnumerable<string> titleList = books.Select(book => book.Title); //將lambda表達式的計算結果Book的Title存儲到結果集IEnumerable中,將IEnumerable<string>作為結果集返回
        foreach (var title in titleList)
        {
            Console.WriteLine(title);//print 寂靜的春天,萬有引力之虹,卡拉馬佐夫兄弟
        }

        IEnumerable<double> priceList = books.Select(book => book.Price); //將lambda表達式的計算結果Book的Price存儲到結果集IEnumerable中,將IEnumerable<double>作為結果集返回
        foreach (var price in priceList)
        {
            Console.WriteLine(price);//print 19.8,20.7,30.5
        }

        IEnumerable<Book> bookList = books.Select(book => book); //將lambda表達式的計算結果Book存儲到結果集IEnumerable中,將IEnumerable<Book>作為結果集返回
        foreach (var book in bookList)
        {
            Console.WriteLine($"{book.Title}{book.Price}");//print ……
        }

        IEnumerable<List<Author>> authorListList = books.Select(book => book.Authors); //將lambda表達式的計算結果Book的Authors存儲到IEnumerable中,將 IEnumerable<List<Author>>作為結果集返回
        foreach (var authorList in authorListList)
        {
            foreach (var author in authorList)
            {
                Console.WriteLine(author.Name); //print sam,leo,korn,Tim,lily,lvis
            }
        }
    }
}

SelectMany ( lambda )
//根據lambda表達式指定的邏輯將計算的結果從集合中抽取出來存入結果集返回
//此方法所要求的委托還可以有第三個參數:Index,表示當前對象在集合中的索引號
//示例:
IEnumerable<Author> authorList = books.SelectMany(book => book.Authors); //將lambda表達式的計算邏輯的結果從集合中抽取出來存入結果集返回,因為Authors是一個List<Author>,抽取Author后存入結果集,所以結果集就是一個 IEnumerable<Author> 

3.去重

根據參數提供的lambda表達式返回的項對集合進行去重,將去重后的結果存入結果集返回

Distinct( [ IEqualityComparer < T > comparer ] )
//去除集合中的重復項
//comparer:引用類型的去重需要手動為類型實現 IEqualityComparer<T>
//此方法內部維護了一個測試相等性的比較器
//如果項是struct或string,則比較值的相等性
//如果項是class,則比較堆地址的相等性,可是多半在執行查詢時查詢的是class,class多半都是不相等的堆引用,所以此方法無參版只能用於struct或string的去重

ExceptIEnumerable < T > second [ , IEqualityComparer < T > comparer ] )
//將參數1的集合與當前集合做比較,返回沒有同時出現在兩個集合中的項。也即求差集
//comparer:可選,引用類型的比較需要手動為類型實現 IEqualityComparer<T>
//此方法內部維護了一個測試相等性的比較器
//如果項是struct或string,則比較值的相等性
//如果項是class,則比較堆地址的相等性,可是多半在執行查詢時查詢的是class,class多半都是不相等的堆引用,所以此方法無參版只能用於struct或string的差集

IntersectIEnumerable < T > second [ , IEqualityComparer < T > comparer ] )
//將參數1的集合與當前集合做比較,返回同時出現在兩個集合中的項。也即求交集
//comparer:可選,引用類型的比較需要手動為類型實現 IEqualityComparer<T>
//此方法內部維護了一個測試相等性的比較器
//如果項是struct或string,則比較值的相等性
//如果項是class,則比較堆地址的相等性,可是多半在執行查詢時查詢的是class,class多半都是不相等的堆引用,所以此方法無參版只能用於struct或string的交集

UnionIEnumerable < T > second )
//將參數1的集合與當前集合進行合並,也即求並集
//用於引用類型的自定義相等性比較器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.UI;
using System.IO;
 
namespace Yin.General
{
    public static class LinqHelper<T>
    {
        public static IEqualityComparer<T> CreateComparer<TV>(Func<T, TV> keySelector)
        {
            return new CommonEqualityComparer<TV>(keySelector);
        }

        public static IEqualityComparer<T> CreateComparer<TV>(Func<T, TV> keySelector, IEqualityComparer<TV> comparer)
        {
            return new CommonEqualityComparer<TV>(keySelector, comparer);
        }

        private class CommonEqualityComparer<TV> : IEqualityComparer<T>
        {
            private readonly IEqualityComparer<TV> _comparer;
            private readonly Func<T, TV> _keySelector;

            public CommonEqualityComparer(Func<T, TV> keySelector, IEqualityComparer<TV> comparer)
            {
                _keySelector = keySelector;
                _comparer = comparer;
            }

            public CommonEqualityComparer(Func<T, TV> keySelector)
                : this(keySelector, EqualityComparer<TV>.Default)
            { }
            public bool Equals(T x, T y)
            {
                return _comparer.Equals(_keySelector(x), _keySelector(y));
            }

            public int GetHashCode(T obj)
            {
                return _comparer.GetHashCode(_keySelector(obj));
            }
        }
    }
}
相等性比較器
//有了以上的自定義的比較器,現在可以對引用類型的項進行自定義的去重了
var records = Authors.Distinct(LinqHelper<Author>.CreateComparer<string>(a => a.FirstName)); //根據引用類型Author的FirstName進行去重

4.轉換

ToArray( )
//將結果集轉換為數組,不再具有延遲機制,查詢將立即執行

ToList( )
//立即將結果集轉換為列表集合,不再具有延遲機制,查詢將立即執行

ToDictionary( )
//立即將結果集轉換為哈希字典,主要用於將List<T>集合中的元素的某個成員作為哈希字典的key來存取元素,不再具有延遲機制,查詢將立即執行
//示例
public class Animal
{
    public int Id { get; set; }
    public string Name { get; set; }
}

List<Animal> list = new List<Animal>
{
    new Animal{  Id=1, Name="sam"},
    new Animal{  Id=2, Name="leo"}
};

Dictionary<int, Animal> dictionary = list.ToDictionary( animal => animal.Id ); //用元素的id作為哈希字典的鍵
Console.WriteLine(dictionary[1].Name );

Cast<T>()
//將非泛型集合轉換為泛型集合,非泛型集合沒有Linq查詢,因為存儲的都是object,轉換之后就可以執行linq查詢

AsEnumerable( )
//將結果集轉換為IEnumerable<T>類型,主要用於屏蔽集合的索引器,因為IEnumerable只有一個可迭代的方法,所以如果你只想將集合暴露給用戶查看而不需要他們去設置集合的元素,就可以使用此方法

AsQueryable( )
//將結果集轉換為IQueryable<T>類型

聚合

Sum() 
//所見即所得

Count()
//所見即所得
//ICollection接口有一個Count的屬性
//而擴展方法為集合提供了Count()方法
//兩者都會執行遍歷計算總項數,如果只想測試是否有項,可使用效率更高的Any()

Min()
//所見即所得

Max()
//所見即所得

Average()
//所見即所得

Aggregate()
//所見即所得

6.排序

按lambda表達式返回的項進行排序

OrderBy( lambda [ , IComparer < T > comparer] )
//按lambda表達式返回的項進行升序排序,返回一個 IOrderedEnumerable<TSource>
//只能使用一次,如果多次使用了OrderBy方法,那么只有最后一個會生效
//要在一個已經OrderBy的集合上執行再排序應使用ThenBy(),ThenBy()可出現N次

OrderByDescending( lambda [ , IComparer < T > comparer] )
//降序排序

ThenBy( lambda [ , IComparer < T > comparer] )
//在IOrderedEnumerable<TSource>集合上執行升序排序

ThenByDescending( lambda [ , IComparer < T > comparer] )
//在IOrderedEnumerable<TSource>集合上執行降序排序

Reverse( lambda [ , IComparer < T > comparer] )
//倒轉序列

7.分組

按lambda表達式返回的項進行分組 

GroupBy( lambda )
//按lambda表達式指定的項進行分組,返回一個IEnumerable<IGrouping<TKey,T>>

List<Book> list = new List<Book>
{
    new Book{  Title="寂靜的春天", Price=19.8, Type="社科"},
    new Book{  Title="單向度的人", Price=19.8, Type="社科"},
    new Book{  Title="歷史研究", Price=10.8,Type="歷史"},
    new Book{  Title="卡拉馬佐夫兄弟", Price=10.8,Type="小說"},
};

//單條件分組
var query = list.GroupBy(book => book.Type);
StringBuilder bs = new StringBuilder();

foreach (var group in query)
{
    bs.Append("-----" + group.Key + "-----\r\n"); //Key是組頭,此處是book.Type
    foreach (var book in group)
    {
        bs.Append("     " + book.Title + "\r\n");
    }
    Console.WriteLine(bs.ToString());
    bs.Clear();
}


//多條件分組,同時滿足指定的條件會被分為一組
var query = list.GroupBy(book =>new { book.Type, book.Price });
StringBuilder bs = new StringBuilder();

foreach (var group in query)
{
    bs.Append("-----" + group.Key.Type + "-----\r\n"); //Key是匿名對象,此處是new { book.Type, book.Price }
    foreach (var book in group)
    {
        bs.Append("     " + book.Title + "\r\n");
    }
    Console.WriteLine(bs.ToString());
    bs.Clear();
}


ToLookup(lambda )
//與GroupBy是一樣的,但不延遲查詢,返回一個ILookup<TKey, T>,該類型是IEnumerable<IGrouping<TKey, T>> 的簡化版
var grouping = list.ToLookup(book => book.Type); //立即分組

foreach (var group in grouping)
{
    Console.WriteLine($"========{group.Key}========");
    foreach(var item in group)
    {
        Console.WriteLine(item.Title);
    }
}

8.聯結

將N張表(集合)建立聯結,每張表都有一個共同的鍵,鍵相同的行數據則被歸納到一行中, 下表中的出版社和圖書以出版社ID為關聯鍵位,使用聯結查詢后,相同關聯鍵位的行數據會被整合到一行中

操作符:Join ( ) | GroupJoin ( )

9-1.內聯結:查詢關聯的多張表,但不返回無關聯的記錄(inner join)

Join ( 聯結的另一個集合,能返回被查詢的左邊集合的關聯鍵位的函數,能返回被聯結的右邊集合的關聯鍵位的函數,根據前兩個函數的參數能創建新結果集的函數 )



//適用於1對1、1對多關系,類似於sql的inner join,無關聯的記錄不會返回
var query = publishers.Join(books, publish => publish.ID, book => book.PublisherID, (publish, book) => new
{
    pubID = publish.ID,
    pubName = publish.Name,
    bookName = book.Name
});
 

9-1.分組聯結:將關聯的多張表的記錄進行分組

單表分組使用GroupBy,多表連接分組則使用GroupJoin

GroupJoin ( 聯結的另一個集合,能返回被查詢的左邊集合的關聯鍵位的函數,能返回被聯結的右邊集合的關聯鍵位的函數,根據前兩個函數的參數能創建新結果集的函數 ) 

var query = publishers.GroupJoin(books, p => p.ID, b => b.PublisherID, (publish, bookList) => new
{
    pubID = publish.ID,
    pubName = publish.Name,
    bookAllName = !bookList.Any()==true?"無":string.Join(",",bookList.Select(b=>b.Name))
});
  

9-2.外連接:鍵位不相等時,保證左邊可以返回,右邊則以null填充(left join)

使用GroupJoin 方法,利用投影和DefaultIfEmpty方法可實現左外連接。

 
 
//適用於1對1、1對多關系,類似於sql的left join,無關聯的記錄時,滿足左邊記錄的輸出
var query = publishers.GroupJoin(books, p => p.ID, b => b.PublisherID, (publish, bookList) => new { publish.ID, publish.Name, bookList }).
    SelectMany(x => x.bookList.DefaultIfEmpty(), (publish, book) => new
    {
        pubID = publish.ID,
        pubName = publish.Name,
        bookName = book == null ? "無" : book.Name
    });
 

左聯結的表達式版本

var list = from b in books
            join p in publishers
            on b.PublisherID equals p.ID
            into newTable
            from x in newTable.DefaultIfEmpty( )
            select new
            {
                ID = b.ID,
                Name = b.Name,
                pubName = x==null? "null":x.Name
            };

大部分情況下可能會使用GroupJoin方法,但是假如關鍵鍵位本身是必填的,那么使用Join方法就可以了,因為關聯鍵位是必填,則不會存在關聯到空數據的情況,也就沒必要使用GroupJoin。

10.全連接

在sql中存在全連接的概念,它是指兩張或N張表無論左或右出現了未匹配上的行時,都以null填充。在linq中並不支持全連接,如果有這種需求,可以考慮先得到兩個結果集,最后再UNION即可。

11.交叉連接

也稱笛卡爾積查詢,可使用linq表達式寫個簡單的例子:

List < char >  aList  =  new  List < char >
{
     'A' , 'B' , 'C'
};

List < char >  bList  =  new  List < char >
{
     'x' , 'y'
};

var  query  =
     from  a  in  aList
     from  b  in  bList
     select  new  {  a ,  b  };

foreach  ( var  item  in  query )
{
     Console . WriteLine ( $" { item . a } 與上 { item . b } " );
}

12.串聯

操作符:Concat ( )

將5另一個序列與當前序列合並為一個序列

13.分區

操作符:Skip ( ) | SkipWhile ( ) | Take ( ) | TakeWhile ( )

Skip():跳過集合中指定個數的項,返回剩下的項。

SkipWhile():返回滿足條件的項。

Take():從索引起始位置開始提取指定個數的項。

TakeWhile():提取滿足條件的項

14.生成

操作符:DefaultIfEmpty ( ) 

DefaultIfEmpty() :用於當集合為空時為集合提供帶有默認值的一個項

15.判斷

操作符:All ( ) | Any ( ) | Contains ( )

All() :集合中所有項都滿足條件嗎?

Any():集合至少包含了一個項嗎?

Contains():集合包含了參數指定的項嗎?

16.比較

操作符:SequenceEqual ( )
逐一比較兩個集合中的項是否相等,如果相等則兩個集合被視為相等。  

LINQ To Object

查詢泛型集合

所有的LINQ操作符(即擴展方法)均定義在System.Linq.Enumerable靜態類中,為所有實現了IEnumerable<T>的強類型集合擴展出了LINQ查詢方法。所以數組、泛型集合都可以使用LINQ查詢。

object[] objArray = { "sam", 32, "beijingStreet", false, 'a' };
var records = objArray.Select(m => m.GetType().FullName).OrderBy(t => t);
ObjectDumper.Write(records);

Book[] books =
{
    new Book{  Title="萬有引力之虹", Isbn="993748928", PageCount=300 },
    new Book{  Title="解體概要", Isbn="325757665", PageCount=500 },
    new Book{  Title="寂靜的春天", Isbn="229911000", PageCount=200 }
};

var records = books.Where(b => b.Isbn.Contains("9")).Select(b => b.Title);
ObjectDumper.Write(records);

List<Book> bList = new List<Book>
{
    new Book{  Title="萬有引力之虹", Isbn="993748928", PageCount=300 },
    new Book{  Title="解體概要", Isbn="325757665", PageCount=500 },
    new Book{  Title="寂靜的春天", Isbn="229911000", PageCount=200 }
};
var records = bList.Where(b => b.Isbn.Contains("9")).Select(b => b.Title);
ObjectDumper.Write(records);

Dictionary<string, int> bNary = new Dictionary<string, int>
{
    { "sam", 12 },
    { "corz", 22 },
    { "korn", 53 },
};

var records = bNary.Where(o => o.Value > 20);
ObjectDumper.Write(records);
View Code

查詢非泛型集合

只需要使用Cast<T>將非泛型集合轉換為泛型即可。 

//存進ArrayList的元素都是object弱類型
ArrayList list = new ArrayList()
{
    new Book{ Title="寂靜的春天"},
    new Book{ Title="萬有引力之虹"},
    new Book{ Title="解體概要"}
};

var records = list.Cast<Book>().Select(b => new { bName= b.Title });
View Code

參數化查詢

可在查詢中使用變量,還可以動態構造查詢,比如定義一個函數,函數以Func泛型委托做參數,通過條件測試傳遞不同的Lambda表達式。

<body>
    <select name="combobox">
        <option value="0">查詢標題</option>
        <option value="1">查詢出版社</option>
    </select>
    <div> 
        @ViewData["show"]
    </div>
</body>
View Code
[HttpPost]
public ActionResult Index(string combobox)
{
    string recordsJson = string.Empty;
    switch (combobox)
    {
        case "0":
            recordsJson=ComboboxLambda(b => b.Title);
                break;
        case "1":
            recordsJson=ComboboxLambda(b=>b.Publisher.Name);
            break;                
    }
    ViewData["show"= recordsJson;
    return View();
}

[NonAction]
public string ComboboxLambda<T>(Func<Book,T> selector)
{
    var records = SampleData.books.Select(selector);
    return JsonConvert.SerializeObject(records);
}
View Code

LINQ查詢是一系列的鏈式操作,所以完全可以根據條件動態增加查詢,所以以下代碼並不會發生覆蓋而是追加。

[HttpPost]
public ActionResult Index(int maxPage, string orderByTitle)
{

    IEnumerable<Book> books = SampleData.books;
    if (maxPage != 0)
    {
        books = books.Where(b => b.PageCount > maxPage);
    }
    if (!string.IsNullOrEmpty(orderByTitle))
    {
        books = books.OrderBy(b => b.Title);
    }
    books = books.Select(b => b); //查詢會在符合條件的判斷中形成鏈式操作

    ViewData["show"= JsonConvert.SerializeObject(books);
    return View();
}
View Code

讀取文件

一個txt文件存儲了圖書信息,為StreamReder添加一個擴展方法用於獲取所有行,再通過LINQ查詢將數據打包。 

public static class StreamRederExtention
{
    /// <summary>
    /// 讀取每一行
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static IEnumerable<string> Lines(this StreamReader source)
    {
        string line;
        while (!string.IsNullOrEmpty(line = source.ReadLine()))
        {
            yield return line;
        }
    }
}
View Code
public ActionResult Index()
{
    string filePath = Server.MapPath("~/bookMessage.txt");
    StreamReader reader = new StreamReader(filePath);
    using (reader)
    {
        var bookMsg = reader.Lines()
        .Where(line => !line.StartsWith("#")) //過濾掉首行
        .Select(line => line.Split(','))
        .Select(part => new
        {
            Isnb = part[0],
            title = part[1],
            publisher = part[2],
            author = part[3].Split(';').Select(authorPerson => authorPerson)
        });

        ViewData["show"= JsonConvert.SerializeObject(bookMsg);
    }
    return View();
}
View Code

 

LINQ To XML

XObject類

表示XML節點的抽象基類
AddAnnotation(object)
//添加注釋節點。
Annotation(Type)
//獲取第一個注釋節點,類似行為的有Annotation<T>()、Annotations(Type)、Annotations<T>()。
方法

XNode類

表示項、注釋、 文檔類型、處理指令、文本節點。節點可以是項節點、屬性節點、文本節點。 

AddAfterSelf(object)
//緊跟在此節點之后添加指定的內容
AddBeforeSelf(object)
//緊跟在此節點之前添加指定的內容
Remove()
//從父節點總移除自身
Ancestors()
//獲取當前節點的祖先元素節點集合
ElementsAfterSelf()
//獲取當前節點后面的兄弟元素節點集合
ElementsBeforeSelf()
//獲取當前節點的前面的兄弟元素節點集合
NodesAfterSelf()
//獲取當前節點后面的兄弟元素節點集合
NodesBeforeSelf()
//獲取當前節點前面的兄弟元素節點集合
方法

XContainer類

表示可包含其他節點的節點。
Nodes()
//獲取當前節點包含的所有子節點集合
Elements()
//獲取當前節點包含的所有子元素節點集合
Elements(XName)
//獲取當前節點包含的所有能匹配參數指定的XName的子元素節點集合
Element(XName)
//獲取當前節點包含的能匹配參數指定的XName的第一個子元素節點
Descendants()
//獲取當前節點包含的所有子代元素節點集合,即后代元素節點集合
DescendantNodes()
//獲取當前節點包含的所有子代節點集合,即后代節點集合
Descendants(XName)
//獲取當前節點包含的所有能匹配參數指定的XName的子代元素節點集合,即后代元素節點集合
Add(object)
//添加子節
AddFirst(object)
//將參數指定的節點添加為作為自身包含的第一個子節
RemoveNodes()
//刪除自身的所有子節
ReplaceNodes(object)
//將自身包含的所有子節替換為參數指定的節點
ReplaceWith(object)
//將自身替換為參數指定的節點
方法

XElement類

表示XML項節點。  

XElement(XNameString, String | XElement | XAttribute | XProcessingInstruction | XComment | IEnumerable )
//XElement的構造函數,用於創建XML元素節點,參數2如果是String則成為元素節點包含的文本節點,否則可同時創建出后代元素節點
XNameString:元素節點名稱的字符表示或XName對象

//示例1:使用函數式鏈式操作創建XML元素節點
XElement books = new XElement("books",
    new XElement("book",
        new XElement("author""寒食"),
        new XElement("author""寒食")
    )
);
 
//示例2:調用函數創建XML元素節點
XElement book = new XElement("book");
book.Add(new XElement("author","寒食"));
book.Add(new XElement("author""無垠"));
XElement books = new XElement("books");
books.Add(book);
 
//示例3:創建命名空間
XElement books = new XElement("{http://google.com/}books",
    new XElement("book",
        new XElement("author""寒食"),
        new XElement("author""寒食")
    )
);

//示例4:創建命名空間前綴
XElement books = new XElement("books",
        new XElement("book",
            new XAttribute(XNamespace.Xmlns + "1", ns),
            new XElement("author""寒食"),
            new XElement("author""寒食")
        )
);


Add(XElement)
//添加子元素節點
Save()
//保存XML文檔
WriteTo(XmlWriter)
//將元素節點寫入XmlWriter中
Attribute(XName)
//獲取屬性節點集合
Attribute(XName)
//根據指定的屬性節點名獲取屬性節點值
AncestorsAndSelf()
//獲取自身並包括其祖先元素節點集合
DescendantsAndSelf()
//獲取自身並包括其后代元素節點集合
RemoveAll()
//移除所有后代
RemoveAttributes()
//移除自身的所有屬性節點
RemoveAnnotations(Type)
//移除自身的所有注釋節點
SetAttributeValue(XNameObject)
//根據指定的屬性節點名設置屬性節點值
SetElementValue(XNameObject)
//根據指定的屬性節點名設置子元素節點的屬性節點值

//==========靜態方法==========
load(PathLoadOptions)
//加載XML文檔
//LoadOptions:可選的枚舉,可能的值有:
//None:不保留無意義的空白
//PreserveWhitespace:保留無意義的空白
//SetBaseUri:保留URI信息
//SetLineInfo:保留請求的行信息
//示例:
try
{
    XElement x = XElement.Load(Server.MapPath("/books.xml"), LoadOptions.PreserveWhitespace); // 可從URL、磁盤路徑加載
}


Parse()
//從XML字符串解析出XElement
//示例:
XElement x = XElement.Parse(@"               
    <book>
        <title>LINQ in Action</title>
        <author>Fabrice Marguerie</author>
        <author>Steve Eichert</author>
        <author>Jim Wooley</author>
        <publisher>Manning</publisher>
        <rating>4</rating>
    </book>    
"
);
方法

XDocument類

表示XML文檔,與XElement一樣,它們都提供Load()、Parse()、Save()和WriteTo()方法,但此類可以包含更多信息,如包含一個根項的XElement、XML聲明、XML文檔類型和XML處理指令。如果使用函數式鏈式操作創建XML項,則使用XDocument與使用XElement創建XML項的語法格式是完全一樣的。 

//XDocument比XElement多了以下內容,其它則是一樣的
XDocument doc = new XDocument("books",
    new XDeclaration("1.0""utf-8""yes"), // 表示XML文檔版本、字符編碼、是否是獨立信息的節點
    new XProcessingInstruction("XML-sylesheet""friendly-rss.xsl"),  // 表示XML文檔處理指令的節點,此處為XML文檔配置了一個樣式表用以格式化XML文檔以便在瀏覽器以友好的界面顯示XML數據
    new XDocumentType("HTML""-//w3c//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict/dtd"null// 表示XML文檔類型聲明的節點           
    )
);
View Code

XAttribute類 

Remove()
//從父元素節點中移除自身

//==========屬性==========
Parent
//獲取父元素節點
方法

XName

表示項的名字或項屬性的名字

讀取XML的簡單示例  

<?xml version="1.0" encoding="utf-8" ?>
<category name="知識">
          <childcategory name="自然">
                    <books>
                              <book>寂靜的春天</book>
                              <book>自然之力</book>
                    </books>
          </childcategory>
          <childcategory name="宗教">
                    <books>
                              <book>佛陀的證悟</book>
                    </books>
          </childcategory>
          <childcategory name="社會">
                    <books>
                              <book>智利天下寫春秋</book>
                              <book>文明的解析</book>
                    </books>
          </childcategory>
</category>
XML
public class DefaultController : Controller
{
    public ActionResult Index()
    {
        XElement root = XElement.Load(Server.MapPath("~/books.xml"), LoadOptions.PreserveWhitespace);
        var childcategory = root.Element("childcategory"); // 獲取第一個childcategory元素節點
        var childcategoryAttri = childcategory.Attribute("name"); // 獲取第一個childcategory的name屬性節點
        IEnumerable<XElement> books = childcategory.Element("books").Elements("book"); // 獲取第一個childcategory所包含的第一個books元素節點包含的所有book元素節點
        ViewData["childcategory"= childcategory;
        ViewData["childcategoryAttri"= childcategoryAttri;
        ViewData["childs"= string.Join("", books.ToList());
        // 輸出XElement時會將它所包含的所有后代一並輸出
        // 輸出 IEnumerable<XElement>元素節點集合時必須ToList后與字符相連
        return View();
    }
}
讀取XML
// 獲取每個book元素節點,從集合中過濾出其包含的文本為寂靜的春天的元素節點
// (string)XElement可將元素節點轉為其包含的文本 這樣就可以執行一次判斷 前提是該元素節點和包含的文本是在一行顯示的
ViewData["show"=string.Join("",root.Descendants("book").Where(book => (string)book == "寂靜的春天"));
獲取包含指定文本的項節點

 

LINQ To Entity

當查詢數據庫表的結果集時,比如在調用Where方法返回的結果集上再調用其它Linq擴展方法會拋出異常,這可能是因為數據庫結果集已經返回,應將其ToList后轉化為內存中的集合對象,然后才能使用Linq to Object的方法進行進一步的過濾。否則鏈式的Linq方法會被當做是在數據庫執行查詢
MyEntity.DrugsErpEntities dbcontext = new MyEntity.DrugsErpEntities();
//如果Where后沒有ToList,而是直接調用Select方法將會引發異常
var list = dbcontext.UserManagers.Where(user => user.LoginName == "a").ToList().Select((p, i) => new { u = p.LoginName, id = p.UserId, index = i }).ToList();

DbSet<TEntity> 類

此類實現了IQueryable<T>接口,一系列的擴展方法可用於Linq查詢。
Include()
//使用預加載策略建立左聯結查詢,模型必須具有導航屬性,此方法根據導航屬性來查詢與主實體所關聯的其它實體,此方法會立即開啟查詢
MusicStoreDB db = new MusicStoreDB();
var albums = db.Albums.Include(a => a.Artist).Include(a => a.Genre); //Artist和Genre都是Album的導航屬性,不需要像Join方法那般指定主外鍵,只需要指定導航屬性即可建立左聯結查詢

//批量刪除記錄
//示例:
public ContentResult DelBtch(string IDs) //IDs,需要被刪除的記錄的ID,如"1,15,32"
{
    try
    {
        var records = DBContext.TbRights.RemoveRange(DBContext.TbRights.Where(t => IDs.Contains(t.TbRightId.ToString())));
        DBContext.SaveChanges();
        DBContext.Dispose();
        return Content("{msg:'操作成功'}");
    }
    catch
    {
        return Content("{msg:'操作失敗'}");
    }
}

AddRange()
//批量添加記錄

AddOrUpdate(TEntity[] )
//System.Data.Entity.Migrations命名空間中為DbSet<TEntity>定義的擴展,自動識別插入或更新,當實體主鍵在數據庫表中不存在時視為插入,當實體主鍵在數據庫表中存在時視為更新。在參數數組包含的實體的主鍵在數據庫表中既有存在的也有不存在的情況時,沒關系,該更新該插入,此方法自動幫你完成。
//注意:如果某個字段為空,也將插入或更新到數據庫,如果數據庫設置了不允許空則引發異常
//示例:
var dirtyRecs = JsonConvert.DeserializeObject<List<TbRight>>(dirtyRecords); //包含了需要更新和需要插入的實體         
DBContext.TbRights.AddOrUpdate(dirtyRecs.ToArray() ); //同時執行了插入和更新

插入數據前檢測數據庫是否已經具有一條完全相同的記錄

#region 員工權限批量插入
/*
xTbUserRight:需要插入數據庫的記錄的Json表示
userIDs:以逗號分隔的員工ID的字符表示
checkValues:以逗號分隔的權限ID的字符表示
查詢員工權限表時先對照userIDs和checkValues查出相同的記錄,如果存在這樣的記錄則先刪除它們
最后將xTbUserRight轉化為的實體記錄集合插入數據庫
*/

public ContentResult AddTbUserRightBtch( string xTbUserRight , string xUserIDs , string xCheckValues )
{
    int [ ] userIDs = Array.ConvertAll ( xUserIDs.Split ( ',' ) , o => int.Parse ( o ) );
    int [ ] checkValues = Array.ConvertAll ( xCheckValues.Split ( ',' ) , o => int.Parse ( o ) );
    var newRecords = JsonConvert.DeserializeObject<List<TbUserRight>> ( xTbUserRight );
    var oldRecords = DBContext.TbUserRights.Where ( t => userIDs.Contains ( t.UserId ) && checkValues.Contains ( t.TbRightId ) );
    if ( oldRecords.Count ( ) != 0 )
    {
        DBContext.TbUserRights.RemoveRange ( oldRecords );
    }
    DBContext.TbUserRights.AddRange ( newRecords );
    DBContext.SaveChanges ( );
    return Content ( "{msg:'操作成功'}" );
}
#endregion
View Code

根據某個字段的數據集合查詢出另一張表中對應的記錄

int UserId = int.Parse ( xUserId );
//UserAuthority是用戶權限表實體,它存儲三個字段,主鍵、用戶ID和用戶權限ID,一個用戶具有多個權限ID,結構如下:
//PrimaryID      UserID      AuthorityID
//   1                   32             20
//   2                   32             26
//Authority是權限表實體,它存儲兩個字段,主鍵和權限名稱,結構如下:
//AuthorityID   AuthorityName
//    32             ERP登錄權限
//現在要查詢出當前用戶所具有的所有權限的記錄
var userAuthorityID = DBContext.UserAuthority.Where ( t => t.UserID == UserId ).Select ( t => t.AuthorityID ).ToList ( ); //獲取用戶權限ID集合        
var userInclud = DBContext.Authority.Where ( t => userAuthorityID.Contains ( t.AuthorityID ) ); //獲取用戶具有的權限記錄
var userNotInclud = DBContext.Authority.Where ( t => !userAuthorityID.Contains ( t.AuthorityID ) ); //獲取用戶不具有的權限記錄
View Code

 


免責聲明!

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



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