Showing posts with label Linq2DataSet. Show all posts
Showing posts with label Linq2DataSet. Show all posts

Thursday, September 30, 2010

Comparing different Linq Providers

I’ve been playing with Linq and different "back ends" (not using "providers" since Linq to DataSets probably doesn’t qualify).

In all three examples below, I create the automatic default model with Northwind’s Products and Categories tables. The Linq queries require a join and a filter.

Linq to DataSet

Over the past year or so, I’ve been working fairly heavily what I call Linq To DataSets, you know, Linq on top of classic ADO.NET. I have written several blog entries and made a couple of Code Camp presentations on this subject.

What makes Linq to DataSets is that it uses a disconnected data model (data is eagerly loaded).

public static NorthwindDS CreateContext()
{
    NorthwindDS ds = new NorthwindDS();
    using (var catda = new CategoriesTableAdapter())
    {
        catda.Fill(ds.Categories);
    }
    using (var proda = new ProductsTableAdapter())
    {
        proda.Fill(ds.Products);
    }
    return ds;
}

public static void DoDemo()
{
    var ds = CreateContext();
    var list = from p in ds.Products
             join c in ds.Categories on p.CategoryID equals c.CategoryID
        where c.CategoryName == "Seafood"
        orderby p.ProductName
        select new
        {
            p.ProductID,
            p.ProductName,
            p.UnitPrice,
            c.CategoryName
        };

        Console.WriteLine("DataSet Demo");
        foreach (var item in list)
        {
            Console.WriteLine(
                string.Format("{0}\t{1:c}\t{2}\t{3}", 
                    item.ProductID, 
                    item.UnitPrice,
                    item.ProductName,
                    item.CategoryName));
        }
    }
}

This one requires real code to load. And everything is loaded. If I need to, I can filter at the DataAdapter level, but that requires fancy Designer work.

Linq to SQL

On my last project, we used Linq to SQL. It is easy to develop and capable, but limited. You will use SQL Server, You will not use Stored Procedures to read and write data (it does have some stored procedure support, but not even to the level of Classic ADO.NET).

public static NorthwindL2SDataContext CreateContext()
{
    return new NorthwindL2SDataContext();
}

public static void DoDemo()
{
    var context = CreateContext();
    var list = from p in context.Products
            join c in context.Categories on p.CategoryID equals c.CategoryID
        where c.CategoryName == "Seafood"
        orderby p.ProductName
        select new
        {
            p.ProductID,
            p.ProductName,
            p.UnitPrice,
            c.CategoryName
        };

    Console.WriteLine("Linq2Sql Demo");
    foreach (var item in list)
    {
        Console.WriteLine(
            string.Format("{0}\t{1:c}\t{2}\t{3}",
                item.ProductID,
                item.UnitPrice,
                item.ProductName,
                item.CategoryName));
    }
}

Creating the context in Linq to Sql is trivial, just new it up. The Linq is really similar to the Linq to DataSets that I’ve been working with for the last year.

The cool thing is that the data isn’t gotten until I ask for it in the Linq query and only the records I ask for.

Linq to Sql’s model is really close to the table structure of the underlining database. It is less capable than the Entity Framework (below), but it is also less complicated.

Entity Framework

I have also played with the Entity Framework. It is more powerful than the others but more difficult to use than the others.

public static NorthwindEntities CreateContext()
{
    return new NorthwindEntities();
}

public static void DoDemo()
{
    var ds = CreateContext();
    var list = from p in ds.Products
        where p.Categories.CategoryName == "Seafood"
        orderby p.ProductName
        select new
        {
            p.ProductID,
            p.ProductName,
            p.UnitPrice,
            p.Categories.CategoryName
        };

    Console.WriteLine("EntityFramework Demo");
    foreach (var item in list)
    {
        Console.WriteLine(
            string.Format("{0}\t{1:c}\t{2}\t{3}",
                item.ProductID,
                item.UnitPrice,
                item.ProductName,
                item.CategoryName));
    }
}

Again it is simple to make a new context.

The Entity Framework supports lazy loading and using stored procedures for Insert, Update and Delete operations.

Notice that there is no join in this query, the Category is imbedded in to the Products. The Entity Framework can work with data models that differ from the underlining data store.

The same treatment is applied to Mongo DB

Monday, May 17, 2010

Portland Code Camp and Me

I will be presenting Introduction to Linq and Linq2DataSets an the Portland Code Camp from 9:00 to 10:15 AM on Saturday, May 22, 2010 (Session 0x01) in Room 13.

Thursday, April 08, 2010

Tuesday, March 30, 2010

My Boise Code Camp Presentation Notes

If you are looking for my Linq2DataSets presentation from Saturday's code camp, I am in the progress of getting them ready and finding a place to post item.

Monday, March 15, 2010

Group By doesn’t appear to work in Linq2DataSet

As I’m working on my presentation for Boise Code Camp, I’ve come across some issues trying to demonstrate Group By. I haven’t been able to make a Group By query work. When I run the same query using Linq2Sql, it works just fine.

For example, given the following code (in VB 10):

Dim MyList = From o In myData.Orders _
    Join od In myData.Order_Details On o.OrderID Equals od.OrderID _
    Join e In myData.Employees On o.EmployeeID Equals e.EmployeeID _
    Group By FullOrder = New With _
        { _
            .OrderId = od.OrderID, _
            .EmployeeName = (e.FirstName & " " & e.LastName), _
            .ShipCountry = o.ShipCountry, _
            .OrderDate = o.OrderDate _
        } _
    Into Amount = Sum(od.Quantity * od.UnitPrice) _
    Where FullOrder.ShipCountry = "USA" _
    Order By FullOrder.OrderId _
    Select FullOrder.OrderId, _
        FullOrder.OrderDate, _
        FullOrder.EmployeeName, _
        Amount

For Each x In MyList
    Console.WriteLine( _
        String.Format( _
           "{0}; {1:d}; {2}: {3:c}", _
           x.OrderId, _
           x.OrderDate, _
           x.EmployeeName, _
           x.Amount))
Next

When myData is a DataSet: I get:

10262; 7/22/1996; Laura Callahan: $204.00
10262; 7/22/1996; Laura Callahan: $360.00
10262; 7/22/1996; Laura Callahan: $60.80
10269; 7/31/1996; Steven Buchanan: $120.00
10269; 7/31/1996; Steven Buchanan: $556.00
10271; 8/1/1996; Michael Suyama: $48.00
10272; 8/2/1996; Michael Suyama: $388.80
10272; 8/2/1996; Michael Suyama: $400.00
10272; 8/2/1996; Michael Suyama: $667.20

There is more than 1 entry for each OrderId, the query failed to group properly. Order 10262 has 3 lines corresponding to the 3 line items of the order.

If I run the same Linq query with myData as a Linq2Sql DataContext, I get:

10262; 7/22/1996; Laura Callahan: $624.80
10269; 7/31/1996; Steven Buchanan: $676.00
10271; 8/1/1996; Michael Suyama: $48.00
10272; 8/2/1996; Michael Suyama: $1,456.00

Here everything is grouped together and Amount is totaled. Order 10262 has 1 entry as I would expect.

There may be a slightly more complex way to make this query work using the DataSet and I haven’t found it, but right now, it doesn’t look good for Linq2DataSets in this area.

Tuesday, February 23, 2010

Loading Object Graphs with Linq2DataSets

The data base likes to keep data in relational tables and the middle tier likes to present the data in object graphs where the details are stored within the patient object, so everything is close at hand. So how do you transform the data from tables to graphs? I use Linq2DataSets.

The Stored Procedure

Suppose you are asked to load an object graph with the following stored procedure (using Northwind, my favorite database of all times):

-- This procedure returns 2 results sets the first includes all of the Orders and 
-- the second all of the Order Details (regardless of order).
CREATE PROCEDURE dbo.GetAllOrders
AS
SELECT o.OrderID, o.CustomerID, o.EmployeeID, o.OrderDate, o.RequiredDate, 
    o.ShippedDate, o.ShipVia, o.Freight, o.ShipName, o.ShipAddress, o.ShipCity, 
    o.ShipRegion, o.ShipPostalCode, o.ShipCountry 
  FROM [Orders] o

SELECT od.OrderID, od.ProductID, od.UnitPrice, od.Quantity, od.Discount
  FROM [Order Details] od

In real life, I would probably have parameters to limit the result sets to the data that I really want. For the purpose of demonstration, it is helpful to process a lot of data.

Getting the data into the Object Graphs

Now, suppose you want to load this data into Object Graphs. In my example, I have an Order class that includes a list of its Order Details (a real production SP would probably include parameters to filter the data returned; you don’t want to load all the orders for a real company).

The SP returns the two lists that you will have to stitch together yourself in code:

/// <summary>
/// Loads all the orders into a list of Order objects
/// </summary>
public static List GetAllOrders(OrderDS ds)
{
    return new List(
        from orders in ds.Orders
        select new Order(
            orders.OrderID,
            orders.CustomerID,
            orders.EmployeeID,
            orders.OrderDate,
            orders.RequiredDate,
            !orders.IsShippedDateNull() ? orders.ShippedDate : (DateTime?)null,
            orders.ShipVia,
            orders.Freight,
            orders.ShipName,
            orders.ShipAddress,
            orders.ShipCity,
            !orders.IsShipRegionNull() ? orders.ShipRegion : string.Empty,
            !orders.IsShipPostalCodeNull() ? orders.ShipPostalCode : string.Empty,
            orders.ShipCountry,
            getOrderDetails(orders.OrderID, ds)));
}
/// <summary>
/// Loads the order details associated with a given order into a list of 
/// OrderDetal objects
/// </summary>
public static List getOrderDetails(int orderId, OrderDS ds)
{
    return new List(
        from orderDetails in ds.OrderDetails
        where orderDetails.OrderID == orderId
        select new OrderDetail(
            orderDetails.OrderID,
            orderDetails.ProductID,
            orderDetails.UnitPrice,
            orderDetails.Quantity,
            orderDetails.Discount));
}

From the above code, I would like to point out

  • GetAllOrders() calls getOrderDetails() from within the linq query. When you call the Orders constructor, you can call any code you would call in any other constructor.
  • Even though I’m in the middle of a Linq query in GetAllOrders(), I can create a second Linq query in getOrderDetails using the same DataSet.
  • Since we are using DataSets and other classic ADO.NET objects, we need to call IsFooNullable() to avoid an InvalidCastException.
  • When I’m passing null to the Order constructor, I need to cast it to the correct type.

Appendix

Here are the classes that I loaded the in the code above

public class Order
{
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    public int EmployeeID { get; set; }
    public DateTime OrderDate { get; set; }
    public DateTime RequiredDate { get; set; }
    public DateTime? ShippedDate { get; set; }
    public int ShipVia { get; set; }
    public decimal Freight { get; set; }
    public string ShipName { get; set; }
    public string ShipAddress { get; set; }
    public string ShipCity { get; set; }
    public string ShipRegion { get; set; }
    public string ShipPostalCode { get; set; }
    public string ShipCountry { get; set; }
    public List Details { get; set; }

    public Order(int orderID, string customerID, int employeeID, DateTime orderDate, 
        DateTime requiredDate, DateTime? shippedDate, int shipVia, decimal freight, 
        string shipName, string shipAddress, string shipCity, string shipRegion, 
        string shipPostalCode, string shipCountry, List details)
    {
        this.OrderID = orderID;
        this.CustomerID = customerID;
        this.EmployeeID = employeeID;
        this.OrderDate = orderDate;
        this.RequiredDate = requiredDate;
        this.ShippedDate = shippedDate;
        this.ShipVia = shipVia;
        this.Freight = freight;
        this.ShipName = shipName;
        this.ShipAddress = shipAddress;
        this.ShipCity = shipCity;
        this.ShipRegion = shipRegion;
        this.ShipPostalCode = shipPostalCode;
        this.ShipCountry = shipCountry;
        this.Details = details;
    }
}
public class OrderDetail
{
    public int OrderID { get; set; }
    public int ProductID { get; set; }
    public decimal UnitPrice { get; set; }
    public int Quantity { get; set; }
    public float Discount { get; set; }

    public OrderDetail(int orderID, int productID, decimal unitPrice, int quantity, 
        float discount)
    {
        this.OrderID = orderID;
        this.ProductID = productID;
        this.UnitPrice = unitPrice;
        this.Quantity = quantity;
        this.Discount = discount;
    }
}

And GetDataSet() the function that loads the DataSet

/// <summary>
/// Loads OrderDS using the SP GetAllOrders
/// </summary>
public static OrderDS GetDataSet()
{
    var ds = new OrderDS();
    SqlConnection conn = new SqlConnection(CONNECTION_STRING);

    using (SqlCommand cmd = new SqlCommand("GetAllOrders", conn))
    {
        cmd.CommandType = System.Data.CommandType.StoredProcedure;

        SqlDataAdapter DA = new SqlDataAdapter(cmd);
        DA.TableMappings.Add("Table", "Orders");
        DA.TableMappings.Add("Table1", "OrderDetails");

        DA.Fill(ds);
    }
    return ds;
}

Since I’m too lazy to do graphics on this blog, I will leave it to the reader to make the actual DataSet.

Saturday, January 30, 2010

Linq2DataSet replaces the DataView

Remember the ADO.NET DataView? It is the old-fashioned way to filter an existing DataSet from the days of ADO.NET 1.0. They are useful for filtering and sorting DataSets that you already have on hand for other reasons.

Let’s say you want a list of all the Northwind employees who live in the United States sorted by LastName:

// If you use DataSets you can probably write your own EmployeeDS & SetupData():
EmployeeDS db = SetupData();

// Filter by Country = “USA”: Sort by LastName
DataView dv = new DataView(
    db.Employees,
    "Country = 'USA'",  // The Filter
    "LastName",  // Sort Order
    DataViewRowState.CurrentRows);

// the DataView consists of a collection of DataRowView, so you have to cast back
// to get to the strongly typed DataRows EmployeeDS.EmployeesRow
foreach (DataRowView dvRow in dv)
{
    EmployeeDS.EmployeesRow item = (EmployeeDS.EmployeesRow)dvRow.Row;
    Console.WriteLine(item.LastName);
}

In the code above, the filter and the sort are in strings, so the compiler can’t find errors; if the Employee table doesn’t have a “Country” field, the above code will compile but throw an EvaluateException at runtime.

In the foreach loop you need to cast the DataRowView into the strongly typed DataRow before you can access the properties. If you really don’t want to do the cast, you could use “dvRow["LastName"]” to get the last name, but that would involve another “magic string”.

Now, let’s get the same list in Linq2DataSet:

// If you use DataSets you can probably write your own EmployeeDS & SetupData():
EmployeeDS db = SetupData();

OrderedEnumerableRowCollection<EmployeeDS.EmployeesRow> linqRows = 
    from e in db.Employees
    where e.Country == "USA"
    orderby e.LastName
    select e;

// Linq2DataSet gives you a collection of strongly typed DataRows.
// You don’t have to cast to get to the LastName element here:
foreach (EmployeeDS.EmployeesRow item in linqRows)
    Console.WriteLine(item.LastName);

In this code, the only thing in a string is “USA”; the compiler will freak out if you don’t have a “Country” field. You don’t have to rely on angry user to find these bugs.

In the foreach loop you don’t have to cast, the output of the Linq query is a collection of strongly typed DataRows (however it isn’t a DataTable, but a DataRow can’t belong to more than one DataTable anyway).

If you really only want LastNames you can get only LastNames:

// If you use DataSets you can probably write your own EmployeeDS & SetupData():
EmployeeDS db = SetupData();

// Get me only LastNames as a collection of the field’s data type (in this case, string):
EnumerableRowCollection<string> LastNames = from e in db.Employees
        where e.Country == "USA"
        orderby e.LastName
        select e.LastName;

// Loop through the strings
foreach (string LastName in LastNames)
    Console.WriteLine(LastName);

Here Linq2DataSet gives us back a collection of strings because in the DataSet LastName is a string; if you ask for HireDate, you will get a collection of DateTimes.

Suppose you want a both LastName and FirstName you could do this (with Anonymous Types):

// If you use DataSets you can probably write your own EmployeeDS & SetupData():
EmployeeDS db = SetupData();

// Here we need to use “var” because the type doesn’t exist until the
// compiler creates it:
var Names = from e in db.Employees
            where e.Country == "USA"
            orderby e.LastName
            select new { e.LastName, e.FirstName };

// Here we are using the same type that the compiler created above:
foreach (var Name in Names)
    Console.WriteLine(string.Format("{0}, {1}",Name.LastName, Name.FirstName));

To make this work, C# created an anonymous type to put the results of the Linq query. Anonymous Types are strong types that are created at compile time; they just don’t have names and there are no class or struct declaration needed in the code.

(On my machine, according to the debugger, Names is a System.Data.EnumerableRowCollection<<>f__AnonymousType0<string,string>>)

More about using Anonymous Types in later posts. . .

Thursday, January 14, 2010

Linq2DataSet Introduction

Lately I’ve been working for a client that recently moved an existing application to VS 2008 / .NET 3.5. The app uses some DataSets, so I’ve been playing to what I call Linq2DataSet (or Linq to DataSet).

Getting Started

To use Linq2DataSet will need to use the .NET 3.5 runtime and, in addition to the usual ADO.NET assemblies you will need to add a reference to System.Data.DataSetExtensions.dll.

Quick comparison to Linq2Sql

// Linq To Sql
int Linq2SqlGetCount()
{
     BigTableSQLDataContext data = new BigTableSQLDataContext();
     return (from x in data.Big_Tables select x).Count();
}
// Linq to DataSet
int Linq2DataSetGetCount()
{
     BigTableDS data = new BigTableDS();
     Big_TableTableAdapter da = new Big_TableTableAdapter();
     da.Fill(data.Big_Table);

     return (from x in data.Big_Table select x).Count();
}

The code isn’t that much different. There is a little more code to setup the DataSet than the SQL DataContext. No big deal.

There is a big gotcha. I ran both functions against a table with 20,000 records. The DataSet is fully loaded before anything can happen. In the code above, the Linq2Sql function runs in < 1 second and the Linq2DataSet version takes about a minute and a half.

In Linq2DataSet, all 20,000 are loaded into the DataSet before the Linq code is executed. In Linq2Sql, the engine executes the Linq and generates the SQL to get the data (probably something like SELECT COUNT(*) FROM Big_Table).

Yes, this looks bad, however if you already have the DataSet hanging around, Linq2DataSet may be a Mega-Cool way to solve your problems. For the record, I would not recommend Linq2DataSet for new “Green Field” projects, however, if you already have the DataSet lying around, Linq can make it easy to get that little bit of extra info you need. Linq2DataSets is also a way to get into Linq quickly then you can learn about Linq2Sql or the Entity Framework.

More to come…