首页 > 代码库 > ASP.NET - LINQ 语言集成查询

ASP.NET - LINQ 语言集成查询

LINQ(Language Integrated Query)
LINQ语言集成查询是一组用于C#语言的扩展。它允许编写C#代码对数据集进行查询,这等同于使用数据库查询语句,这样程序员不必掌握数据库查询语句而是使用Linq就能完成相同的查询任务。而传统数据查询的弱点很多,比如执行简单查询也需要冗长的操作代码,查询语句是字符串格式,无法让编译器执行检查错误及早提示,查询不是强类型,查询参数容易写错,查询结果没有真正面向对象,每次查询取结果还得事先知道列名或列索引,不使用抽象工厂的前提下,查询语句是定死的,只能应用于一种数据库。 即使使用抽象工厂,代码量也巨大 

技术分享

 

Lambda表达式

格式

Lambda表达式比函数有着更为简洁的语法格式。在某些情况下使用Lamnbda就可以替代函数,让代码变得更简洁易读。而定义Lambda表达式与定义函数其实差别不大,而且还做到了简化。 

=> e.Name == "sam" 
定义函数你得声明函数的权限修饰符、形态修饰符和参数列表、返回类型等,而Lambda就这么一段代码就做完了函数的工作
e:参数
=>:操作符,可描述执行后面的逻辑……操作符后面的则是表达式,也可以是语句块。无论是表达式还是语句块都必须返回一个结果,哪怕是返回void

示例

(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表达式的参数和返回值类型必须符合委托的参数和返回类型。 

技术分享
class Program
{
    static void ShowTime(Func<DateTime> d) //接收一个泛型委托
    {
        string time= d().ToString(); 
        Console.WriteLine(time);
    }
    static void Main(string[] args)
    {
        //定义一个返回时间的Lambda表达式
        //因为Lambda表达式就是函数的简洁版,所以它也可以是委托类型,以便将它当做参数传递
        //使用内置Func<T>泛型委托是因为这种委托被设计成可以有返回值,这符合Lambda的需求,它返回一个时间
        //使用Action委托就不可以了,因为Action委托被设计成不能有返回值,而Lambda又需要返回一个值
        Func<DateTime> GetDateTime = () => DateTime.Now;
        ShowTime(GetDateTime);
    }
}
View Code

 

LINQ的扩展方法(LINQ操作符)

LINQ对实现了IEnumrable<T>接口的泛型集合类型扩展出了一系列的方法,这些方法提供了排序、提取等操作,也将这些扩展方法称为LINQ操作符。

技术分享
1.OrderByDescending()
//OrderByDescending是泛型集合的扩展方法,用于对泛型集合元素进行降序排序。此方法要求一个Func<T, Key> 的泛型委托,它会自动迭代集合元素,每迭代一次将调用泛型委托,并把集合中的一条记录作为参数传递给委托(Lambda),OrderByDescending()方法期待该委托返回一个值作为键,以键进行降序排序

2.Take(int count)
//用于从结果集中提取指定记录条数

3.Sum(Fun < T, decimal > selector)
//用于对泛型集合元素进行求和。
View Code

 

LINQ延迟查询机制

LINQ查询操作符并不会立即执行,它总是在查询结果集真正被使用的时候才会开启查询。 而且每次只讲迭代的当前元素存入内存中处理。这就降低了内存占用率。

技术分享
static double Square(double n)
{
    Console.WriteLine("执行查询:" + n);
    return n;
}
static void Main(string[] args)
{
    double[] array = { 100, 5, 8,900 };
    //延迟查询机制保证下面的查询并赋值的操作不会执行,直到records真正被使用的时候才会开启查询
    var records = from n in array
                    select Square(n);

    //想象一下假设有上千条数据需要被查询出来放进records,可是程序并不知道你在后来有没有使用过records
    //如果你根本不使用records,那么查询就没有意义,如果你真的没有使用它,那么上千条记录读进内存就是在浪费资源
    //所以查询并不会真的开启直到你确实使用了records时,查询才会开始运作

    //records被迭代时才会自动打开查询
    foreach (var r in records) { }
}
测试延迟查询

但警惕聚集计算操作符和某些需要一次性统计所有集合的操作符如OrderBy、Reverse、ToList、ToArray、ToDictionary,它们都会破坏延迟查询,一次性将所有集合元素存入内存以便处理。这就是为什么很多人说LINQ查询的性能不高的根本原因。

 

LINQ查询表达式

LINQ查询表达式类似于SQL查询语句,但个人不喜欢它的书写格式。它是由from、join group by等句子组成的LINQ查询表达式,而LINQ查询表达式与直接使用LINQ扩展方法执行查询在语法结构上是不一样的,我认为后者更易于阅读,所以此处略过。

 

LINQ表达式树(System.Linq.Expressions.Expression

表达式树是将代码以一种抽象的方式表示成一棵直观的对象树,树中每个节点就代表了一个表达式。表达式树不是可执行代码,它只是一种数据结构。但树能存储表达式的信息,以便根据不同的数据源构造出不同的查询方式、在运行期间分析表达式树,并进行查询语句的优化。

表达式树的创建

根据表达式的不同类型,可创建不同的表达式树。比如单个值的表达式使用ConstantExpression创建,而两个值的运算的表达式则由BinaryExpression创建。Lambda表达式树又由LambdaExpression创建,它们都派生自Expression基类。其它类可参考MSDN DOC。 

单值表达式树

技术分享
ConstantExpression aa = ConstantExpression.Constant(6); // 创建一棵表达式树。单个值本身就是一个表达式,所以此处就创建了一棵表达式树,树上只有一个节点,节点又有其属性
ExpressionType nodeType = aa.NodeType; // print Contand 获取该节点在树上的类型
Type structType = aa.Type; // print Int32 获取该节点的真正类型
单值表达式树

技术分享

求值表达式树

技术分享
ConstantExpression aa = ConstantExpression.Constant(6);
ConstantExpression bb = ConstantExpression.Constant(6);
BinaryExpression sum = BinaryExpression.Add(aa, bb); // 创建一棵求值运算的表达式树
ExpressionType nodeType=sum.NodeType; // print add 获取该节点在树上的类型
Type structType = sum.Type; // print Int32 获取该节点的真正类型
求值表达式树
技术分享
技术分享

Lambda表达式树

Lambda是一个匿名函数,所以它自身是一个表达式,而其每个参数、方法体代码都是表达式。Lambda表达式树是由LambdaExpression创建,但构造这样的树要使用Expression.Lambda()方法,而通常可能使用Expression<Func<TSource>>来创建Lambda表达式树。

// Lambda可当做委托实例使用
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource> predicate);
//Lambda可当做表达式树
//IQueryable<TSource>泛型接口类型的泛型集合实现了IEnumerable<TSource>相同的LINQ扩展方法,但它的第二个参数是能构造表达式树的Expression<Func<TSource>>类型
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource>> predicate); 
 
技术分享
Expression<Func<int, int, int>> lambdaTree = (x, y) => x + y;// 创建一个Lambda表达式树
ParameterExpression paramNode1 = lambdaTree.Parameters[0]; // 获取参数
ParameterExpression paramNode2 = lambdaTree.Parameters[1]; // 获取参数       
BinaryExpression body = lambdaTree.Body as BinaryExpression; // 获取表达式主体代码块,此处是一个加法运算的表达式x+y
ParameterExpression leftNode = body.Left as ParameterExpression; // 获取表达式主体代码左边的表达式 此处即x
ParameterExpression rightNode = body.Right as ParameterExpression; // 获取表达式主体代码右边的表达式 此处即y            
Func<int, int, int> GetLambda = lambdaTree.Compile(); //从表达式树中取出Lambda表达式以便执行
int result = GetLambda(1, 2);
ExpressionType nodeType = lambdaTree.NodeType; // print Lambda
Type type = lambdaTree.Type; //print Func[Int,Int,Int]
Lambda表达式树
技术分享
技术分享技术分享

 

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操作符

1.过滤

操作符:Where()

用于对泛型集合元素进行筛选。此方法要求一个Fun<T,Key>的泛型委托,它会自动迭代集合元素,每迭代一次将调用泛型委托,并把集合中的一条记录作为参数传递给委托(Lambda),Where()方法期待该委托返回一个布尔值,为真时,将对象保留,否则排除。此方法所要求的委托还可以有第三个参数:Index,表示当前对象在集合中的索引号。如

books.Where((b, index) => b.Title.Length > 10 && index > 1)

2.投影

2-1.操作符:Select()

指定需要查询的元素的成员以便将它们组成新的结果集返回。

操作符:SelectMany()

指定需要查询的元素的集合成员以便将它们组成新的结果集返回。主要应用于一个对象的属性是一个子集合时,将取出子集合并组成新的结果集返回。此方法所要求的委托还可以有第三个参数:Index,表示当前对象在集合中的索引号。

//过滤出图书标题大于10的图书并在结果集中查询出图书作者的各项信息
var records = SampleData.books.Where(b => b.Title.Length > 10 ).SelectMany( b => b.Authors ); //Authors是book的属性,是一个包含n个属性的集合

3.去重

操作符:Distinct()

去除集合中的重复元素并返回去除后的结果集。此方法内部维护了一个测试相等性的比较器,如果元素是结构或字符串类型,则比较所有元素的值以确认元素是否相等,如果元素是引用类型,则比较所有元素在堆上的引用地址以确认元素是否相等。可是多半在执行查询时查询的是对象,对象多半都是不相等的堆引用,所以根本没法去重,别急!此方法还有重载,重载版可实现自定义的测试相等性的规则,重载版需要第二参数,参数是一个IEqualityComparer<T>泛型接口,也即你必须实现这个接口从而实现自定义的比较器。

技术分享
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
{
    /// <summary>
    /// 常用的方法
    /// </summary>
    public static class LinqHelper<T>
    {
        #region 公共方法

        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);
        }

        #endregion

        #region Nested type: CommonEqualityComparer

        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)
            { }

            #region IEqualityComparer<T> Members

            public bool Equals(T x, T y)
            {
                return _comparer.Equals(_keySelector(x), _keySelector(y));
            }

            public int GetHashCode(T obj)
            {
                return _comparer.GetHashCode(_keySelector(obj));
            }

            #endregion
        }

        #endregion
    }
}
自定义比较器

有了以上的自定义的比较器,现在可以对元素的某个成员进行去重了。

var records = SampleData.Authors.Distinct(LinqHelper<Author>.CreateComparer<string>(a=>a.FirstName)); 

4.转换

操作符:ToArray() | ToList() | ToDictionary() | ToCast<T>()

 

前三个操作符将立即执行查询并将结果集转换为数组、泛型集合、泛型字典集合。LINQ有延迟查询机制,转换方法可立即开启查询。最后一个将非泛型集合转换为泛型集合,以此可查询非泛型集合

5.聚合

操作符:Sum() | Count() | Min() | Max()

所见即所得

6.排序

操作符:OrderBy() | OrderByDescending() | ThenBy() | ThenByDescending()

7.嵌套

所谓嵌套即在一个操作符的查询中嵌套了另一个操作符。 

//查询出版社并同时查询图书列表,找到与当前出版社匹配的图书
var records = SampleData.Publishers.Select(p => p).Select(p => new { publisher = p.Name, books = SampleData.books.Where(b => b.Publisher == p).Select(b => b.Title)  });

8.分组

操作符:GroupBy()

将集合中的元素按委托指定的规则进行分组。返回一个IEnumerable<IGrouping<TKeyTSource>>的泛型字典集合,该集合存储的是每个组的元素集合和一个Key,Key则是按此元素进行分组的元素。

//将出版社相同的图书分为一组。出版社本身是一个对象,被作为分组的依据,group即是组对象,它存储了分组的元素集合,通过Key能得到出版社本身
var records = SampleData.books.GroupBy(b => b.Publisher).Select(group => new { books = group.Select(b=>b.Title), publisher = group.Key.Name });

多条件分组

即同时满足多个条件的相同元素被分为一组。

//按多个条件分组
//此处查询图书,按图书出版社和图书类别进行分组,
//将这两个条件封装到一个匿名对象中,此时GroupBy会迭代每本图书并对照出版社和图书类别是否与另一本书是完全一样的,完全一样则会分成一组,不一样则单独分一组
//GroupBy返回的并不是匿名对象,而是分组后的图书信息集合groups
var records = SampleData.books.GroupBy(b => new { b.Publisher, b.Subject });

//如果想在集合中显示分组标题则可以在组上继续迭代以便在每个组中增加标题
//迭代groups,取出每一个组(group),通过Key可以得到封装分组条件的匿名对象
var records = SampleData.books.GroupBy(b => new { b.Publisher, b.Subject }).Select(group => new {
        pName = group.Key.Publisher.Name,
        subject = group.Key.Subject.Name,
        bName = group.Select(b=>b.Title)
    });

9.联结

9-1.内联结:只返回匹配的结果集。

操作符:Join(联结的另一个集合,能返回被查询的左边集合的关联键位的函数,能返回被联结的右边集合的关联键位的函数,根据前两个函数的参数能创建新结果集的函数)

//内联结查询
var records = SampleData.Publishers.Join( SampleData.books, p => p.Name, b => b.Publisher.Name, ( p , b ) => new { publisherName= p.Name , bookTitle = b.Title } );
//将查询结果装入了一个匿名对象,也可以不指定匿名对象的字段名称,这样,则会将查询的字段作为匿名对象的字段
( p , b ) => new {  p.Name , b.Title };

9-1.左联结(组联结):不满足匹配时,保证左边集合数据的完整性,右边集合以null填充

操作符:GroupJoin(联结的另一个集合,能返回被查询的左边集合的关联键位的函数,能返回被联结的右边集合的关联键位的函数,根据前两个函数的参数能创建新结果集的函数) 

//左外联结:lanmbda表达式版本必须使用GroupJoin,实际上是将两个集合以左边集合的键为组头将数据进行了分组,
//所以下面的p是出版社(组头),books是分组后的图书集合(分组后的图书集合注册在组头下面)
//测试books.Count()==0而不是测试books==null是因为books已经被初始化,所以不可能是null
var results = SampleData.Publishers
.GroupJoin(SampleData.books, p => p.Name, b => b.Publisher.Name, (p, books) =>
new { 出版社=p.Name,图书= books.Count()==0?"没有图书":string.Join(",",books) });

LINQ表达式版本的左外联结

LINQ操作符其实没有左联结的说法,所使用的就是组联结而已,而LINQ表达式可以实现真正的左联结。 

技术分享
//左联结查询
var records = from p in SampleData.Publishers
                join b in SampleData.books
                on p.Name equals b.Publisher.Name
                into newTables
                from b in newTables.DefaultIfEmpty()
                select new { pName = p.Name, bName = b == default(Book) ? "没有图书" : b.Title };
View Code

9-2.右联结:要先满足哪个集合就把该集合写在左边就行了

10.略过

操作符:Skip()

跳过集合中指定个数的元素,返回剩下的元素。

11.提取

操作符:Take()

从索引起始位置开始提取指定个数的元素。

 

 

 

 

 

 

 

 

 

 

 

 

LINQ To SQL

让类使用Table和Column特性

System.Data.Linq.Mapping命名空间提供了为类添加特性,特性可以看成是对类、类成员字段的一种描述。当为类应用特性后,使用LINQ查询时,类将被映射为数据库的表,查询语句就可以支持强类型对象,同时数据库返回的结果也同样被映射为强类型的对象。特性Table应用于类,特性Column应用于字段。

技术分享
[Table(Name = "Employees")] //声明此类映射数据库的Employees表
public class Employee
{
    [Column(IsPrimaryKey =true,Name = "EmployeeID")] //声明ID映射EmployeeID列并且是主键
    public int ID { getset; }
    [Column(Name = "FirstName")] 
    public string Name { getset; }
    [Column]
    public string City { getset; } //与数据库表同名的字段只需要声明是Column
    [Column]
    public string Address { getset; }
}
View Code
技术分享
public class DefaultController : Controller
{
    public ActionResult Index()
    {          
        DataContext context = new DataContext("server=DESKTOP-D445L4U;database=Northwind;uid=sa;pwd=123456");
        var records = from e in context.GetTable<Employee>()
                      where e.ID % 2 == 0
                      select e;
        StringBuilder s = new StringBuilder();
        foreach(var record in records)
        {
            s.AppendFormat("<div>ID:{0}   Name:{1}   Address:{2}   City:{3}</div>", record.ID, record.Name, record.Address, record.City);
        }

        ViewData["records"= s.ToString();
        return View();
    }
}
View Code

 

 

ASP.NET - LINQ 语言集成查询