• C#3.0 LINQ详解


    LINQ,即Language Integrated Query。很多人熟悉SQL语言,用它来操作数据库非常方便。现在在C#3.0中也可以使用相同的语法来操作各种数据,包括数组,文件,数据库等。由于LINQ的东西比较多,准备分三次讲,第一次主要介绍一下LINQ,第二次主要介绍用LINQ操作数据库,第三次主要介绍用LINQ操作XML,第四次主要介绍LINQ的一些函数应用。有时间在写一些关于LINQ的专题。


    下面看一个例子,程序员就应该拿代码说话:

     class Program
        ...{
            static void Main(string[] args)
            ...{
                string[] names=...{"Everett", "Albert", "George", "Harris", "David" };
                var items=from name in names
                          where name.Length>=6
                          orderby name
                          select name.ToUpper();
                foreach(var item in items)
                    Console.WriteLine(item);
            }
        }
    是不是对上面的from,where,orderby等单词很熟悉,我们可以很轻松的像查询数据库一样查询某些数组。其实上述表达式等价于下面这样的写法:

         var items=names.Where(name=>name.Length>=6).OrderBy(name=>name).Select(name=>name.ToUpper());
     那么我们为什么能在数组上应用这些方法呢,本来数组上没有这些方法啊?回忆一下我们原来说过的内容,扩展方法,对,我们在这里看到扩展方法的用武之地了。而且我们也看到了Lambda表达式的应用使得语句更加简洁,易懂。这些扩展方法定义在哪里呢?我们可以在System.LINQ这个程序集里面看到这样的定义:

    namespace System.LINQ ...{
    public static class Enumerable ...{
    public static IEnumerable<T> Where<T>(
    this IEnumerable<T> source, Func<T, bool> predicate) ...{

    foreach (T item in source)
    if (predicate(item)
    )
    yield return item;
    }
    }
    }
    这里我们可以清晰的看到许多this关键字,这些正是扩展方法的标志。若果我们对Lambda表达式不是很熟悉的化,上面的LINQ语句还可以进一步转化成委托的形式。

    Func<string, bool> filter = delegate (string s) ...{return s.Length >=6;};

    Func<string, string> extract = delegate (string s) ...{return s; };

    Func<string, string> project = delegate (string s) ...{return s.ToUpper(); };

    var items = names.Where(filter) .OrderBy(extract) .Select(project);
    有一个问题,我们会注意到items前面是用var做它的类型,那么有没有提出这样的疑问,这个var到底是什么类型呢?我们仔细看一下那个System.LINQ程序集,就会发现扩转方法返回的是IEnumerable<T>类型,一个泛型接口,没错var就是这个泛型接口。还有一个问题就是当names满足什么条件时,我们可以应用LINQ表达式进行查询。这一点还要从那个程序集上仔细观察。我们会发现有一个source参数,根据扩展方法的语法我们知道这个source参数就是调用了这个方法的对象。那么我们可以推断出这个对象要能转换成IEnumerable<T>。那什么类型可以成功转换呢?很显然只要实现了IEnumerable<T>这个泛型接口的类型都可以转换。譬如数组类型,List<T>,等等。当我们还可以自己定义类型只要实现了这个接口就可以用LINQ对这个类型进行操作

    这一次只是简单的介绍一下LINQ,以后我们在具体的讲一些应用,不过从介绍中我们看到LINQ的一些强大功能

    LINQ TO SQL是LINQ技术在数据库方面的应用。数据库技术从OLEDB,ODBC到ADO,在到ADO.NET到现在的LINQ TO SQL,让程序员操作数据库越来越简单。 LINQ 的宗旨就是让查询无处不再,这当然要包括对数据库的查询。LINQ不仅仅可以对数据库进行查询,同样CUID(Create,Update,Insert,Delete)都可以实现,而且非常方便。

    下面逐一对查询以及增删改方面进行介绍

    要想查询数据库中表的数据,就要先建立对数据库表的映射,就像要想使用ADO.NET就需要先把数据库中的数据存到DataSet中,看一下代码

    /*注意,其中的[Table] 特性表示将Category类与数据库中名称为Category的表相对应。[Column] 特性则对应数据库中的列。*/
    //这里我不想详细进行说明,以后在写LINQ专题的时候专门对这些进行介绍。

    [Table(Name = "Category")]
    public class Category
    ...{
    [Column(IsPrimaryKey = true)]
    public string CategoryId;
    [Column]
    public string Name;
    [Column]
    public string Descn;
    }
    以上就是对数据表的一个映射。使用的数据库是SQL SERVER2000中自带的数据库Northwind。其实写过C#的人也应该能看懂上面的代码。建立好对表的映射之后,就可以对其进行相应的操作了。

    注意这里我们使用手工写数据表的映射,其实MS为我们提供了相应的自动化工具SqlMetal,大家可以使用这个工具自动产生对数据库表映射的代码,效率比较高,如果自己有什么需要改动的地方还可以对产生的代码进行更改。

    下面可以查询了

    DataContext db = new DataContext("Server=localhost;Database=northwind;Trust_connection=true");
    //这里的连接字符串根据自己的配置进行相应更改即可
    Table<Category> Categorys = db.GetTable<Category>();
    var result =
    from c in Categorys
    select c;

    //大家看到了DataContext就相当于ADO.NET中的Connection,但是它提供了更强大功能
    //其中result就相当于IEnumerable<Category>如果不知道为什么会推出这个来,可以参考我原来写的Lambda表达式那篇

    //下面可以输出result中的内容
    foreach(var c in result)
         Console.WriteLine("CategoryId={0},Name={1},Descn={2}",c.CategoryId,c.Name,c.Descn);

    /**//*这里对关于LINQ的智能感知说明一下。如果用的是VS2005+LINQ的插件建的LINQ工程,那么在VS里面没有对LINQ TO SQL的智能感知支持。也就是说当你在输出的时候,写c然后.将不会出现内容,但是你可以按照自己建的映射写出相应的字段,编译和运行没有问题。如果是用VS2008 也就是代号为“Orcas”建的LINQ工程,那么就有智能感知的支持。非常方面*/
    以上就是对数据库的查询,我们可以写出很复杂的查询来,其实在内部,LINQ会把你写的LINQ语句,转换成SQL语句送到数据库中去执行。然后返回相应的结果。如果大家想看转换后的SQL语句,可以在建立完连接之后加上这样一句:db.Log=Console.Out。这样程序将自动输出SQL语句和查询结果.这里只是简单的介绍了一下如何查询,我们知道表与表之间还有关系,这些复杂的东西我们以后在详细说明。

    下面再来看看如何更改

    //更改
    string id = “DOGS";
    var cat = db.Categories.Single(c => c. CategoryId == id);
    cat. Name = “New Dogs";
    //添加
    Product p = new Product ...{ …….};
    cat.Products.Add(p);
    //删除
    string id = “DOGS";
    var cat = db.Categories.Single(c => c. CategoryId == id);
    db.Categories.Remove(cat)
    //提交更改
    db.SubmitChanges();
    //注意, SubmiChanges() 完成了对象层到数据层的更改。也就说不进行提交,更改的结果不会存储到数据库中。
    以上只是对LINQ TO SQL进行了简单的介绍,其实它的内容还有很多。以后在写LINQ专题的时候详细说明。

    如果想更深入的学习LINQ TO SQL,大家可以到下面这个地址去下载视频看一下。点击进入>>

    我们知道关于XML,W3C有一套DOM模型,C#语言有一套在DOM模型下操作XML的类库。但是在LINQ出现以后,微软又重新做了一套关于XML的模型,而且操作起来同那套DOM模型没什么两样,但是更加的简单。

    下面看一下这套模型的图: 

    以上是一套新的类库。其中最核心的类就是XElement,不要看它的层次低,但是绝对是核心。还有一些其他特性与DOM模型不一样,其中之一就是XAttribute和XNode在同一个层次上,还有就是XDocument不再是必须的。其他不同点可以参考DOM模型自己比较。

    下面用代码对比一下DOM模型和LINQ模型操作XML的区别:

    //DOM模型

    XmlDocument doc = new XmlDocument();
    XmlElement name = doc.CreateElement("name");
    name.InnerText = "Patrick Hines";
    XmlElement phone1 = doc.CreateElement("phone");
    phone1.SetAttribute("type", "home");
    phone1.InnerText = "206-555-0144";       
    XmlElement phone2 = doc.CreateElement("phone");
    phone2.SetAttribute("type", "work");
    phone2.InnerText = "425-555-0145";       
    XmlElement street1 = doc.CreateElement("street1");       
    street1.InnerText = "123 Main St";
    XmlElement city = doc.CreateElement("city");
    city.InnerText = "Mercer Island";
    XmlElement state = doc.CreateElement("state");
    state.InnerText = "WA";
    XmlElement postal = doc.CreateElement("postal");
    postal.InnerText = "68042";
    XmlElement address = doc.CreateElement("address");
    address.AppendChild(street1);
    address.AppendChild(city);
    address.AppendChild(state);
    address.AppendChild(postal);
    XmlElement contact = doc.CreateElement("contact");
    contact.AppendChild(name);
    contact.AppendChild(phone1);
    contact.AppendChild(phone2);
    contact.AppendChild(address);
    XmlElement contacts = doc.CreateElement("contacts");
    contacts.AppendChild(contact);
    doc.AppendChild(contacts);
    //LINQ模型

    XElement contacts =
        new XElement("contacts",
            new XElement("contact",
                new XElement("name", "Patrick Hines"),
                new XElement("phone", "206-555-0144",
                    new XAttribute("type", "home")),
                new XElement("phone", "425-555-0145",
                    new XAttribute("type", "work")),
                new XElement("address",
                    new XElement("street1", "123 Main St"),
                    new XElement("city", "Mercer Island"),
                    new XElement("state", "WA"),
                    new XElement("postal", "68042")
                )
            )
        );

    从对比上我们也可以看出LINQ模型的简单性。我们还可以从LINQ模型上看出XElement的重要性。使用XElement不仅可以从头创建xml文件,还可以使用Load的方法从文件加载。还可以从数据库中取出所需元素,这就要用到LINQ TO SQL的东西了,同样可以从数组中取出元素。操作完成后可以使用Save方法进行保存。

    下面简单介绍一下增删查改XML。

    //查询
    foreach (c in contacts.Nodes()) ...{
    Console.WriteLine(c);
    }

    我们看到在输出XML元素的时候并不需要对每个元素进行强制的类型转换,这里C#编译器已经做了这些事情,它会在输出的时候调用每个元素的ToString()方法。

    //插入元素
    XElement mobilePhone = new XElement("phone", "206-555-0168");
    contact.Add(mobilePhone);


    这里只是很简单的演示一些操作,至于那些复杂的操作,只要DOM模型能实现的LINQ模型就一定能实现。插入的时候还可以使用AddAfterThis和AddBeforeThis等方法,提高效率。

    //删除元素
    contact.Element("phone").Remove();                                                      //删除某一具体元素
    contact.Elements("phone").Remove();                                                    //删除一组元素
    contacts.Element("contact").Element("address").RemoveContent();      //删除某一元素内容
    删除元素还可以使适用SetElement方法,把某一元素设置为null也就是删除了这元素。

    //修改元素
    contact.Element("phone").ReplaceContent("425-555-0155");    //这里是修改第一个phone元素的内容
    当然同样可以使用SetElement方法,这里才是它的用武之地。

    从上面简单的介绍我们可以清楚的看到,使用LINQ操作XML是多么的简单,这里使用的C#语法,如果要是使用VB.NET还会更简单。有一些方法在VB.NET中可以使用但是在C#中却没有。毕竟VB.NET是晚绑定语言,可以充分发挥它的优势。

    这次我们来看看LINQ提供的一些函数操作。下面看一下函数列表:

      List of Standard LINQ Query Operators Operator
     Lazy?
     Description
     
    Aggregate
     No
     Applies a function to a sequence, yielding a single value.
     
    All
     No
     Applies a function to a sequence to see if all elements satisfy the function.
     
    Any
     No
     Applies a function to a sequence to see if any element satisfies the function.
     
    Average
     No
     Computes the average of a numeric sequence.
     
    Cast
     Yes
     Yields the elements of a sequence type-casted to a given type.
     
    Concat
     Yes
     Yields the concatenation of two sequences S1 and S2.
     
    Contains
     No
     Searches a sequence to see if it contains a given element.
     
    Count
     No
     Counts the number of elements in a sequence, yielding an integer result.
     
    DefaultIfEmpty
     Yes
     Given a sequence S, yields S or a sequence with the default value if S is empty.
     
    Distinct
     Yes
     Returns a sequence with duplicates eliminated.
     
    ElementAt
     No
     Returns the ith element of a sequence.
     
    ElementAtOrDefault
     No
     Returns the ith element of a sequence, or the default value if sequence is empty.
     
    Empty
     Yes
     Yields an empty sequence.
     
    EqualAll
     No
     Compares two sequences for equality.
     
    Except
     Yes
     Given two sequences S1 and S2, returns the set difference S1 S2.
     
    First
     No
     Returns the first element of a sequence.
     
    FirstOrDefault
     No
     Returns the first element of a sequence, or the default value if sequence is empty.
     
    Fold
     No
     Obsolete, see Aggregate.
     
    GroupBy
     Yes
     Groups the elements of a sequence by key.
     
    GroupJoin
     Yes
     Performs a join of two sequences S1 and S2, yielding a hierarchical result.
     
    Intersect
     Yes
     Given two sequences S1 and S2, returns the set intersection of S1 and S2.
     
    Join
     Yes
     Performs a traditional inner equijoin of two sequences S1 and S2.
     
    Last
     No
     Returns the last element of a sequence.
     
    LastOrDefault
     No
     Returns the last element of a sequence, or the default value if sequence is empty.
     
    LongCount
     No
     Counts the number of elements in a sequence, yielding a long result.
     
    Max
     No
     Returns the maximum of a sequence.
     
    Min
     No
     Returns the minimum of a sequence.
     
    OfType
     Yes
     Yields the elements of a sequence that match a given type.
     
    OrderBy
     Yes
     Orders a sequence of elements by key into ascending order.
     
    OrderByDescending
     Yes
     Orders a sequence of elements by key into descending order.
     
    Range
     Yes
     Yields a sequence of integers in a given range.
     
    Repeat
     Yes
     Yields a sequence of values by repeating a given value n times.
     
    Reverse
     Yes
     Reverses the elements of a sequence.
     
    Select
     Yes
     Applies a projection function to a sequence, yielding a new sequence.
     
    SelectMany
     Yes
     Applies a projection function to flatten a sequence of sequences.
     
    Single
     No
     Returns the lone element of a singleton sequence.
     
    SingleOrDefault
     No
     Returns the lone element of a singleton sequence, or default if sequence is empty.
     
    Skip
     Yes
     Skips the first n elements of a sequence, yielding the remaining elements.
     
    SkipWhile
     Yes
     Given function F and sequence S, skips the initial elements of S where F is true.
     
    Sum
     No
     Computes the sum of a numeric sequence.
     
    Take
     Yes
     Yields the first n elements of a sequence.
     
    TakeWhile
     Yes
     Given function F and sequence S, yields the initial elements of S where F is true.
     
    ThenBy
     Yes
     Takes an ordered sequence and yields a secondary, ascending ordering.
     
    TheyByDescending
     Yes
     Takes an ordered sequence and yields a secondary, descending ordering.
     
    ToArray
     No
     Iterates across a sequence, capturing results in an array.
     
    ToDictionary
     No
     Iterates across a sequence, capturing results in a Dictionary<K, V>.
     
    ToList
     No
     Iterates across a sequence, capturing results in a List<T>.
     
    ToLookup
     No
     Iterates across a sequence, capturing results in a Lookup<K, IEnumerable<V>>.
     
    ToSequence
     Yes
     Casts a sequence as an IEnumerable sequence for use with standard query ops.
     
    Union
     Yes
     Given two sequences S1 and S2, returns the set union of S1 and S2.
     
    Where
     Yes
     Applies a Boolean function to a sequence, yielding a sub-sequence.
     


    其中先说明一下第二栏LAZY?的意思。我们知道有一个术语叫:Lazy Evalution。就是说不到用的时候程序不对它进行求值。

    下面我们用个例子说明一下:

    class Doctor
    ...{
        public string Name;
        public int Initials;
    }

    public static bool dump(Doctor d)
    ...{
      System.Console.WriteLine(d.Initials);
      return true;
    }

    Doctor[] doctors=...{new Doctor...{Name="Mary",Initials=123},
                                 new Doctor...{Name="Jack",Initials=456},
                                 new Doctor...{Name="Maggie",Initials=789}
                                };
                                   
    var query = from d in doctors
                where dump(d)
                select d;


    看看上面的代码会输出什么??答案是Nothing,什么都不输出。因为那个查询语句根本就没有执行。也就是说只有在下面用到query这个变量的时候,那个查询语句才真正的执行。大家可以在写完查询语句之后加上这样一句话

    foreach(var q in query);
    这样就会输出内容。这就是Lazy Evalution简单解释。用这种方法又好多好处。这里就不再具体介绍了。我们还可以看到并不是所有的LINQ函数都应用了Lazy Evalution。大家自己去尝试。

    下面介绍几个比较重要的:

    1     Any(下面继续用上面写的Doctor的例子)

    //查询是否有一个名字叫Lary的医生,有返回true,没有返回false

    bool inLakeForest = doctors.Any(doc => doc.Name == "Lary");
    //等价于
    var query = from doc in doctors
                where doc.Name == "Lary"
                select doc;

    bool inLakeForest = query.Any();


    2      Cast

    System.Collections.ArrayList al = new System.Collections.ArrayList();
    al.Add("abc");
    al.Add("def");
    al.Add("ghi");

    var strings = al.Cast<string>();

    foreach(string s in strings)   // "abc", "def", "ghi"
      Console.WriteLine(s);

    //Cast主要作用是让LINQ可以应用在非泛型集合上
     3     Distinct

    int[] ints  = ...{ 1, 2, 2, 3, 2, 3, 4 };

    var distinctInts    = ints.Distinct();

    foreach(var x in distinctInts)  // 1, 2, 3, 4
      Console.WriteLine(x);

    //主要是返回一个集合中不同的元素


    4     Except

    int[] intsS1 = ...{ 1, 2, 2, 3, 2, 3, 4, 5, 6 };
    int[] intsS2 = ...{ 1, 3, 6, 7 };

    var diffInts    = intsS1.Except(intsS2);

    foreach(var x in diffInts)  // 2, 4, 5
      Console.WriteLine(x);

    //可以查询在一个集合中出现却不能在另一个集合出现的元素
    5     First

    int[]   ints    = ...{ 1, 2, 3, 4, 5, 6 };

    int    first  = ints.First();
    Doctor doctor = doctors.First(doc => doc.Name == "Mary");

    Console.WriteLine(first);             // 1
    Console.WriteLine(doctor.Initials);   // 123

    //返回集合中第一个满足条件的元素
     6     OrderBy

    int[] ints      = ...{3, 1, 6, 4, 2, 5};

    var sorted = ints.OrderBy(x => x);

    foreach(var x in sorted)   // 1, 2, 3, 4, 5, 6
      Console.WriteLine(x);

    //排序,还可以用OrderByDescending,逆序
    7     Select

    int[] ints      = ...{1, 2, 3, 4, 5, 6};

    var sameInts = ints.Select(x => x>=3);

    foreach(var x in sameInts)   // 3, 4, 5, 6
      Console.WriteLine(x);

    //选出满足条件的元素
    8     Sum

    int[]      ints    = ...{ 1, 2, 3, 4, 5, 6 };
    decimal?[] values  = ...{ 1, null, 2, null, 3, 4 };

    int      sum1     = ints.Sum();
    decimal? sum2     = values.Sum();

    Console.WriteLine(sum1);   // 21
    Console.WriteLine(sum2);   // 10

    //求和,可以应用在 int, int?, long, long?, decimal, decimal?, double, or double?. 这些类型上,返回值与上述类型一样
    9     ToList

    Doctors doctors = new Doctors();

    var query = from doc in doctors
                where doc.Name == "Mary"
                select doc;

    List<Doctor> chicago = query.ToList();

    //把查询结果转换成List集合
    10     Where

    int[] ints = ...{1, 2, 3, 4, 5, 6};

    var even    = ints.Where( x => x % 2 == 0);

    foreach(var x in even)      // 2, 4, 6
      Console.WriteLine(x);

    //返回满足条件的元素集合
    大概先介绍这几个,其他的大家自己去摸索

  • 相关阅读:
    教你如何剖析源码
    singleCall单来源调用解析及实现
    守护进程详细解读
    终端&作业控制&会话启动过程
    时间复杂度&空间复杂度
    linux环形buff模拟多线程信号量操作
    linux多线程-互斥&条件变量与同步
    linux线程控制&线程分离
    栈帧的不安全程序示例
    如何获取程序返回值,退出码,错误码
  • 原文地址:https://www.cnblogs.com/hackpig/p/1668450.html
Copyright © 2020-2023  润新知