Reprint: LINQ query in C#

Table of contents

LINQ

What is LINQ

LINQ provider

Method syntax and query syntax

Query variables

Query expression structure

Standard query operators

LINQ to XML


LINQ

What is LINQ


In a relational database system, data is organized into well-normalized tables and accessed through the simple and powerful SQL language. Because data follows certain strict rules in tables, SQL works well with them. 
However, in a program as opposed to a database, the data stored in class objects or structures varies greatly. Therefore, there is no universal query language to get data from data structures. Methods for obtaining data from objects have always been designed as part of a program. However, using LINQ you can easily query a collection of objects. 
The following are important advanced features of LINQ.

  • LINQ (pronounced link) stands for Language Integrated Query
  • LINQ is an extension of the .NET framework that allows us to query data collections in the same way as using SQL to query a database.
  • Using LINQ, you can query data from databases, collections of program objects, and XML documents

Example: LINQ example

class Program
{
    static void Main()
    {
        int[] numbers={2,12,5,15};
        IEnumerable<int> lowNums=
                           from n in numbers
                           where n<10
                           select n;
        foreach(var x in lowNums)
        {
            Console.WriteLine(x);
        }
    }
}

LINQ provider


In the previous example, the data source was just an array of ints, which is the program's object in memory. However, LINQ can also work with various types of data sources. However, for each data source type, there must be a code module behind it that implements LINQ queries based on that data source type. These code modules are called LINQ providers. 
The key points about LINQ providers are as follows

  • Microsoft provides LINQ Provider for some common data source types
  • Third parties continue to provide LINQ Providers for various data source types

In this chapter, we mainly introduce LINQ and explain how to use it for program objects (LINQ to Object) and XML (LINQ to XML). Other details and usage will not be discussed.

anonymous type

Before going into the details of LINQ query features, we first learn about a feature that allows us to create unnamed class types. Anonymous types are often used in the results of LINQ queries. 
Chapter 6 introduces the object initialization statement , which allows us to initialize the fields and properties of a new class instance when using an object creation expression. As a reminder, this form of object creation expression consists of three parts: the new keyword, the class name or constructor, and the object initialization statement. An object initialization statement contains a comma-separated list of member initializations within a set of braces. 
Creating variables of anonymous types uses the same form, but without the class name and constructor. The following line of code demonstrates an anonymous type object creation expression:

No class name 
new {FieldProp=InitExpr,FieldProp=InitExpr,...} 
        Member initialization statement

Example: Example of creating and using anonymous types.

class Program
{
    static void Main()
    {
     必须使用var
         ↓
        var student=new{Name="Mary Jones",Age=19,Major="History"};
        Console.WriteLine("{0},Age {1},Major: {2}",student.Name,student.Age,studeng.Major);
    }
}

Here are some important things to know about anonymous types.

  • Anonymous types can only be used with local variables, not class members.
  • Since anonymous types have no names, we have to use the var keyword as the variable type
  • Properties of anonymous type objects cannot be set. Properties created by the compiler for anonymous types are read-only

When the compiler encounters an object initialization statement for an anonymous type, it creates a new class type with a name. Below each member initialization statement, it infers its type and creates a read-only property to access its value. Property and member initialization statements have the same name. After the anonymous type is constructed, the compiler creates an object of this type. 
In addition to the assignment form of object initialization statements, there are two other allowed forms of object initialization statements for anonymous types: simple identifiers and member access expressions. These two forms are called projection initializers . The variable declaration below demonstrates the 3 forms.

var student=new{Age=19,Other.Name,Major};

Example: Use 3 total initialization statements. Note that the projected initialization statement must be defined before the anonymous type declaration.

class Other
{
    static public string Name="Mary Jones";
}
class Program
{
    static void Main()
    {
        string Major="History";
        var student=new{Age=19,Other.Name,Major};
        Console.WriteLine("{0},Age {1},Major: {2}",student.Name,student.Age,studeng.Major);
    }
}

If the compiler encounters another anonymous type with the same parameter names, the same inferred types, and the same order, it will reuse this type and create a new instance directly without creating a new anonymous type.

Method syntax and query syntax


We can use two forms of syntax when writing LINQ queries: method syntax and query syntax.

  • Method syntax uses standard method calls. These methods are methods of a set of standard query operators
  • Query syntax looks similar to SQL statements
  • It is possible to combine both forms in one query

Method syntax is imperative, which specifies the order in which query methods are called. 
The query syntax is declarative, that is, the query describes what you want to return, but does not specify how to execute the query. 
The compiler translates queries expressed using query syntax into method calls. There is no performance difference between the two forms at runtime. 
Microsoft recommends using query syntax because it is more readable, provides a clearer indication of query intent, and is therefore less error-prone. However, some operators must be written using method syntax.

Example: Method syntax and query syntax demonstration

class Program 
{ 
    static void Main() 
    { 
        int[] numbers={2,5,28,31,17,16,42}; 
        var numsQuery=from n in numbers //Query syntax 
                      where n<20 
                      select n; 
        var numsMethod =numbers.Where(x=>x<20); //Method syntax 
        int numsCount=(from n in numbers //Combination of two forms 
                       where n<20 
                       select n).Count(); 
        foreach(var x in numsQuery) 
        { 
            Console.Write("{0}, ",x); 
        } 
        Console.WriteLine(); 
        foreach(var x in numsMethod) 
        { 
            Console.Write("{0}, ",x);
        }
        Console.WriteLine();
        Console.WriteLine(numsCount);
    }
}

Query variables


LINQ queries can return two types of results - either an enumeration (an enumerable set of data, not an enumeration type), which is a list of items that satisfy the query parameters; or it can be a single value called a scalar , which is some summary form of the results that satisfy the query conditions.

Example: query variable example

int[] numbers={2,5,28}; 
IEnumerable<int> lowNums=from n in numbers //returns an enumeration number 
                         where n<20 
                         select n; 
int numsCount=(from n in numbers //returns an integer 
               where n<20 
               select n).Count();

It is important to understand the usage of query variables. After executing the previous code, the lowNums query variable will not contain the results of the query. Instead, the compiler creates code that executes the query. 
The query variable numCount contains the real integer value, which can only be obtained by actually running the query. 
The difference lies in the query execution time, which can be summarized as follows:

  • If a query expression returns an enumeration, the query is not executed until the enumeration is processed
  • If the enumeration is processed multiple times, the query will be executed multiple times
  • If the data is changed after the traversal is performed but before the query is executed, the query will use the new data.
  • If the query expression returns a scalar, the query is executed immediately and the results are stored in the query variable

Query expression structure


The query expression consists of the from clause after the query body. There are some important things to know about query expressions:

  • Clauses must appear in a certain order
  • The two parts of the from clause and select...group clause are required
  • In LINQ query expressions, the select clause is at the end of the expression. One of the reasons C# does this is so that Visual Studio IntelliSense can give us more options when we enter code.
  • There can be any number of from…let…where clauses

from clause

The from clause specifies the data collection to be used as the data source. It also introduces iteration variables. The key points about the from clause are as follows:

  • Iterate variables representing each element of the data source one by one
  • The syntax of the from clause is as follows
    • Type is the type of elements in the collection. This is optional because the compiler can infer the type from the collection
    • Item is the name of the iteration variable
    • Items is the name of the collection to be queried. Collections must be enumerable, see Chapter 18
from Type Item in Items

The following figure demonstrates the syntax of the from clause. Type specifiers are optional. There can be any number of join clauses. 

Although LINQ's from clause and foreach statement are very similar, the main differences are as follows:

  • The foreach statement imperatively specifies that items in the collection be accessed sequentially from the first to the last. The from clause declaratively stipulates that each item in the collection must be accessed, but does not assume in what order
  • The foreach statement executes its body when it encounters code, while the from clause executes nothing. A query is executed only when the program's control flow encounters a statement that accesses the query variable.

join child clause

The join clause in LINQ is similar to the JOIN clause in SQL. The difference is that we can now perform joins not only on database tables, but also on collection objects. If you're new to joins, the following will help clarify your thinking. 
You need to first understand the syntax of the association:

  • Use joins to combine data from two more than one collection
  • The join operation takes two collections and creates a temporary collection of objects, each containing all the fields in the original collection object.

The join syntax is as follows

Keyword Keyword Keyword Keyword 
 ↓ ↓ ↓ ↓ 
join Identifier in Collection2 on Field1 equals Field1 
              Specify another collection and ID to reference it 
var query=from s in students 
          join c in studentsInCourses on s.StID equals c.StID

what is connection

Join in LINQ takes two collections and creates a new collection with each element containing the original members of the two original collections.

Example: Join example 

class Program
{
    public class Student
    {
        public int StID;
        public string LastName;
    }
    public class CourseStudent
    {
        public string CourseName;
        public int StID;
    }
    static Student[] students=new Student[]{
        new Student{StID=1,LastName="Carson"},
        new Student{StID=2,LastName="Klassen"},
        new Student{StID=3,LastName="Fleming"},
    };
    static CourseStudent[] studentsInCourses=new CourseStudent[]{
        new CourseStudent{CourseName="Art",StID=1},
        new CourseStudent{CourseName="Art",StID=2},
        new CourseStudent{CourseName="History",StID=1},
        new CourseStudent{CourseName="History",StID=3},
        new CourseStudent{CourseName="Physics",StID=3},
    }
    static void Main()
    {
        var query=from s in students
                  join c in studentsInCourses on s.StID equals c.STID
                  where c.CourseName=="History"
                  select.LastName;
        foreach(var q in query)
        {
            Console.WriteLine("Student taking History:{0}",q);
        }
    }
}

The from…let…where fragment in the query body

The optional from...let...where part is the first part of the query body and can be combined by any number of 3 clauses – from clause, let clause and where clause.

from clause

A query expression begins with the required from clause, followed by the query body. The body itself can start with any number of other from clauses. Each from clause specifies an additional source data set and introduces iteration variables to be operated on afterwards. The syntax and meaning of all from clauses are the same. 

Example: from clause example

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     from b in groupB
                     where a>4&&b<=8
                     select new{a,b,sum=a+b};//匿名类型对象
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

let clause

The let clause accepts an expression operation and assigns it to an identifier that needs to be used in other operations. The syntax of let clause is as follows:

let Identifier=Expression

Example: let clause example

class Program 
{ 
    static void Main() 
    { 
        var groupA=new[]{3,4,5,6}; 
        var groupA=new[]{6,7,8,9}; 
        var someInts=from a in groupA 
                     from b in groupB 
                     let sum=a+b //Save the result in a new variable 
                     where sum==12 
                     select new{a,b,sum}; 
        foreach(var a in someInts) 
        { 
            Console.WriteLine(a); 
        } 
    } 
}

where child clause

The where clause filters specified items based on subsequent operations. 
As long as it is in the from...let...where part, the query expression can have multiple wheres.

Example: where clause example

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     from b in groupB
                     let sum=a+b         
                     where sum>=11            ←条件1
                     where a==4               ←条件2
                     select new{a,b,sum};
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

orderby clause

The orderby clause returns the result items in order based on the expression. 
The orderby clause syntax is as shown below. The optional ascending and descending keywords set the sorting direction. The expression is usually a field of the item. The field does not have to be a numeric field, it can also be a sortable type such as a string.

  • The orderby clause defaults to ascending order
  • There can be any number of clauses, they must be separated by commas

Example: Sort by student age

class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from student in students
                  orderby student.Age
                  select student;
        foreach(var s in query)
        {
            Console.WriteLine("{0},{1}: {2} - {3}",s.LName,s.FName,s.Age,s.Major);
        }
    }
}

select…group clause

The function of select...group clause is as follows.

  • The select clause specifies which part of the selected object should be selected. It can specify any of the following
    • entire data item
    • A field of the data item
    • A new object composed of several fields of a data item (or similar other values)
  • The group...by clause is optional and is used to specify how the selected items are grouped.

Example: select the entire data item

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from s in students
                  select s;
        foreach(var s in query)
        {
            Console.WriteLine("{0},{1}: {2} , {3}",s.LName,s.FName,s.Age,s.Major);
        }
    }
}

var query=from s in students
          select s.LName;
foreach(var s in query)
{
    Console.WriteLine(s);
}

Anonymous types in queries

Query results can consist of items from the original collection, some fields of the items, or anonymous types. 
Example: Use select to create an anonymous type

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from s in students
                  select new{s.LName,s.FName,s.Major};
        foreach(var s in query)
        {
            Console.WriteLine("{0} {1} -- {2} , {3}",s.FName,s.LName,s.Major);
        }
    }
}

group clause

The group clause groups the selected objects according to some criteria. For example, with the bachelor's array from the previous example, programs could be grouped according to their major courses.

  • If items are included in the results of a query, they can be grouped based on the value of a field. The attribute used as a basis for grouping is called a key.
  • The group clause does not return an enumeration of the items in the original data source, but an enumerable type that can enumerate groups of items that have been formed.
  • Groups themselves are enumerable types, they can enumerate the actual items

Example: Grouping according to bachelor’s major courses

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from s in students
                  group s by s.Major;
        foreach(var s in query)
        {
            Console.WriteLine("{0}",s.Key);
            foreach(var t in s)
            {
                Console.WriteLine("      {0},{1}",t.LName,t.FName);
            }
        }
    }
}

Query continuation: into clause

A query continuation clause can take the results of a part of the query and give it a name so that it can be used in another part of the query. 
 
Example: Connect groupA and groupB and name them groupAandB

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     join b in groupB on a equals b
                     into groupAandB
                     from c in groupAandB
                     select c;
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

Output: 6

Standard query operators


The standard query operators consist of a series of API methods that allow us to query any .NET array or collection. 
Important features of standard query operators are as follows:

  • The collection object being queried is called a sequence, it must implement IEnumerable<T>the interface, and T is the type
  • Standard query operator usage syntax
  • Some operators return IEnumerable objects (or other sequences), while others return scalars. Operators that return a scalar are executed immediately and return a value
  • Many operations take a predicate as a parameter. A predicate is a method that takes an object as a parameter and returns true or false depending on whether the object meets a certain condition.

Example: Use of Sum and Count operators

class Program
{
    static int[] numbers=new int[]{2,4,6};
    static void Main()
    {
        int total=numbers.Sum();
        int howMany=number.Count();
        Console.WriteLine("Total: {0},Count: {1}",total,howMany);
    }
}

Standard query operators can be used to operate on one or more sequences. Sequence refers to the type that implements the IEnumerable<> interface, including List<>, Dictionary<>, Stack<>, Array, etc. 

Signature of standard query operators

The System.Linq.Enumerable class declares the standard query operator methods. These methods are not just methods, they are extension methods that extend IEnumerable<T>the generic class. 
Chapters 7 and 17 introduce class extension methods, so this section is a good opportunity to learn how to use extension methods. 
Just a quick review. Extension methods are public static methods that, although defined in one class, are intended to add functionality to another class (the first formal parameter). This parameter must be preceded by the keyword this.

Example: Signatures of 3 standard query operators

Always public static name and first parameter of generic parameters 
     ↓ ↓ ↓ 
public static int Count<T>(this IEnumerable<T> source); 
public static T First<T>(this IEnumerable<T> source); 
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,...);

Example: The difference between calling an extension method directly and calling it as an extension

using System.Linq;
...
static void Main()
{
    int[] intArray=new int[]{3,4,5,6,7,9};
    //方法语法
    var count1=Enumerable.Count(intArray);
    var firstNum1=Enumerable.First(intArray)
    //扩展语法
    var count2=intArray.Count();
    var firstNum2=intArrya.First();
    Console.WriteLine("Count: {0},FirstNumber: {1}",count1,firstNum1);
    Console.WriteLine("Count: {0},FirstNumber: {1}",count2,firstNum2);
}

Query expressions and standard query operators

Query expressions and method syntax can be combined. The compiler translates each query expression into the form of standard query operators.

class Program
{
    static void Main()
    {
        var numbers=new int[]{2,6,4,8,10};
        int howMany(from n in numbers
                    where n<7
                    select n).Count();
        Console.WriteLine("Count: {0}",howMany);
    }
}

Pass delegate as parameter

We saw earlier that the first parameter of each operator is IEnumerable<T>a reference to an object, and subsequent parameters can be of any type. Many operators accept generic delegates as parameters (Chapter 17). Generic delegates are used to provide user-defined code to operators.

To explain this, let's start with an example that demonstrates several ways of using the Count operator. 
The Count operator is overloaded and has two forms. The first one was used in the previous example. It has one parameter and returns the number of elements in the collection.

public static int Count<T>(this IEnumerable<T> source);

However, suppose we wish to see the total number of odd elements in the array. The Count method must be able to detect whether the integer is odd. 
We need to use the second form of the Count method. As shown below, it has a generic delegate as parameter. When called, we provide a delegate object that accepts a single input parameter of type T and returns a Boolean value. The return value of the delegate code must specify whether the element is included in the total.

public static int Count<T>(this IEnumerable<T> source,Func<T,bool> predicate);
class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        var countOdd=intArray.Count(n=>n%2!=0);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

LINQ predefined delegate types

Much like the Count operator in the previous example, many LINQ operators require us to provide code to instruct the operator how to perform its operation. We do this by using delegate objects as parameters. 
LINQ defines two sets of generic delegate types for use with standard query operators, namely Func delegate and Action delegate, each with 17 members.

  • The delegate object we use as an argument must be of these types or one of these forms
  • TR represents the return value and is always the last one in the type parameter list
public delegate TR Func<in T1,in T2,out TR>(T1 a1,T2 a2); 
                 ↑ ↑ ↑ 
              return type type parameter method parameter

Note that the return type parameter has the out keyword, making it covariant, that is, it can accept the declared type or a type derived from this type. Input parameters have the in keyword, making them contravariant, i.e. you can accept the declared type or a type derived from this type.

Example of using delegate parameters

class Program
{
    static bool IsOdd(int x)
    {
        return x%2!=0;
    }
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        Func<int,bool>myDel=new Func<int,bool>(IsOdd);
        var countOdd=intArray.Count(myDel);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

Example using Lamba expression parameters

The previous examples used separate methods and delegates to attach code to operators. This requires declaring the method and delegate object, and then passing the delegate object to the operator. This method is a good solution if any of the following conditions are true:

  • The method must also be called elsewhere in the program, not just where it is used to initialize the delegate object
  • There is more than one code statement in the function body

If both of these conditions are not true, we may want to use a cleaner and more localized method of providing code for operators, and that is a Lambda expression.

Example: Modify the previous example using Lambda expressions

class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        var countOdd=intArray.Count(n=>n%2!=0);//Lambda表达式
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

We can also use anonymous methods instead of Lambda expressions. However, this approach is cumbersome, and lambda expressions are semantically equivalent to anonymous methods and more concise, so there is no reason to use anonymous methods anymore.

class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        Func<int,bool> myDel=delegate(int x)   //匿名方法
                             {
                                 return x%2!=0;
                             };
        var countOdd=intArray.Count(myDel);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

LINQ to XML


Extensible Markup Language (XML) is an important method for storing and exchanging data. LINQ adds features to the language that make XML much easier to use than XPath and XSLT.

  • XML trees can be created from top to bottom using a single statement
  • Instead of using an XML document containing a tree, you can create and manipulate XML in memory
  • You can create and operate string nodes instead of using Text subnodes.
  • When searching the XML tree, there is no need to traverse it. Just query the tree and have it return the desired results

Although this book will not be a complete introduction to XML, I will give a brief introduction to XML before embracing LINQ to XML.

markup language

A markup language is a set of tags in a document that provide information about the document and organize its content. i.e. markup tags are not data about the document – ​​they contain data about the data. Data about data is called metadata
A markup language is a defined set of tags designed to convey specific types of metadata about the content of a document. For example, HTML is a well-known markup language. Metadata in tags contains information about how a Web page is rendered in a browser and how hyperlinks are used to navigate within the page. 
There are only a few predefined tags in XML, others are defined by the programmer to represent any metadata needed for a specific document type. As long as both readers and writers of the data know what the labels mean, labels can contain any useful information the designer wishes.

XML basics

The data in an XML document contains an XML tree, which is mainly composed of nested elements. 
Elements are the basic elements of XML trees. Each element has a name and contains data, and some elements contain other nested elements. Elements are divided by opening and closing tags. Any element containing data must be between the opening and closing tags.

  • start tag <ElementName>
  • end tag </ElementName>
  • Single tag with no content <ElementName/>

example:

   Start tag content End tag 
      ↓ ↓ ↓ 
<EmployeeName>Sally Jones</EmployeeName> 
<PhoneNumber/> ←Element with no content

Important things to know about XML:

  • XML documents must have a root element that contains all other elements
  • XML tags must be properly nested
  • Unlike HTML tags, XML tags are case-sensitive
  • XML attributes are name/value pairs that contain additional metadata about an element. The value part of the attribute must be enclosed in quotation marks, either single or double quotation marks are acceptable
  • White spaces in XML documents are valid. This is different from HTML which outputs spaces as spaces.
<Employees>
    <Employee>
        <Name>Bob Smith</Name>
        <PhoneNumber>408-555-1000</PhoneNumber>
        <CellPhone/>
    </Employee>
    <Employee>
        <Name>Sally Jones</Name>
        <PhoneNumber>415-555-2000</PhoneNumber>
        <PhoneNumber>415-555-2001</PhoneNumber>
    </Employee>
</Employees>

XML class

LINQ to XML can be used with XML in two ways. The first is as a simplified XML manipulation API, and the second is using the LINQ query tool seen earlier in this chapter. 
I will introduce the API method first. 
The LINQ to XML API consists of many classes that represent XML tree components. We mainly use 3 classes, XElement, XAttribute and XDocument. 
The figure below demonstrates the classes used to construct an XML tree and how they are nested.

  • Available as a direct child of an XDocument node
    • In most cases, there is one of each of the following node types: XDeclaration nodes, XDocumentType nodes, and XElement nodes
    • Any number of XProcessingInstruction nodes
  • If there is a top-level XElement node in the XDocument, then it is the root of other elements in the XML tree
  • The root element can contain any number of XElement, XComment or XProcessingInstruction nodes, nested at any level

With the exception of the XAttribute class, most of the classes used to create XML trees inherit from a class called XNode, often referred to as "XNodes" in the book.

Create, save, load and explicit XML documents

Example: Create an XML tree containing Employees nodes

using System; 
using System.Xml.Linq; 
class Program 
{ 
    static void Main() 
    { 
        XDocument employees1= 
            new XDocument( //Create XML document 
                new XElement("Employees", 
                    new XElement("Name","Bob Smith"), 
                    new XElement("Name","Sally Jones") 
                ) 
            ); 
        employees1.Save("EmployeesFile.xml"); //Save to file 
        XDocument employees2=XDocument.Load("EmployeesFile.xml"); 
                                   Static method 
        Console.WriteLine(employees2); //explicit file 
    }
}

Create XML tree

Example: Create XML tree

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employeeDoc=
            new XDocument(                    //创建XML文档
                new XElement("Employees",
                    new XElement("Employee",
                        new XElement("Name","Bob Smith"),
                        new XElement("PhoneNumber","408-555-1000")),
                    new XElement("Employee",
                        new XElement("Name","Sally Jones"),
                        new XElement("PhoneNumber","415-555-2000"),
                        new XElement("PhoneNumber","415-555-2001"))
                )
            );
        Console.WriteLine(employeeDoc);
    }
}

Using values ​​from an XML tree

The power of XML is realized when we traverse the XML tree to obtain or modify values. The following table gives the main methods used to obtain data. 
 
Regarding the table above, some things to note are as follows:

  • Nodes The Nodes method returns IEnumerable<object>an object of type, because the returned nodes may be of different types, such as XElement, XComment, etc. We can use a method that takes a type as a parameter OfType(type)to specify that a node of a certain type should be returned. For example, the following code can only obtain XComment nodes
    • IEnumerable<XComment> comments=xd.Nodes().OfType<XComment>()
  • Elements Since obtaining XElement is a very common need, the short form of the `Nodes.OfType(XElement)()` expression – the Elements method appeared
    • The parameterless Elements method returns all child XElements
    • The Elements method with a single name parameter returns the child XElements with that name. For example, the following code returns a child XElement node with the name PhoneNumber
    • IEnumerable<XElement> empPhones=emp.Elements("PhoneNumber");
  • Element This method only gets the first child XElement of the current node. If there is no parameter, get the first XElement node. If there is one parameter, get the first child XElement with this name.
  • Descendants and Ancestors These methods are similar to the Elements and Parent methods, except that they do not return direct child elements and parent elements, but ignore nesting levels, including all nodes below or above them.
using System;
using System.Collections.Generic;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employeeDoc=
            new XDocument(                    //创建XML文档
                new XElement("Employees",
                    new XElement("Employee",
                        new XElement("Name","Bob Smith"),
                        new XElement("PhoneNumber","408-555-1000")),
                    new XElement("Employee",
                        new XElement("Name","Sally Jones"),
                        new XElement("PhoneNumber","415-555-2000"),
                        new XElement("PhoneNumber","415-555-2001"))
                )
            );
        //获取第一个名为“Employees”的子XElement 
        XElement root=employeeDoc.Element("Employees");
        IEnumerable<XElement> employees=root.Elements();
        foreach(XElement emp in employees)
        {
            XElement empNameNode=emp.Element("Name");
            Console.WriteLine(empNameNode.Value);
            IEnumerable<XElement> empPhones=emp.Elements("PhoneNumber");
            foreach(XElement phone in empPhones)
            {
                Console.WriteLine(phone.Value);
            }
        }
    }
}

Add nodes and manipulate XML

We can use the Add method to add child elements to an existing element.

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                new XElement("first")
            )
        );
        Console.WriteLine("Original tree");
        Console.WriteLine(xd);
        Console.WriteLine();
        XElement rt=xd.Element("root");
        rt.Add(new XElement("second"));
        rt.Add(new XElement("third"),
               new XComment("Important Comment"),
               new XElement("fourth"));
        Console.WriteLine("Modified tree");
        Console.WriteLine(xd);
    }
}

The following table lists some of the most important methods of manipulating XML. 

Use XML attributes

Attributes provide additional information about the XElement node, which is placed in the opening tag of the XML element. 
When we construct an XML tree using a functional method, we only need to include the XAttribute constructor in the constructor of XElement to add features. The XAttribute constructor has two forms: one accepts name and value, and the other accepts a reference to an existing XAttribute.

Example: Add two features to root.

XDocument xd=new XDocument(
    new XElement("root",
            new XAttribute("color","red"),
            new XAttribute("size","large"),
        new XElement("first"),
        new XElement("second")
    )
);

Example: Get properties

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        Console.WriteLine(xd);
        Console.WriteLine();
        XElement rt=xd.Element("root");
        XAttribute color=rt.Attribute("color");
        XAttribute size=rt.Attribute("size");
        Console.WriteLine("color is {0}",color.Value);
        Console.WriteLine("size is {0}",size.Value);
    }
}

Example: Remove attributes

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        XElement rt=xd.Element("root");
        rt.Attribute("color").Remove();//移除color特性
        rt.SetAttributeValue("size",null);//移除size特性
        Console.WriteLine(xd);
    }
}

Example: Adding or changing the value of a characteristic

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        XElement rt=xd.Element("root");
        rt.SetAttributeValue("size","midium");  //改变特性值
        rt.SetAttributeValue("width","narrow"); //添加特性
        Console.WriteLine(xd);
    }
}

Other types of nodes

XComment

XML comments <!--和-->consist of text between tokens. Text between tokens is ignored by the XML parser. We can use the XComment class to insert text into an XML document. As shown in the following code: 

 new XComment("This is a comment")  

This code produces the following XML document: 
 <!--This is a comment--> 

XDeclaration

The XML document begins with a line that contains the version number used by the XML, the character encoding type, and whether the document relies on external references. This is information about XML, so it's actually metadata about the data. This is called an XML declaration and can be inserted using the XDeclaration class. The following code gives an example of XDeclaration: 
 new XDeclaration("1.0","uff-8","yes")  
This code produces the following XML document: 
 <?xml version="1.0" encoding="utf-8 " standalone="yes"?> 

XProecssingInstruction

XML processing instructions are used to provide additional data on how the XML document is used and translated. The most common processing instructions are used to associate the XML document with a style sheet. 
We can use the XProecssingInstruction constructor to include processing instructions. It accepts two string parameters: target and data string. The Ruge processing instruction accepts multiple data parameters, which must be included in the second string parameter of the XProecssingInstruction constructor, as shown in the following constructor code.

 new XProecssingInstruction("xml-stylesheet",@"href=""stories"",type=""text/css""") 

This code produces the following XML document: 
 <?xml-stylesheet href="stories.css" type="text/css"?> 

example:

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XDeclaration("1.0","uff-8","yes"),
            new XComment("This is a comment"),
            new XProecssingInstruction("xml-stylesheet",@"href=""stories"",type=""text/css"""),
            new XElement("root",
                new XElement("first"),
                new XElement("second")
            )
        );
    }
}

The code will produce the following output file. However, if used WriteLine(xd), the declaration statement will not be printed. 

LINQ query using LINQ to XML

Now, we can combine the LINQ XML API and LINQ query expressions into simple yet powerful XML tree searches.

Example: Create a sample XML tree

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("MyElements",
                new XElement("first",
                    new XAttribute("color","red"),
                    new XAttribute("size","small")),
                new XElement("second",
                    new XAttribute("color","red"),
                    new XAttribute("size","midium")),
                new XElement("third",
                    new XAttribute("color","blue"),
                    new XAttribute("size","large"))
            )
        );
        Console.WriteLine(xd);
        xd.Save("SimpleSample.xml");
    }
}

Example: LINQ to XML

class Program
{
    static void Main()
    {
        XDocument xd=XDocument.Load("SimpleSample.xml");
        XElement rt=xd.Element("MyElements");
        var xyz=from e in rt.Elements()
                where e.Name.ToString().Length==5
                select e;
        foreach(XElement x in xyz)
        {
            Console.WriteLine(x.Name.ToString());
        }
        Console.WriteLine();
        foreach(XElement x in xyz)
        {
            Console.WriteLine("Name: {0}, color: {1}, size: {2}",
                              x.Name,
                              x.Attribute("color").Value,
                              x.Attribute("size").Value);
        }
    }
}

Example: Get all top-level elements of the XML tree and create an anonymous type object for each element

using System;
using System.Linq;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument xd=XDocument.Load("SimpleSample.xml");
        XElement rt=xd.Element("MyElements");
        var xyz=from e in rt.Elements()
                select new{e.Name,color=e.Attribute("color")};
                //创建匿名类型
        foreach(var x in xyz)
        {
            Console.WriteLine(x);
        }
        Console.WriteLine();
        foreach(var x in xyz)
        {
            Console.WriteLine("{0,-6},    color:{1,-7}",x.Name,x.color.Value);
        }
    }
}

From these examples we can see that the XML API and LIQN query tools can be easily combined to produce powerful XML query capabilities.

 

Author: Moonache 
Source: http://www.cnblogs.com/moonache/The 
copyright of this article belongs to the author and the blog park. Reprinting is welcome, but this statement must be retained without the author's consent, and a link to the original text must be provided in an obvious position on the article page. , otherwise we reserve the right to pursue legal liability.

Guess you like

Origin blog.csdn.net/qq_24600981/article/details/81114413