Chapter 6. Introduction to LINQ

Language Integrated Query (LINQ), which was discussed briefly in Chapter 1, is a unified model for accessing data in a declarative and object-oriented manner. This chapter covers LINQ more fully. Why the additional attention? The reason is that LINQ is the distinguishing new feature of Microsoft Visual Studio C# 2008. C# 3.0 has been extended in several ways to accommodate LINQ. Language extensions such as extension methods, lambda expressions, expression trees, anonymous types, and object initializers provide support for LINQ. Of course, many of these extensions are useful as standalone features. For example, extension methods now are employed in several places in the .NET Framework Class Library (FCL) apart from LINQ, and extension methods have been added to the string class. Some of these extension methods, such as the Reverse extension method, are useful beyond LINQ.

LINQ is a data abstraction layer. Programming is largely about data. The first commercial computer was delivered in 1951 to the U.S. government. The Census Bureau used UNIVAC 1 to complete the census, which was an enormous challenge. It was able to process large amounts of data. Not much has changed in the last half century. We are fully entrenched in the information age, and data is more important now than ever. Developers must understand competing models for accessing and manipulating this data: relational, hierarchal, and file/directory-based (such as Microsoft Symbol Server) databases. In addition, there are Extensible Markup Language (XML) data stores and in-memory data collections. Working with these competing models creates unneeded complexity. LINQ abstracts the source and provides a single query language for these competing models. Of course, this also reduces errors and makes the application easier to maintain. The salient benefit is that instead of focusing on the intricacies of implementation, you can concentrate on creating robust and efficient solutions. From this perspective, LINQ provides a consistent data model for building data-driven applications.

Traditionally, the programming language and the query languages are separate artifacts. In the traditional model, learning a programming language is not sufficient for data management—particularly for persisted data. You also have to understand the query language and other nuances of your data provider, such as ADO.NET. This lack of integration causes several problems. Schema problems are uncovered at run time instead of compile time. Because the schema is not available at compile time, type safety is not guaranteed and IntelliSense is not provided. You are forced to treat information as data to be mined rather than as objects. LINQ is integrated into C# 3.0, a development that acknowledges the important role of data in modern-day programming. Data access is no longer an adjunct to the programming language.

In object-oriented languages, such as C#, objects and data are separated. Objects represent in-memory objects that are type-safe and support encapsulation. These attributes do not commonly extend to data. Data is neither type-safe nor extensible. For that reason, the preference is to work with objects rather than raw data. Data is fragile, while objects are not. LINQ removes the distinction between object-oriented and relational data models. This distinction is not easy to remove. Substantive differences exist: incompatible transaction models, varying type systems, and divergent optimization priorities are just a few of the problems in bridging objects and data. The traditional method, object mapping, is available. Instead of forcing relational data into the object-oriented box, LINQ abstracts the differences between the two paradigms. This is accomplished by promoting query expressions to the language level, creating a natural conduit between data and objects.

Query expressions, such as provided with LINQ, are declarative. A query expression declares what data is requested. Previously in C#, data has been accessed imperatively, for example through a foreach loop that iterates a collection. The term imperative describes how the data is retrieved. The declarative approach is preferred to the imperative. With the declarative approach, the method of retrieval remains largely with the query engine. The query engine can manage any optimization. Declarative code is easier to refactor in the future to accommodate changes. Refactoring an imperative algorithm, which is less specific, is undoubtedly more difficult.

There are several LINQ providers:

  • LINQ to Objects. The most accessible data of an application are in-memory collections, such as the System.Array or List<T> collections. You can access collections with a declarative query instead of using the imperative approach. For example, you can enumerate the elements of an array with a query statement versus a foreach loop. This provides added flexibility while making coding actually simpler. LINQ to Objects also provides an important test case. You can validate the query expressions in the more transparent model provided by LINQ to Objects before using another provider, such as LINQ to ADO.NET. In this chapter, we use LINQ to Objects to explain the core concepts of LINQ.

  • LINQ to XML. XML is ubiquitous in .NET. Configuration files, datasets, and Simple Object Access Protocol (SOAP) are a handful of the many implementations based on XML in .NET. Some of the ways to access XML include as a text file, through configuration settings, via the XmlReader type, and through the XmlDocument type. With LINQ to XML, you can access XML data sources in memory using stand query language syntax. LINQ to XML also provides the ability to navigate and manage XML trees.

  • LINQ to SQL. Use LINQ to SQL to access relational databases. Importantly, LINQ to SQL allows data to be accessed as objects. This translation is done using data mapping, as described in Chapter 11. The Visual Studio Integrated Development Environment (IDE) has support for LINQ to SQL beginning with LINQ to SQL classes and the Object Relational Designer (O/R Designer). With LINQ to SQL, you can browse, update, delete, and add records to a SQL database.

LINQ to Objects is the basis of this chapter. LINQ to XML and LINQ to SQL are discussed in detail in Chapter 11.

C# Extensions

As mentioned, C# 3.0 has been extended in support of LINQ. This includes support for type inference, anonymous types, and other elements. In addition, the .NET FCL 3.5 is extended to provide supporting types for LINQ. Lookup, BinaryExpression, ChangeSet, and AssociationAttribute represent a handful of new types added to the .NET FCL 3.5 for LINQ. The following sections are a review of the language extensions in C# related to LINQ.

Type Inference

You can declare a local variable without assigning a specific type. The actual type is inferred at compile time from the expression used to initialize the variable. This is called type inference. Because type inference is done at compile time, the variable is type-safe. As seen later in this chapter, there are occasions in LINQ where you might not be aware of the type of a variable. However, the compiler knows the appropriate type. Declare a local variable with the var keyword for type inference. The var keyword cannot be used with data members or properties. The following code declares a variable (aVar) with type inference. Because it is initialized as an integer variable, aVar becomes an integer type at compile time. This is confirmed with the Console.WriteLine statement, which will display System.Int32:

int iVar = 5;
var aVar = iVar;
Console.WriteLine(aVar.GetType().ToString());

Object Initializers

Object initializers allow you to initialize an object without a constructor. The public data members and properties can be initialized directly. Object initializers are a convenience. You do not have to create a constructor for every permutation of data members and properties that require initialization. In the initializer list, the members are named and assigned a value. You do not have to initialize every data member or property. The object initializer list, which is contained within curly braces, assigns a new instance to the reference. Within the list, each member is delimited with a comma. Here is an example of an object initializer list. In this example, an Employee object is initialized using the initializer list:

using System;

namespace CSharpBook {
    class Program {
        static void Main(string[] args) {
            Employee bob = new Employee
                { Name = "Bob Kelly", Age = 25, Salary = 65000 };
        }
    }
    class Employee {
        public string Name { get; set; }
        public int Age{ get; set; }
        public decimal Salary { get; set; }
    }
}

Anonymous Types

Types typically have names. You use the class or struct keyword to declare and name a type formally. Anonymous types are declared inline and do not use the class or struct keyword. Because the class or struct keyword is not used, the type is unnamed and therefore anonymous. Declare an anonymous type using the new keyword and an object initializer list. The items in the initializer list can be named or unnamed. If a type is unnamed, its name is inferred from the name of the item. Because the resulting object type is anonymous, you must assign an instance of an anonymous type to an object using type inference (that is, using the var keyword). The following code defines an anonymous type with Name, Age, and Salary members. The name and type of the Name member is implied from the local variable in the initialize list:

using System;

namespace CSharpBook {
    class Program {
        static void Main(string[] args) {
            string Name = "Bob Kelly";
            var obj = new { Name, Age = 25, Salary = (decimal)65000 };
            Console.WriteLine("{0} {1} {2}",
                obj.Name, obj.Age, obj.Salary);
        }
    }
}

Extension Methods

Extension methods extend the interface of a class. Traditionally, you inherit a class and extend the base type in the derived class. However, this is not always possible. For example, the class might be sealed. The first parameter of an extension method must be prefixed with the this keyword. In addition, extension methods must be static and public. Extension methods are not defined in the target class but in another class. This class may hold one or more extension methods that are applicable to the target type. If the target type has a method that matches the extension method, the member method always has priority and is called. Look at the following code. IsOdd and IsEven are extension methods for integer (int) types. IsOdd returns true if the integer contains an odd value, such as 3, 5, or 7. IsEven returns true if the integer contains an even value:

static class IntegerExtensions {
    public static bool IsOdd(this int number) {
        return (number % 2) == 0 ? false : true;
    }
    public static bool IsEven(this int number) {
        return (number % 2) == 0 ? true : false;
    }
}
class Startup {
    public static void Main() {
        bool result = 6.IsOdd();
        if (result){
            Console.WriteLine("odd");
        }
        else {
            Console.WriteLine("even");
        }
    }
}

Lambda Expression

Lambda expressions encapsulate code without the keyword normal function signature or a formal function body. Define a lambda expression using the lambda operator (=>). The left-hand side of the lambda operator is the input. The right-hand side is the code. Similar to anonymous methods, lambda expressions are unnamed. However, lambda expressions are more flexible than anonymous methods. For example, lambda expressions can be delay-compiled. This is done with expression trees, which are explained in the next section. In the following example code, a lambda expression is defined that accepts a value and returns the square of that value. The lambda expression initializes a delegate, which is called on the subsequent line. The func method is called in the Console.Writeline statement, and the result (25) is displayed:

delegate int MyDel(int a);
static void Main(string[] args) {
    MyDel func = (a) => { return  a * a; };
    Console.WriteLine(func(5));
}

In the previous code, the verbose syntax is used for lambda expressions. There is also an abbreviated syntax. If the lamba expression has only a single statement, and that statement sets the return value, you can omit the curly braces and the return statement. In addition, when there is a single parameter, the parentheses can be dropped from the left-hand side. Here is an example of the abbreviated syntax:

MyDel func = a => a * a;

The inputs for a lambda expression are normally the parameters. Within the body of the lambda expression, you also can refer to local variables, data members, and properties that are within scope at that time. The following code shows an example of a lambda expression that uses a local variable. (Do not attempt to use the lambda expression where elements of the lambda expressions are no longer within scope.)

int iVar = 5;
MyDel func = (int a) => {return  a * iVar; };
Console.WriteLine(func(5));

In LINQ, there are two special signatures for lambda expressions. A lambda expression that returns a Boolean value is called a predicate. Projections are lambda expressions that accept a single parameter of a specific type but that return a different type.

Expression Trees

In an expression tree, code is data. More accurately, code in an expression tree is populated using lambda expressions. As data, you can modify and even compile the code at run time. Expression trees are represented by the Expression type. Expression objects are initialized with lambda expressions. In LINQ, expression trees are used to parse, compile, and defer the execution of query expressions.

An expression tree can consist of multiple expressions, such as unary and binary expressions. The following code is an example of a binary expression. The lambda expression (x * y) is a binary expression. We initialize an Expression instance with the lambda expression. The generic argument for the Expression type is Func<int, int, int>. For a binary expression, Func<T1, T2, TR> is a delegate for a function that has two parameters and a return value. T1 and T2 are the parameters, and TR is the return type. BinaryExpression encapsulates a binary expression. The code then extracts the body, the left parameter, and the right parameter of the expression. Finally, the program compiles and executes the expression. Then the result is displayed:

using System;
using System.Linq.Expressions;

namespace CSharpBook {
    class Program {
        static void Main(string[] args) {
            Expression<Func<int, int, int>> product =
                    (x, y) => x * y;
            BinaryExpression body = (BinaryExpression)product.Body;
            ParameterExpression left = (ParameterExpression)body.Left;
            ParameterExpression right = (ParameterExpression)body.Right;
            Console.WriteLine("{0}
Left: {1} Right: {2}", body, left, right);
            var lambda = product.Compile();
            Console.WriteLine(lambda(2, 3));
        }
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.77.250