Chapter 1. Overview

When we say Language Integrated Query, we might think that it is already integrated into the programming language, just as we write SQL queries in our application. So what is the difference or additional features that we are going to get in LINQ? How is LINQ going to make our programming life easier? Also, I am sure that we all want to know how the new feature, LINQ, is making use of the other new features of C# 3.0. We'll see many of those in this book.

LINQ Architecture

Language Integrated Query is a new feature in Visual Studio 2008 that extends the query capabilities, using C# and Visual Basic. Visual Studio 2008 comes with LINQ provider assemblies that enable the use of Language Integrated Queries with different data sources such as in-memory collections, SQL relational database, ADO.NET Datasets, XML documents and other data sources.

In Visual Studio 2008, Visual C# and Visual Basic are the languages that implement the LINQ language extensions. The LINQ language extensions use the new Standard Query Operators API, which is the query language for any collection that implements IEnumerable<T>. It means that all collections and arrays can be queried using LINQ. The collections classes simply needs to implement IEnumerable<T>, to enable it for LINQ to query the collections.

The following figure shows the architecture of LINQ, which can query different data sources using different programming languages:

LINQ Architecture

LINQ to Objects

Refers to the use of LINQ queries to access in-memory data structures. We can query any type that supports IEnumerable(Of T) (Visual Basic) or IEnumerable<T> (C#).

LINQ to SQL

LINQ to SQL is used for managing the relational data as objects. It is part of the ADO.NET family of technologies. LINQ to SQL translates the Language Integrated Queries in the object model to SQL and sends them to the database for execution. When the database returns the result, LINQ to SQL translates them back to objects that we can work with. LINQ to SQL also supports stored procedures and user-defined functions in the database.

LINQ to Datasets

LINQ to Datasets makes it easier to query the data cached in Datasets. A Dataset is disconnected, consolidated data from different data sources.

LINQ to Entities

The Entity Data Model is a conceptual data model that can be used to model the data so that applications can interact with data as entities or objects. Through the Entity Data Model, ADO.NET exposes the entities as objects.

LINQ to XML

LINQ to XML provides the in-memory document modification capabilities of the Document Object Model and supports LINQ queries. Using LINQ to XML, we can query, modify, navigate, and save the changes of an XML document. It enables us to write queries to navigate and retrieve a collection of elements and attributes. It is similar to XPath and XQuery.

Integration with SQL

LINQ to SQL supports LINQ queries against a relational database for data retrieval and manipulation. Currently in software development, we use Relational data for most of the applications, and we depend on database queries and objects, in some way or the other. The applications use APIs to process or get details from the database by passing queries as strings, or calling database objects by passing parameters. So what is the purpose of LINQ here? Presently scenario, we see a lot of applications, especially multi-tier applications, having a separate data access layer and a business logic layer. If we take the business logic layer, we must have lots of entities or objects to hold the information. These objects represent the database table rows in the form of objects. We use object references similar to primary keys in the database to identify a set of information.

To get data from a relational database to an application, the programmer ends up creating two different types of objects, but in different formats. The other disadvantage is that the programmer has to pass relational database queries as text strings from the application, then get it executed from the relational database, and pass on the information to the application objects or entity objects. We will not be able to validate the query, which is passed as text strings, until it gets compiled and executed at the database server. We cannot make use of IntelliSense or the debugging feature of the development environment to validate the queries.

Using LINQ to SQL, we can manage relational data as objects at run time using the querying facility. The LINQ queries get translated to SQL queries for the database to execute the queries, and the results are again translated to objects for the application to understand. LINQ uses the same connection and transaction features of current .NET framework for connecting to the database and manipulating the data under transaction. We can also make use of the IntelliSense feature for validating LINQ queries. To represent relational data, we need to create classes for the entities. For creating the entity classes, we need to specify some custom attributes to the class declaration. This is to make the entity objects have similar properties as that of the database objects.

Integration with XML

LINQ to XML is a new concept that supports queries against XML data. This is similar to the LINQ queries with relational data, but here the source of data is XML. Using LINQ to XML, we can manipulate XML documents and elements, create or save XML documents, and traverse through the XML tree.

When we use XML, we will be talking about elements and attributes. XML trees are composed of only attributes and elements. If we look at W3C DOM, the XML document object contains the whole XML tree in it. All elements and attributes are created within the context of the XML document. For example, the .NET 2.0 way of creating the XML element, Icecream using XML Document object model is shown below:

XmlDocument xdoc = new XmlDocument();
XmlElement Icecream = xdoc.CreateElement("Icecream");

This is an unnecessary dependency that we have to follow.

.NET 3.0 avoids creating XML document objects and we can directly create elements and attributes. For example, the following code is used for creating the Icecream element using XElement.

XElement Icecream = new XElement("Icreceram", "Chocolate Fudge");

The XML document feature is still supported for adding information like processing instructions and comments to XML.

LINQ to XML has better features than DOM for handling names and namespaces, fragments, loading XML, inner XML, annotations, and schema information.

Functional construction is a new approach taken by LINQ to XML for constructing XML elements. Using functional construction, we can create the entire XML tree in a single statement. XElement is the main class used for the construction. It has different constructors by which we can construct an XML tree. We will see this in detail later when we discuss functional construction. This is one of the important features of LINQ to XML.

LINQ to XML has a set of classes under its hierarchy structure for constructing and manipulating XML data. XElement, XNode, XName, XContainer, XAttribute and XText are some of the classes in the hierarchy. XElement is the main class for building and manipulating the XML tree.

Support for C# 3.0 Language Features

There are various features in C# 3.0 which support LINQ. They are explained in detail.

Anonymous Types

Anonymous types are used to define strong types without defining the full class. Anonymous types are strongly typed and checked at compile time. This type is widely used by LINQ, because LINQ returns dynamically-shaped data, whose type is determined by the LINQ query. In LINQ, the types are defined in situations where the types are needed temporarily, or just once. For example, given an ice-cream which has properties like name, flavor, ingredients, price, and fat content, we might sometimes only need the name of the ice-cream and the price. Anonymous type helps us to define a dynamic type containing only the name and price of the Icecream object. This is also called shaping the result of the data being queried into a different structure or format than the original data source.

For example, following is a class that is defined for an application, and objects created and assigned some data to it.

public class Icecream
{
public string name;
public string ingredients;
public string totalFat;
public string cholesterol;
public string totalCarbohydrates;
public string protein;
public double price;
}
List<Icecream> icecreamsList = new List<Icecream>
{
new Icecream
{
name="Chocolate Fudge Icecream",
ingredients="cream, milk, mono and diglycerides...",
totalFat="20g",
cholesterol="50mg",
totalCarbohydrates="35g",
protein="4g",
price=10.5
},
new Icecream
{
name="Vanilla Icecream",
ingredients="vanilla extract, guar gum, cream...",
totalFat="16g",
cholesterol="65mg",
totalCarbohydrates="26g",
protein="4g", price=9.80
},
new Icecream
{
name="Banana Split Icecream",
ingredients="Banana, guar gum, cream...",
totalFat="13g",
cholesterol="58mg",
totalCarbohydrates="24g", protein="6g",
price=7.5
}
};

I have created a list, containing details of different ice-creams. Now I can use the projection or transformation capabilities of LINQ, and create structure and give a custom shape to something other than the original Icecream object. I don't have to explicitly define a new class for the new custom shaped structure. Instead, I can use the anonymous type feature to implicitly define a new type with just two properties to represent my custom shaped data.

var IcecreamsWithLessPrice =
from ice in icecreamsList
where ice.Price < 10
select new
{
Name = ice.Name,
Price = ice.Price
};
Console.WriteLine("Ice Creams with price less than 10:");
foreach (var icecream in IcecreamsWithLessPrice)
{
Console.WriteLine("{0} is {1}", icecream.Name,
icecream.Price);
}

In this code, I am declaring an anonymous type within the CIT clause in my LINQ query. The anonymous type has only two properties, Name and Price, whose property names and values are inferred from the shape of the query.

In the next step, I am referring to the IEnumerable<T> collection of this anonymous type returned by the query and loop over them and extract the details. This feature gives it a dynamic language-like flexibility.

Object Initializers

Object initializers lets you assign values to the properties of an objects at the time of creating the object. Normally in .NET 1.1 and 2.0, we define the class with properties, then create the instance, and then define the values for properties either in constructor or in the function which is using the object. Here, in C# 3.0 we can define the values at the time of creation itself. Consider the following example of class Icecream with two auto-implemented properties. Auto-implemented properties are the properties without a local variable to hold the property value.

public class Icecream
{
public string Name { get; set; }
public double Price { get; set; }
}

Now when I create the new object of type Icecream, I can directly assign values directly.

Icecream ice = new Icecream { Name = "Chocolate Fudge
Icecream", Price = 11.5 };

This is not only for the auto-implemented properties, but I can also assign a value to any accessible field of the class. The following example has a field named Cholestrol added to it.

public class Icecream
{
public string Name { get; set; }
public double Price { get; set; }
public string Cholestrol;
}

Now I can assign values to this new field added to the class at the time of creating the object itself.

LINQ query expressions make use of these object initializers for initializing anonymous types. In the Anonymous Types section, as discussed previously, we have a select query which creates an anonymous type with two properties. The values are also assigned using object initializers.

var IcecreamsWithLessPrice =
from ice in icecreamsList
where ice.Price < 10
select new
{
Name = ice.Name,
Price = ice.Price
};

Collection Initializers

Collection initializers use object initializers to initialize their object collection. By using a collection initializer, we do not have to initialize objects by having multiple calls. For example, in the Anonymous Types section a little earlier, we created a list named icecreamsList which is a collection of Icecreams. All Icecream objects added to the collection are initialized using the collection initializer, as follows:

List<Icecream> icecreamsList = new List<Icecream>
{
new Icecream
{
Name="Chocolate Fudge Icecream",
Ingredients="cream, milk, mono and diglycerides...",
Cholesterol="50mg",
Protein="4g",
TotalCarbohydrates="35g",
TotalFat="20g",
Price=10.5
},
new Icecream
{
Name="Vanilla Icecream",
Ingredients="vanilla extract, guar gum, cream...",
Cholesterol="65mg",
Protein="4g",
TotalCarbohydrates="26g",
TotalFat="16g",
Price=9.80
},
new Icecream
{
Name="Banana Split Icecream",
Ingredients="Banana, guar gum, cream...",
Cholesterol="58mg",
Protein="6g",
TotalCarbohydrates="24g",
TotalFat="13g",
Price=7.5
}
};

Partial Methods

Microsoft introduced the concept of partial classes in .NET 2.0, which allows multiple developers to work on the same class file at the same time. This feature provides a way to split the definition of a class in multiple files. All these files are combined at the time of compilation. This is very helpful in adding new code or new functionality to the class without disturbing the existing class files; partial is a keyword modifier used for splitting the class.

Partial method is a new feature introduced in .NET 3.0 which is similar to partial classes. Partial methods are a part of partial classes, where the implementer of one part of the class just defines the method, and the other implementer can implement the method. It is not necessary that the second implementer of the class has to implement the method. If the method is not implemented, the compiler removes the method signature and all the calls to this method. This helps the developer to customize the code with his own implementation. It is safe to declare the partial methods without worrying about the implementation. The compiler will take care of removing all the calls to the method. Following is an example for defining and implementing partial methods:

// Defining UpdateItemsList method in Items1.cs file
partial void UpdateItemsList();
//Implemeting UpdateItemsList method in Items2.cs file
partial void UpdateItemsList()
{
// The method Implementation goes here
}

There are some constraints in using partial methods. They are as follows:

  1. Method declaration must begin with the keyword partial and the method should return void.

  2. Methods can have ref parameters, but not out parameters.

  3. Methods cannot be virtual, as they are private implicitly.

  4. Partial methods cannot be extern as the presence of a body determines whether they are defining or implementing.

  5. We cannot make a delegate to a partial method.

Implicitly Typed Local Variables

Implicitly typing variables is a new feature that makes your job easier. The compiler takes care of identifying the type of variables from the value used for initializing the variables. LINQ also make use of this new feature for identifying the type of data that results from the LINQ queries. The programmer need not specify the return type of the querie's result. We normally declare variables by specifying the type of the variable. For example, to declare variables of type integer, string, and array of integers we would be writing it as:

int iCount = 0;
string sName = "Hi";
int[] iIntegers = new int[] {1,2,3,4,5,6,7,8,9};

The equivalent of the above declarations using implicit typing would be as follows:

var iCount = 0;
var sName = "Hi";
var iIntegers = new int[] {1,2,3,4,5,6,7,8,9};

We have used the keyword var and a value for initializing the variable. We have not used any type for the variable. In this case, the compiler takes care of defining the variable type from the value assigned to it. The variable iCount is considered as an integer as the value assigned to it is an integer. So for any variable to be an implicitly typed variable, it should have an initializing value assigned to it, and it cannot have null value assigned to it. As the type is defined by the initial value, the initial value cannot be changed over the lifetime of the program. If we do so, we will end up getting an error while compiling.

We can also use implicit typing for declaration of collections. This is very useful when instantiating complex generic types. For example, the normal way of declaring a collection which holds item numbers is given as follows:

List<int> itemNumbers = new List<int>();
itemNumbers.Add(100005);
itemNumbers.Add(100237);
itemNumbers.Add(310078);

The equivalent for the above declaration, using implicit typing, would be as follows:

var itemNumbers = new List<int>();
itemNumbers.Add(100005);
itemNumbers.Add(100237);
itemNumbers.Add(310078);

In all the previous cases, the implicit type declaration has some restrictions and limitations:

  • We should use only the var keyword with an initializer for the declaration.

  • The intializer cannot be a null value.

  • The initializer cannot be an object or collection by itself.

Once initialized, the type cannot be changed throughout the program. Even though implicit typing gives the advantage of not specifying the type of the variable, it is better practice to use typed variable in order to clearly know the type of the variable declared.

Extensions

Extension methods are static methods that can be invoked using instance method syntax. Extension methods are declared using this keyword as a modifier on the first parameter of the method. Extension methods can only be declared in static classes. The following is an example of a static class that has the extension method CountCharacters to count the number of characters in the parameter string:

namespace Newfeatures.Samples
{
public static class Example
{
public static int CountCharacters(string str)
{
var iCount = str.Length;
return iCount ;
}
}
}

To test the above extension methods, include the following code into the main method of the program. Now run the application and test it.

static void Main(string[] args)
{
string[] strings = new string[]
{"Name", "Chocolate Fudge Icecream" };
foreach (string value in strings)
Console.WriteLine("{0} becomes: {1}",value,
Example.CountCharacters(value));
}

In order to define the previous method to be an extension method that can be invoked using the instance method syntax, include the keyword this as the modifier for the first parameter:

public static int CountCharacters(this string str)

In the Main method, change the invocation of CountCharacters to use the instance method syntax making CountCharacters appear as a method of the string class, as shown:

static void Main(string[] args)
{
string[] strings = new string[]
{ "Name", "Chocolate Fudge Icecream"};
foreach (string value in strings)
Console.WriteLine("{0} becomes: {1}",
value, value.CountCharacters());
}

Extension methods can also be added to generic types, such as List<T> and Dictionary<T>, as in the case of normal types.

public static List<T> result<T>(this List<T> firstParameter, List<T> secondParameter)
{
var list = new List<T>(firstParameter);
// required coding
return list;
}

It is recommended that we use extension methods only when it is really required. It is better to use inheritance, and create a new type by deriving the existing type wherever it is possible. An extension method will not be called if it has the same signature as a method defined in the type. Extension methods are defined at the namespace level, so we should avoid using it when we create class libraries.

Expressions

The various types of expressions used in LINQ are explained below.

Lambda Expressions

Anonymous methods in C# 2.0 help us to avoid declaring a named method by writing methods inline with code. This can be used in places where we need the functionality only within the parent method. We cannot reuse the anonymous method code in the other methods, as it is available within the parent method. Following is an example for finding a particular string from a list of strings:

class Program
{
static void Main(string[] args)
{
List<string> icecreamList = new List<string>();
icecreamList.Add("Chocolate Fudge Icecream");
icecreamList.Add("Vanilla Icecream");
icecreamList.Add("Banana Split Icecream");
icecreamList.Add("Rum Raisin Icecream");
string vanilla = icecreamList.Find(FindVanilla);
Console.WriteLine(vanilla);
}
public static bool FindVanilla(string icecream)
{
return icecream.Equals("Vanilla Icecream");
}
}

The equivalent anonymous method for the above code would be as follows:

List<string> icecreamList1 = new List<string>();
icecreamList1.Add("Chocolate Fudge Icecream");
icecreamList1.Add("Vanilla Icecream");
icecreamList1.Add("Banana Split Icecream");
icecreamList1.Add("Rum Raisin Icecream");
string vanilla1 = icecreamList1.Find(delegate(string icecream)
{
return icecream.Equals("Vanilla Icecream");
});
Console.WriteLine(vanilla1);

In the previous example, a method is defined inline and we do not have any external method to find the string.

Now C# 3.0 has a new feature called lambda expression which helps us to avoid the anonymous methods itself. For example, here is the equivalent method with lambda expression for the previous anonymous method.

// Using Lambda Expressions
List<string> icecreamList2 = new List<string>();
icecreamList2.Add("Chocolate Fudge Icecream");
icecreamList2.Add("Vanilla Icecream");
icecreamList2.Add("Banana Split Icecream");
icecreamList2.Add("Rum Raisin Icecream");
string vanilla2 = icecreamList2.Find((string icecreamname)
=>icecreamname.Equals("Vanilla Icecream"));
Console.WriteLine(vanilla2);

A lambda expression is the lambda with the expression on the right side.

(input parameters separated by commas) => expression

We can also specify the types of the input paramaters; for example, (int x, int y) => x > y.

There is another type called statement lambda that consists of a number of statements enclosed in curly braces.

The following is an example of lambda expression with an extension method. It uses the where extension method to get the total number of integers, and the list of integers (which are less than 10) in the array of integers.

var numbers = new int[] { 1, 10, 20, 30, 40, 5, 8, 2, 9};
var total = numbers.Where(x => x < 10);
Console.WriteLine("Numbers less than ten: " + total.Count());
foreach(var val in total)
Console.WriteLine(val);

LINQ provides the ability to treat expressions as data at runtime using the new type Expression<T> which represents an expression tree. This is an in-memory representation of the lambda expression. Using this, we can modify the lambda expressions through code. By getting these expressions as data, we can also build the query statements at runtime. System.Expressions is the namespace used for this. There are some limitations to the lambdas. They are as follows:

  • It must contain the same number of parameters as the delegate type.

  • Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • The return value of the lambda must be convertible to the delegate's return type.

Query Expressions

Currently, we are actually working with two different languages when we retrieve data from the database and work with our front-end applications. One would be for front-end application development and the other is the SQL for retrieving data from the database. These SQL queries are embedded into the application code as strings, so we don't get the facility of the compiler checking the query statements in quotes.

In C# 3.0, we have LINQ which gives the benefit of strong type checking. Also, we don't need to depend on SQL queries and writing it within quotes. LINQ is similar to relational database queries. Query expressions provide the language integrated syntax for queries.

The query expression begins with a from clause and ends with a select or a group clause. The from clause can be followed by many from, let, or where clauses.The from clause is a generator, the let clause is for computing the value, the where clause is for filtering the result and select or group specifies the shape of the result. There are other operators like orderby. For example, the query below is to select ice-creams with price less than 10.

from Icecream Ice in Icecreams
where Ice.Price <= 10.0
select Ice

Following are the syntax for the query expressions:

query-expression:

from-clause query-body

from-clause:

from typeopt identifier in expression join-clausesopt

join-clauses:

join-clause

join-clauses join-clause

join-clause:

join typeopt identifier in expression on expression equals expression

join typeopt identifier in expression on expression equals expression into identifier

query-body:

from-let-where-clausesopt orderby-clauseopt select-or-group-clause query- continuationopt

from-let-where-clauses:

from-let-where-clause

from-let-where-clauses from-let-where-clause

from-let-where-clause:

from-clause

let-clause

where-clause

let-clause:

let identifier = expression

where-clause:

where boolean-expression

orderby-clause:

orderby orderings

orderings:

ordering

orderings , ordering

ordering:

expression ordering-directionopt

ordering-direction:

ascending

descending

select-or-group-clause:

select-clause

group-clause

select-clause:

select expression

group-clause:

group expression by expression

query-continuation:

into identifier join-clausesopt query-body

C# 3.0 actually translates the query expressions into invocation of methods like where, select, orderby, groupby, thenby, selectmany, join, cast, groupjoin that have their own signatures and result types. These methods implement the actual query for execution. The translation happens as a repeated process on the query expressions, until no further translation is possible. For example, the following query:

from Icecream Ice in Icecreams
where Ice.Cholestrol == "2mg"
select Ice

...is first translated into:

from Icecream Ice in Icecreams.Cast<Icecream>()
where Ice.Cholestrol == "2mg"
select Ice

...the final translation would be as follows:

Icecreams.Cast<Icecream>().Where(Ice => Ice.Cholestrol == "2mg")

The following query:

from Ice in Icecreams
group Ice.Name by Ice.Cholestrol

...is translated into the following:

Icecreams.GroupBy(Ice => Ice.Cholestrol, Ice =>Ice.Name

Let us see how we can make use of these queries with in-memory collections. The System.Linq namespace has all the standard query operators. We have to use this namespace for writing queries. Create a class, Icecream as follows:

public class Icecream
{
public string name;
public string ingredients;
public string totalFat;
public string cholesterol;
public string totalCarbohydrates;
public string protein;
public double price;
}

Using the above Icecream class, create list of ice-creams and assign that to a list variable. We will see how we can easily retrieve information from this list using queries.

List<Icecream> icecreamsList = new List<Icecream>
{
new Icecream("Chocolate Fudge Icecream", "cream, milk, mono and
diglycerides...", "20g", "50mg", "35g", "4g", 10.5),
new Icecream ("Vanilla Icecream", "vanilla extract, guar gum,
ream...", "16g", "65mg", "26g", "4g", 9.80 ),
new Icecream ("Banana Split Icecream", "Banana, guar gum,
cream...", "13g", "58mg", "24g", "6g", 7.5)
};

The following query will return the name and price of the ice-creams with a price less than or equal to 10. In this query we have not specified any type for variables; it's all implicit. Even if we want to specify the type, it is not easy to identify the type of the value returned for the query.

var icecreamswithLeastPrice =
from Ice in icecreamsList
where Ice.price <= 10
select new { Ice.name, Ice.price };
Console.WriteLine("Icecreams with least price: ");
foreach (var ice in icecreamswithLeastPrice)
{
Console.WriteLine(ice.name + " " + ice.price);
}

Let us see how we can leverage the feature of lambda expressions here. For example, we will find out the list of ice-creams with a lower price using the lambda expressions. Include the following code to the main method of the program after creation of the icecreamsList.

var count = icecreamsList3.Count<Icecream>(Ice => Ice.price <=0);
Console.WriteLine("Number of Icecreams with price
less than ten: {0} ", count);

The above lambda expression will return the total number of ice-creams with a price less than or equal to 10.

Expression Trees

Expression trees are an in-memory representation of a lambda expression. Using this we can modify and inspect the lambda expressions at runtime using expression trees in the System.Linq.Expressions namespace. Lambda expressions are compiled as code or data, depending on the context they are used in. If we assign a lambda expression to a variable of type delegate, then the compiler will generate the corresponding executable code; but we assign the lambda expression to a variable of the generic type Expression<T>, so the compiler won't create the executable code, but will generate an in-memory tree of objects that represents the structure of the expression. These structures are known as expression trees.

For example, consider the following lambda expression using delegate:

Func<int, int> func = x => x + 5;

This code is compiled as executable and can be executed as follows:

var three = func(1);

The same delegate is no longer compiled as executable, but compiled as data if we define the delegate as expression tree:

Expression<Func<int, int>> expression = func => x + 5;

To use this expression in the application, it has to be compiled and invoked as follows:

var originalDelegate = expression.Compile();
var three = originalDelegate.Invoke(2);

Each node in the expression tree represents an expression. If we decompose the expression, we can find out how the expression tree represents the lambda expressions. Following is the sample code to decompose the previous expression:

ParameterExpression parameter =
ParameterExpression)expression.Parameters[0];
BinaryExpression operation = (BinaryExpression)expression.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
parameter.Name, left.Name, operation.NodeType, right.Value);

The output of the above decomposition would be:

Decomposed expression: func => func Add 5

Expression trees are implemented in the System.Query.dll assembly under the System.Linq.Expressions namespace. The abstract class Expression provides the root of a class hierarchy used to model expression trees. The Expression class contains static factory methods to create expression tree nodes of various types.

There are many different abstract classes used to represent the different types of elements in an expression. These classes are derived from the non-generic version of Expression. The following table examines some abstract classes derived from the Expression class.

Class

Description

Parameters

lambda expression

This is the bridge between the generic Expression<T> class and non-generic Expression class.

Its main properties are body and parameters.

Body—represents the body of the expression.

Parameters—represents the list of parameters it uses.

constant expression

This represents the constant values that appear in the expression.

Value is it's main property, which returns the constant value in the expression.

parameter expression

Represents a named parameter expression. Values must be passed to parameter to evaluate the expression.

Name is the property of the parameter expression to represent the name of the parameter.

unary expression

Represents an expression that has the unary operator.

The main property of this class is operand which is associated with the operand in expression.

binary operator

This is to represent the binary operators, like sum, multiplication, and many others.

The main properties of this class are left and right which provides access to the left and right operand in the expression.

method call expression

This represents the method call in an expression.

The main properties of this class are:

Method—metadata information associated to the method to be called.

Object—the object to which the method call will be applied.

Parameters—to represent the arguments used in the method.

conditional expression

Represents an expression that has a conditional operator.

The main properties of conditional expression are.

IfFalse—gets the expression to execute if the test evaluates to false.

IfTrue—gets the expression to execute if the test evaluates to true.

Summary

In this chapter, we have seen an overview of Language Integrated Query. The architecture diagram explains the different types of LINQ which are used for querying data from different sources of data. Also, we have seen some of the new features of C# 3.0 and above in relation to LINQ. This chapter also explained some of the new features introduced in C# 3.5 such as partial methods, expressions and anonymous types with some examples for each of those. We will be looking into details of LINQ features and its usability in the coming chapters.

..................Content has been hidden....................

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