|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Visit the project homepage at CodePlex. IntroductionThe LINQ project that will be part of the next version of Visual Studio (codename "Orcas") is a set of extensions that make it possible to query data sources directly from the C# or VB.NET languages. LINQ extends the .NET Framework with classes to represent queries and both C# and VB.NET language with features that make it possible to write these queries easily. It also includes libraries for using queries with the most common types of data sources like SQL database, DataSets and XML files. This article requires some basic knowledge of LINQ and C# 3.0, so I recommend looking at the LINQ Overview available from the official project web site before reading the article. LINQ includes extensions for C# and VB.NET, but there are no plans for supporting LINQ in C++/CLI. The goal of the CLinq project is to allow using part of LINQ's functionality from C++/CLI. Thanks to a very powerful operator overloading mechanism in C++/CLI it is possible to enable use of LINQ in SQL for accessing SQL databases in C++/CLI, as well as some other LINQ uses. I will first demonstrate how the same database query looks in C# 3.0 and C++/CLI. Then we will look at CLinq in more detail. The following query, written in C# 3.0, uses the Northwind database and returns name of contact and company for all customers living in London: // create connection to database
NorthwindData db = new NorthwindData(".. connection string ..");
// declare database query
var q =
from cvar in db.Customers
where cvar.City == "London"
select cvar.ContactName + ", " + cvar.CompanyName;
// execute query and output results
foreach(string s in q)
Console.WriteLine(s);
Now, let's look at the same query written in C++/CLI using CLinq. It is a bit more complex, but this is the price for implementing it as a library instead of modifying the language: // create connection to database
NorthwindData db(".. connection string ..");
// declare database query
Expr<Customers^> cvar = Var<Customers^>("c");
CQuery<String^>^ q = db.QCustomers
->Where(clq::fun(cvar, cvar.City == "London"))
->Select(clq::fun(cvar,
cvar.ContactName + Expr<String^>(", ") + cvar.CompanyName));
// execute query and output results
for each(String^ s in q->Query)
Console::WriteLine(s);
LINQ and C++/CLI overviewIn this section, I'll very shortly recapitulate a few LINQ and C++/CLI features that are important for understanding how CLinq works. If you're familiar with LINQ and C++/CLI, you can safely skip this section. Important LINQ featuresProbably the most important extensions in C# that make LINQ possible are lambda expressions. Lambda expressions are similar to anonymous delegates, but the syntax is even simpler. Lambda expressions can be used for declaring functions inline and you can pass them as a parameter to methods. There is, however, one important difference to anonymous delegates: lambda expressions can be either compiled as executable code, like anonymous delegates, or as a data structure that represents the lambda expression source code. The structure is called an expression tree. Expression trees can be also compiled at runtime, so you can convert this representation to executable code. What LINQ to SQL does is take the expression tree representing the query that contains lambda expressions and converts it to the SQL query, which is sent to the SQL Server. LINQ to SQL also contains a tool called sqlmetal.exe, which generates objects that represent the database structure. So, when you're writing the queries you can work with these type-safe objects instead of having to specify database tables or columns by name. Important C++/CLI featuresNow I'd like to mention a few from the rich set of C++/CLI features. LINQ itself is available for .NET, so we'll use the ability to work with managed classes a lot in the whole project. We'll also use the ability to work with both C++ templates and .NET generics. CLinq benefits from the fact that .NET generics can be compiled and exported from an assembly, while C++ templates are interesting thanks to their template specialization support. This means that if you have a Basic CLinq featuresIn the previous example, we used the Before we start, I'll explain how the CLinq library is organized. It consists of two parts. The first part is the EeekSoft.CLinq.dll assembly, which contains core CLinq classes. You'll need to reference this assembly from your project either using project settings or with the I already mentioned the Expr and Var classesLet's look at some sample code. As you can see from the previous paragraph, the // Declare variable of type int called 'x'
Expr<int> x = Var<int>("x");
// Declare expression of type String initialized with literal
Expr<String^> str("Hello world!");
// Expression representing addition of the x variable and
// result of the method call to 'IndexOf' method.
Expr<int> expr = str.IndexOf("w") + x;
If you look at the code, you could think that the // Convert to LINQ expression
System::Expressions::Expression^ linqExpr = expr.ToLinq();
// Print string representation of LINQ expression
Console::WriteLine(linqExpr);
The result printed to the console window will be: Add("Hello world!".IndexOf("w"), x)
Lambda expressionsLet's now look at the syntax for writing lambda expressions in CLinq. Lambda expressions are represented by the generic // Declare parameter (variable) and method body (expression)
Expr<int> var = Var<int>("x");
Expr<int> expr = Expr<String^>("Hello world!").IndexOf("w") + var;
// First argument for the clq::fun function is lambda expression
// parameter, the last argument is the lambda expression body
Lambda<Func<int, int>^>^ lambda = clq::fun(var, expr);
// Print string representation of lambda..
Console::WriteLine(lambda->ToLinq());
// Compile & execute lambda
Func<int, int>^ compiled = lambda->Compile();
Console::WriteLine(compiled(100));
After executing this example, you should see the following output in the console window. The first line represents the lambda expression and the second line is the result of lambda expression invocation: x => Add("Hello world!".IndexOf("w"), x)
106
Similarly to LINQ, you can compile the CLinq expression at runtime. Actually, CLinq internally uses LINQ. This was done in the previous example using the As in LINQ, you can use only up to 4 parameters in lambda expressions. This is due to the limitations of Expr<int> x = Var<int>("x");
Expr<int> y = Var<int>("y");
Lambda<Func<int, int, int>^>^ lambda2 =
clq::fun(x, y, 2 * (x + y) );
Console::WriteLine(lambda2->Compile()(12, 9));
In this example, the body of the lambda expression isn't declared earlier as another variable, but composed directly in the Supported types and operatorsIn the previous example, I used two overloaded operators. These operators are declared in the
For a complete list of supported types with a list of methods and operators, see the generated documentation (14.9 Kb). The following example demonstrates using overloaded operators with expressions representing // Declare 'float' variable and 'double' literal
Expr<float> fv = Var<float>("f");
Expr<double> fc(1.2345678);
// Function taking 'float' and returning 'float'
Lambda<Func<float, float>^>^ foo4 = clq::fun(fv,
clq::conv<float>(Expr<Math^>::Sin(fv * 3.14) + fc) );
You can see that we're using another function from the Working with classesI already demonstrated how you can work with basic data types like Typed wrappersUsing class if the corresponding template specialization exists is fairly simple. The following example declares an expression working with the // 'String^' variable
Expr<String^> name = Var<String^>("name");
// Expression that uses 'IndexOf' and 'Substring' methods
Expr<String^> sh("Hello Tomas");
Expr<int> n = sh.IndexOf('T');
Lambda<Func<String^, String^>^>^ foo =
clq::fun(name, sh.Substring(0, n) + name);
// Print LINQ representation and execute
Console::WriteLine(foo->ToLinq());
Console::WriteLine(foo->Compile()("world!"));
In this example, we use two methods that are declared in the name => Concat(new [] {"Hello Tomas".
Substring(0, "Hello Tomas".IndexOf(T)), name})
Hello world!
Indirect member accessTo demonstrate the second approach, we'll first define a new class with a sample property, method and static method. You can also invoke static properties: // Sample class that we'll work with
ref class DemoClass
{
int _number;
public:
DemoClass(int n)
{
_number = n;
}
// Property
property int Number
{
int get()
{
return _number;
}
}
// Standard method
int AddNumber(int n)
{
return _number = _number + n;
}
// Static method
static int Square(int number)
{
return number * number;
}
};
Now let's get to the more interesting part of the example. We will first declare a variable of type // Construct the lambda expression
Expr<DemoClass^> var = Var<DemoClass^>("var");
Lambda<Func<DemoClass^,int>^>^ foo = clq::fun(var,
var.Prop<int>("Number") +
var.Invoke<int>("AddNumber", Expr<int>(6)) +
Expr<DemoClass^>::InvokeStatic<int>("Square", Expr<int>(100) ) );
// Compile the lambda and pass instance of 'DemoClass' as a parameter
DemoClass^ dcs = gcnew DemoClass(15);
int ret = foo->Compile()(dcs);
Console::WriteLine("{0}\n{1}", foo->ToLinq(), ret);
And the output of this example would be: var => Add(Add(var.Number, var.AddNumber(6)), Square(100))
10036
I included the output because I wanted to point out one interesting fact. You can see that there is no difference in the output whether you use generated template specialization or invoke by name. This is because if you're using invoke by name, the method or property that should be invoked is found using reflection before the LINQ expression tree is generated. This also means that if you execute the compiled lambda expression, it will call the method or property directly and not by its name. Calling constructors in projectionSo far, we've looked at calling methods and reading property values. There is one more interesting problem that I didn't write about. Sometimes you may want to create an instance of a class and return it from a lambda expression. CLinq doesn't support anything like C# 3.0's anonymous methods, but you can invoke a class constructor and pass parameters to it using the // Arguments of the lambda expression
Expr<String^> svar = Var<String^>("s");
Expr<int> nvar = Var<int>("n");
DemoCtor^ d = clq::fun(svar, nvar, clq::newobj<DemoCtor^>(svar, nvar) )
->Compile()("Hello world!", 42);
After executing this code, the Using LINQYou're now familiar with all of the CLinq features that you need to start working with data using LINQ in C++/CLI! The key for working with data is the Working with SQL DatabaseLINQ to SQL: IntroductionWe will use two tools to generate a CLinq header file with classes that will represent the database structure. The first tool is shipped as part of LINQ and is called sqlmetal /server:localhost /database:northwind /xml:northwind.xml
Once we have the XML file, we can use the clinqgen /namespace:EeekSoft.CLinq.Demo
/class:NorthwindData /out:Northwind.h $(InputPath)
Now you'll need to include the generated header file and we can start working with database. We'll first create an instance of the generated // Create database context
NorthwindData db(".. connection string ..");
// (1) Count employees
Console::WriteLine("Number of employees: {0}",
db.QEmployees->Count());
// (2) Calculate average 'UnitPrice' value
Expr<Products^> p = Var<Products^>("p");
Nullable<Decimal> avgPrice =
db.QProducts->Average( clq::fun(p, p.UnitPrice) );
Console::WriteLine("Average unit price: {0}", avgPrice);
// (3) Get first employee whose 'ReportsTo' column is NULL
Expr<Employees^> e = Var<Employees^>("e");
Employees^ boss = db.QEmployees->
Where( clq::fun(e, e.ReportsTo == nullptr) )->First();
Console::WriteLine("The boss: {0} {1}",
boss->FirstName, boss->LastName);
In the first example, we simply called the LINQ to SQL: Filtering & projectionLet's look at the more interesting sample that covers filtering -- i.e. the ref class CustomerInfo
{
String^ _id;
String^ _name;
public:
CustomerInfo([PropMap("ID")] String^ id,
[PropMap("Name")] String^ name)
{
_id=id; _name=name;
}
CustomerInfo() { }
property String^ ID
{
String^ get() { return _id; }
void set(String^ value) { _id = value; }
}
property String^ Name
{
String^ get() { return _name; }
void set(String^ value) { _name = value; }
}
};
The class has two properties -- Why is this information important? First, it will not be used in the following query, but you could write a query that constructs a collection of // DB context & variable..
NorthwindData db(".. connection string ..");
Expr<Customers^> cvar = Var<Customers^>("c");
// Query: select some information about customers living
// in country whose name starts with the letter "U"
CQuery<CustomerInfo^>^ q = db.QCustomers
->Where(clq::fun(cvar, cvar.Country.IndexOf("U") == 0))
->Select(clq::fun(cvar, clq::newobj<CustomerInfo^>(
cvar.CustomerID, cvar.ContactName +
Expr<String^>(" from ") + cvar.Country)));
// Print SQL command sent to SQL server
Console::WriteLine("\nQuery:\n{0}\n\nResults:",
q->Query->ToString());
// Print returned rows
for each(CustomerInfo^ c in q->Query)
Console::WriteLine(" * {0}, {1}", c->ID, c->Name);
This code is quite similar to the code that you usually write when working with LINQ in C# 3.0. In this sample, we first create the database context and declare a variable that will be used in the query. The query itself takes the The sample also prints the SQL command that will be generated from the query. LINQ returns the SQL command if you call the LINQ to SQL: Joins & tuplesFor the last example, I wrote a much more complex query. It first performs the This query also demonstrates a few more interesting things that we didn't need earlier. The example starts with two The query returns the // First declare type for storing Customer and her Orders
typedef IEnumerable<Orders^> OrdersCollection;
typedef Tuple<Customers^, OrdersCollection^> CustomerOrders;
// Connect to DB and declare variables
NorthwindData db(".. connection string ..");
Expr<Customers^> c = Var<Customers^>("c");
Expr<Orders^> o = Var<Orders^>("o");
Expr<OrdersCollection^> orders
= Var<OrdersCollection^>("orders");
Expr<CustomerOrders^> co = Var<CustomerOrders^>("co");
// The Query
CQuery<String^>^ q = db.QCustomers
// Group customers and their orders and
// produce collection of 'CustomerOrders'
->GroupJoin(db.QOrders,
clq::fun(c, c.CustomerID),
clq::fun(o, o.CustomerID),
clq::fun<Customers^, OrdersCollection^, CustomerOrders^>
( c, orders, clq::newobj<CustomerOrders^>(c, orders) ))
// Filter only customers with order shipped to USA
// Note: 'Second' is the collection with orders
->Where( clq::fun(co, co.Second.Where(
clq::fun(o, o.ShipCountry == "USA" )).Count() > 0) )
// Projection - string concatenation
->Select( clq::fun(co,
co.First.CompanyName + Expr<String^>(", #orders = ") +
Expr<Convert^>::ToString(co.Second.Count()) ) );
Let's focus on the The second class that wasn't mentioned earlier is Project summaryCurrently, the project is in a very early phase. This means it needs more testing and also review from other people. If you find any bugs or if you think that CLinq is missing some important LINQ functionality, let me know. The project currently uses the May 2006 CTP version of LINQ, but it will be updated to support Visual Studio "Orcas" once more stable beta versions become available. The project is available at CodePlex [^], so you can download the latest version of the source code and binaries from the project site. Because I'm not a C++/CLI expert, I'm very interested in your comments and suggestions. Also, if you're willing to participate in the project, let me know! Version & updates
|
|||||||||||||||||||||||||||||||||||||||||||||||||