Sunday, April 13, 2008
kick it on DotNetKicks.com

beach


Vacation

If you noticed that I wasn't around much on the forums this past week you weren't imagining things. I was on vacation in Palm Beach Gardens, Florida. It's truly paradise on earth. We went to Juno Beach almost every day (don't let the Jones Beach shirt fool you). I was there with my wife and three children and we played in the waves all week, it was awesome. Maybe I'll post some other photos later. I have to admit it's a little depressing to be back in Indianapolis and it's only 39 degree's here (it was in the mid 80's all week in Florida).

During the evenings I was able to read the new LINQ in Action book published by Manning. I was particularly interested in the LINQ to SQL functionality and how it could be applied to EntitySpaces. Well, it's been less than 24 hours since I've been home and I already have LINQ to SQL working with EntitySpaces. The LINQ in Action book is pretty good as a LINQ reference but it really kind of ticked me off otherwise. The strawman it setup in order to tear down was really blatant. I think I could take nearly all of the the supposed samples it uses to show how much better LINQ is over ADO.NET and provide appropriate samples using EntitySpaces and it would be far more intuitive. I might just do that later to make a point.

Okay, enough ranting, let's focus on using EntitySpaces with LINQ to SQL (for Microsoft SQL Server only so far).

EntitySpaces and LINQ to SQL

Adding support to EntitySpaces for LINQ to SQL was pretty easy. I added [Table] and [Column] attributes from the System.Data.Linq.Mapping namespace to my "generated" Employee entity like so:


[Table(Name="Employee")]
public partial class Employee : esEmployee
{
    [Column(IsPrimaryKey = true, CanBeNull = false)]
    public override int? EmployeeID
    {
        get { return base.EmployeeID;  }
        set { base.EmployeeID = value; }
    }

    [Column]
    public override string LastName
    {
        get { return base.LastName;  }
        set { base.LastName = value; }
    }

    // more properties here ...
}

I had to make pass through's for the properties however. Recall that the properties are in the generated esEmployee class not the generated Employee class and the esEmployee class is abstract. LINQ cannot instantiate the abstract esEmployee class and it appears that the [Table] and [Column] attributes must be on the same physical class (no inheritance allowed). So I made overrides of the properties in my Employee class, applied the attributes, and I was ready to test. For more information on the inheritance LINQ limitation see this MSDN forum post.

After making the above changes it was time to test it and see if worked. Below is my test code.

DataContext dataContext = new DataContext("User ID=sa;password=; ... ");

var employees = dataContext.GetTable<Employee>();
var query = from employee in employees where employee.Age < 50 select employee;

foreach (Employee emp in query)
{
    Console.WriteLine(emp.Age.ToString());
}

As I suspected and hoped, it worked. However, semantically this is a little different that using our DynamicQuery API. I didn't get back an EntitySpaces EmployeesCollection class. Instead I was handed a System.Linq.IQueryable<BusinessObjects.Employee> interface which I could then use to enumerate over the individual Employee objects. However, these were my true EntitySpaces objects which was kind of cool. This is all still exploratory and is slower than using the EntitySpaces DynamicQuery API but still offers valuable functionality. We're pushing hard on the next ES2008 beta and as time allows we will sneak in some of these cool new features as time allows. My guess is this will make the first release of ES2008.

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, a Mono application, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net

posted on Sunday, April 13, 2008 12:32:08 PM (Eastern Standard Time, UTC-05:00)  #   
 Saturday, March 22, 2008

A Quick "Cast" Sample

kick it on DotNetKicks.com

The casting logic is now in-place for the EntitySpaces 2008 March 30 Beta release. Let's look at a few quick examples, so you can get a feel for how this new feature works. These examples are not real world scenarios, and were chosen to merely demonstrate the syntax.

EmployeeCollection coll = new EmployeeCollection();
EmployeeQuery q = coll.Query;

q.Select
(
    (
        (q.LastName + ", " + q.FirstName).Trim() + " [" + (esString)q.Age + "]"
    )
    .ToUpper().As("FirstName")
);

if (coll.Query.Load())
{
    foreach (Employee emp in coll)
    {
        string fn = emp.FirstName;
    }
}

The sample above is a little over the top, but we wanted to show you some of the features. Notice that we Trim the results of ("LastName+ ", " + FirstName") by placing parenthesis around it, and then using Trim. However, we're not finished yet. We then continue by concatenating the Age. We cast Age into a string using the (esString) cast operator. You can use our natural language syntax throughout the EntitySpaces DynamicQuery API. 

The resulting values for FirstName would be as follows:

SMITH, JOHN [30]
DOE, JANE [20]

The SQL generated for the Query above would be as follows:

SELECT UPPER((((LTRIM(RTRIM((([LastName]+', ')+[FirstName])))+' [')+CAST([Age] AS nvarchar))+']')) AS 'FirstName'  FROM [Employee]

If you match-up the color coding from the EntitySpaces DynamicQuery to the resulting SQL, you can see that the (esString) resulted in a call to Cast(). Note how at various points we were able to group things with parenthesis to apply further processing, for example, Trim and ToUpper. Finally, we end the query by providing our virtual column with an alias. We even aliased it back to our FirstName column, so we could access it directly with a property instead of using GetColumn().

Supported Cast Operators

The cast operators supported are as follows:

  • esBoolean
  • esByte
  • esChar
  • esDateTime
  • esDecimal
  • esDouble
  • esGuid
  • esInt16
  • esInt32
  • esInt64
  • esSingle
  • esString

More Advanced Casting

In cases where you need a little more control, you can use the Cast() method in the query explicitly. There are three methods available.

  1. Cast(esCastType castType)                                       - Really offers nothing over the inline casting shown above
  2. Cast(esCastType castType, int length)                     - useful when casting strings
  3. Cast(esCastType castType, int precision, int scale) - useful for numeric/decimal types

Forgive us, if these samples aren't quite real world samples:

Employee emp = new Employee();
emp.Query.Select(emp.Query.Age.Cast(esCastType.Decimal, 8, 4).As("CastColumn"));
emp.Query.es.Top = 1;

if(emp.Query.Load())
{

}

This results in:

SELECT  TOP 1 CAST([Age] AS decimal(8,4)) AS 'CastColumn'  FROM [Employee]

If you didn't need to control the precision and scale, you could just use the syntax shown below:

Employee emp = new Employee();
emp.Query.Select((esDecimal)emp.Query.Age.As("CastColumn"));
emp.Query.es.Top = 1;

if(emp.Query.Load())
{

}

This results in:

SELECT  TOP 1 CAST([Age] AS decimal) AS 'CastColumn'  FROM [Employee]

Summary

Our new casting support, combined with our new SubQuery support, should add to the horsepower already provided by EntitySpaces 2007. EntitySpaces 2008 will give you a real boost when it comes to writing your EntitySpaces applications. EntitySpaces 2008 extends the DynamicQuery API significantly. All of these features are supported for all of the EntitySpaces databases.

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, a Mono application, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net

posted on Saturday, March 22, 2008 6:10:33 PM (Eastern Standard Time, UTC-05:00)  #   
 Sunday, March 02, 2008

kick it on DotNetKicks.com

EntitySpaces, LLC will be delivering substantial SubQuery horsepower in its 2008 release. The EntitySpaces DynamicQuery API (just one of the features of using EntitySpaces) has proven to be a very powerful tool for .NET developers. Not only is the syntax very intuitive and similar to SQL itself but it is also database independent. Even more importantly, the EntitySpaces DynamicQuery API is fully supported on the Compact Framework, Mono, and in ASP.NET Medium Trust Mode. It is also supported for Microsoft SQL and SQL CE, Oracle, MySQL, PostgreSQL, VistaDB, and Microsoft Access and will run on the .NET 2.0 Framework and higher.

SelectAllExcept()

SelectAllExcept() is not really a SubQuery, just a convenient enhancement that allows you to select all except one or more listed columns.

EmployeeCollection coll = new EmployeeCollection();
coll.Query.SelectAllExcept(
    coll.Query.EmployeeID,
    coll.Query.FirstName);
coll.Query.Load();

Results:

SELECT [LastName],[Supervisor],[Age]  
FROM [ForeignKeyTest].[dbo].[Employee]

Select SubQuery

A SubQuery in a Select clause must return a single value.

OrderQuery orders = new OrderQuery("o");
OrderItemQuery details = new OrderItemQuery("oi");

orders.Select
(
    orders.OrderID,
    orders.OrderDate,
    details.Select(
        details.UnitPrice.Max())
        .Where(orders.OrderID == details.OrderID).As("MaxUnitPrice")
);

OrderCollection collection = new OrderCollection();
collection.Load(orders);

Results:

SELECT o.[OrderID],o.[OrderDate], 
    (SELECT MAX(oi.[UnitPrice]) AS 'UnitPrice'  
    FROM [ForeignKeyTest].[dbo].[OrderItem] oi 
    WHERE o.[OrderID] = oi.[OrderID]) AS MaxUnitPrice  
FROM [ForeignKeyTest].[dbo].[Order] o

This is the same as the query above, but returns all columns in the Order table, instead of just OrderID and OrderDate. Notice that the Select clause contains orders, not orders.*. The SQL produced will use the supplied alias o.*.

OrderQuery orders = new OrderQuery("o");
OrderItemQuery details = new OrderItemQuery("oi");

orders.Select
(
    orders,
    details.Select(
        details.UnitPrice.Max())
        .Where(orders.OrderID == details.OrderID).As("MaxUnitPrice")
);

OrderCollection collection = new OrderCollection();
collection.Load(orders);

Results:

SELECT o.*, 
    (SELECT MAX(oi.[UnitPrice]) AS 'UnitPrice'  
    FROM [ForeignKeyTest].[dbo].[OrderItem] oi 
    WHERE o.[OrderID] = oi.[OrderID]) AS MaxUnitPrice  
FROM [ForeignKeyTest].[dbo].[Order] o

From SubQuery

An aggregate requires a GROUP BY for each column in the SELECT that is not an aggregate. Sometimes you wish to include columns in your result set that you do not wish to group by. One way to accomplish this is by using a SubQuery in the From clause that contains the aggregate the way you want it grouped. The outer query contains the results of the aggregate, plus any additional columns.

If you use a SubQuery in a From clause, you must give the From clause its own alias (shown below as "sub"). In the outer query, to refer to an aliased element in the From SubQuery, use the inline raw SQL technique to qualify the aggregate's alias with the From clause alias, i.e., "<sub.OrderTotal>".

OrderQuery oq = new OrderQuery("o");
OrderItemQuery oiq = new OrderItemQuery("oi");

oq.Select(oq.CustID, oq.OrderDate, "<sub.OrderTotal>");
oq.From
    (
        oiq.Select(oiq.OrderID,
            (oiq.UnitPrice * oiq.Quantity).Sum().As("OrderTotal"))
            .GroupBy(oiq.OrderID)
    ).As("sub");
oq.InnerJoin(oq).On(oq.OrderID == oiq.OrderID);

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Results:

SELECT o.[CustID],o.[OrderDate],sub.OrderTotal  
FROM (
    SELECT oi.[OrderID],
    SUM((oi.[UnitPrice]*oi.[Quantity])) AS 'OrderTotal'  
    FROM [ForeignKeyTest].[dbo].[OrderItem] oi 
    GROUP BY oi.[OrderID]) AS sub 
INNER JOIN [ForeignKeyTest].[dbo].[Order] o 
ON o.[OrderID] = sub.[OrderID]

Where SubQuery

In and NotIn are two of the most common operators used in a Where SubQuery. The following produces a result set containing Territories that an Employee is not associated with.

// SubQuery of Territories that Employee 1 is assigned to.
EmployeeTerritoryQuery etq = new EmployeeTerritoryQuery("et");
etq.Select(etq.TerrID);
etq.Where(etq.EmpID == 1);

// Territories that Employee 1 is not assigned to.
TerritoryQuery tq = new TerritoryQuery("t");
tq.Select(tq.Description);
tq.Where(tq.TerritoryID.NotIn(etq));

TerritoryCollection collection = new TerritoryCollection();
collection.Load(tq);

Results:

SELECT t.[Description]  
FROM [ForeignKeyTest].[dbo].[Territory] t 
WHERE t.[TerritoryID] NOT IN (
    SELECT et.[TerrID]  
    FROM [ForeignKeyTest].[dbo].[EmployeeTerritory] et 
    WHERE et.[EmpID] = @EmpID1) 

Exists evaluates to true, if the SubQuery returns a result set.

// SubQuery of Employees with a null Supervisor column.
EmployeeQuery sq = new EmployeeQuery("s");
sq.es.Distinct = true;
sq.Select(sq.EmployeeID);
sq.Where(sq.Supervisor.IsNull());

// If even one employee has a null supervisor,
// i.e., the above query has a result set,
// then run a list of all employees.
EmployeeQuery eq = new EmployeeQuery("e");
eq.Select(eq.EmployeeID, eq.Supervisor);
eq.Where(eq.Exists(sq));

EmployeeCollection collection = new EmployeeCollection();
collection.Load(eq);

Results:

SELECT e.[EmployeeID],e.[Supervisor]  
FROM [ForeignKeyTest].[dbo].[Employee] e 
WHERE EXISTS (
    SELECT  DISTINCT s.[EmployeeID]  
    FROM [ForeignKeyTest].[dbo].[Employee] s 
    WHERE s.[Supervisor] IS NULL) 

Join(query).On(SubQuery)

SubQueries cannot be used directly within a Join(SubQuery) clause, but they can be used within a Join(query).On(SubQuery) clause.

// Query for the Join
OrderItemQuery oiq = new OrderItemQuery("oi");

// SubQuery of OrderItems with a discount
OrderItemQuery oisq = new OrderItemQuery("ois");
oisq.es.Distinct = true;
oisq.Select(oisq.Discount);
oisq.Where(oisq.Discount > 0);

// Orders with discounted items
OrderQuery oq = new OrderQuery("o");
oq.Select(oq.OrderID, oiq.Discount);
oq.InnerJoin(oiq).On(oq.OrderID == oiq.OrderID &
    oiq.Discount.In(oisq));

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Results:

SELECT o.[OrderID],oi.[Discount]  
FROM [ForeignKeyTest].[dbo].[Order] o 
INNER JOIN [ForeignKeyTest].[dbo].[OrderItem] oi 
ON (o.[OrderID] = oi.[OrderID] AND oi.[Discount] IN  (
    SELECT  DISTINCT ois.[Discount]  
    FROM [ForeignKeyTest].[dbo].[OrderItem] ois 
    WHERE ois.[Discount] > @Discount1))

Correlated SubQuery

A correlated SubQuery is where the inner query relies on an element of the outer query. The inner select cannot run on its own. Below, the inner pq query uses the outer query's oiq.ProductID in the Where() clause.

OrderItemQuery oiq = new OrderItemQuery("oi");
ProductQuery pq = new ProductQuery("p");

oiq.Select(
    oiq.OrderID,
    (oiq.Quantity * oiq.UnitPrice).Sum().As("Total")
);
oiq.Where(oiq.ProductID
    .In(
        pq.Select(pq.ProductID)
        .Where(oiq.ProductID == pq.ProductID)
    )
);
oiq.GroupBy(oiq.OrderID);

OrderItemCollection collection = new OrderItemCollection();
collection.Load(oiq);

Results:

SELECT oi.[OrderID],SUM((oi.[Quantity]*oi.[UnitPrice])) AS 'Total'  
FROM [ForeignKeyTest].[dbo].[OrderItem] oi 
WHERE oi.[ProductID] IN (
    SELECT p.[ProductID]  
    FROM [ForeignKeyTest].[dbo].[Product] p 
    WHERE oi.[ProductID] = p.[ProductID])  
GROUP BY oi.[OrderID]

Nested SubQuery

EntitySpaces supports nesting of SubQueries. Each database vendor has their own limits on just how deep the nesting can go. EntitySpaces supports two different syntax approaches to nested SubQueries.

Traditional SQL-style syntax is most useful if you already have a query designed using standard SQL, and are just converting it to a DynamicQuery.

OrderQuery oq = new OrderQuery("o");
CustomerQuery cq = new CustomerQuery("c");
EmployeeQuery eq = new EmployeeQuery("e");

// OrderID and CustID for customers who ordered on the same date
// a customer was added, and have a manager whose 
// last name starts with 'S'.
oq.Select(
    oq.OrderID,
    oq.CustID
);
oq.Where(oq.OrderDate
    .In(
        cq.Select(cq.DateAdded)
        .Where(cq.Manager.In(
            eq.Select(eq.EmployeeID)
            .Where(eq.LastName.Like("S%"))
            )
        )
    )
);

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Results:

SELECT o.[OrderID],o.[CustID]  
FROM [ForeignKeyTest].[dbo].[Order] o 
WHERE o.[OrderDate] IN (
    SELECT c.[DateAdded]  
    FROM [ForeignKeyTest].[dbo].[Customer] c 
    WHERE c.[Manager] IN (
        SELECT e.[EmployeeID]  
        FROM [ForeignKeyTest].[dbo].[Employee] e 
        WHERE e.[LastName] LIKE @LastName1) )

Nesting by query instance name can be easier to understand and construct, if you are starting from scratch, and have no pre-existing SQL to go by. The trick is to start with the inner-most SubQuery and work your way out. The query below produces the same results as the traditional SQL-style query above. The instance names are color coded to emphasize how they are nested.

// Employees whose LastName begins with 'S'.
EmployeeQuery eq = new EmployeeQuery("e");
eq.Select(eq.EmployeeID);
eq.Where(eq.LastName.Like("S%"));

// DateAdded for Customers whose Managers are in the
// EmployeeQuery above.
CustomerQuery cq = new CustomerQuery("c");
cq.Select(cq.DateAdded);
cq.Where(cq.Manager.In(eq));

// OrderID and CustID where the OrderDate is in the
// CustomerQuery above.
OrderQuery oq = new OrderQuery("o");
oq.Select(
    oq.OrderID,
    oq.CustID
);
oq.Where(oq.OrderDate.In(cq));

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Results:

SELECT o.[OrderID],o.[CustID]  
FROM [ForeignKeyTest].[dbo].[Order] o 
WHERE o.[OrderDate] IN (
    SELECT c.[DateAdded]  
    FROM [ForeignKeyTest].[dbo].[Customer] c 
    WHERE c.[Manager] IN (
        SELECT e.[EmployeeID]  
        FROM [ForeignKeyTest].[dbo].[Employee] e 
        WHERE e.[LastName] LIKE @LastName1) )

Any, All, and Some

ANY, ALL, and SOME are SubQuery qualifiers. They precede the SubQuery they apply to. For most databases, ANY and SOME are synonymous. Usually, if you use an operator (>, >=, =, <, <=) in a Where clause against a SubQuery, then the SubQuery must return a single value. By applying a qualifier to the SubQuery, you can use operators against SubQueries that return multiple results.

Notice, below, that the ALL qualifier is set to true for the SubQuery with "cq.es.All = true;".

// DateAdded for Customers whose Manager  = 3
CustomerQuery cq = new CustomerQuery("c");
cq.es.All = true;
cq.Select(cq.DateAdded);
cq.Where(cq.Manager == 3);

// OrderID and CustID where the OrderDate is 
// less than all of the dates in the CustomerQuery above.
OrderQuery oq = new OrderQuery("o");
oq.Select(
    oq.OrderID,
    oq.CustID
);
oq.Where(oq.OrderDate < cq);

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Results:

SELECT o.[OrderID],o.[CustID]  
FROM [ForeignKeyTest].[dbo].[Order] o 
WHERE o.[OrderDate] < ALL (
    SELECT c.[DateAdded]  
    FROM [ForeignKeyTest].[dbo].[Customer] c 
    WHERE c.[Manager] = @Manager1)

Below, is a nested SubQuery. The ANY qualifier is set to true for the middle SubQuery with "cq.es.Any = true;".

// Employees whose LastName begins with 'S'.
EmployeeQuery eq = new EmployeeQuery("e");
eq.Select(eq.EmployeeID);
eq.Where(eq.LastName.Like("S%"));

// DateAdded for Customers whose Managers are in the
// EmployeeQuery above.
CustomerQuery cq = new CustomerQuery("c");
cq.es.Any = true;
cq.Select(cq.DateAdded);
cq.Where(cq.Manager.In(eq));

// OrderID and CustID where the OrderDate is 
// less than any one of the dates in the CustomerQuery above.
OrderQuery oq = new OrderQuery("o");
oq.Select(
    oq.OrderID,
    oq.CustID
);
oq.Where(oq.OrderDate < cq);

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Results:

SELECT o.[OrderID],o.[CustID]  
FROM [ForeignKeyTest].[dbo].[Order] o 
WHERE o.[OrderDate] < ANY (
    SELECT c.[DateAdded]  
    FROM [ForeignKeyTest].[dbo].[Customer] c 
    WHERE c.[Manager] IN (
        SELECT e.[EmployeeID]  
        FROM [ForeignKeyTest].[dbo].[Employee] e 
        WHERE e.[LastName] LIKE @LastName1) )

Some Final Thoughts

The examples given above were designed to demonstrate (and test) usage in a variety of settings. They are not necessarily the simplest, or most efficient, way to achieve the desired result set. Think of them as an API usage guide, not as design guidelines. Most SubQueries can be re-written as Joins, and most Joins can be re-written as SubQueries. If, while coding, you are having trouble conceptualizing one approach, then try the other.

Technically, a JOIN's ON clause can take almost any where_condition, and EntitySpaces supports many of these, including SubQueries. But, we agree with most authorities on the subject, that the ON clause should be reserved for the conditions that relate the two tables. All other conditional statements should be placed in a WHERE clause. Typically, the ON clause only contains a column from each table and a comparison operator.

For example, take the Join(query).On(SubQuery) example above. The much simpler query below returns the same result set. Its Where() clause not only simplifies the On() clause, but eliminates the SubQuery completely.

// Query for the Join
OrderItemQuery oiq = new OrderItemQuery("oi");

// Orders with discounted items
OrderQuery oq = new OrderQuery("o");
oq.Select(oq.OrderID, oiq.Discount);
oq.InnerJoin(oiq).On(oq.OrderID == oiq.OrderID);
oq.Where(oiq.Discount > 0);

OrderCollection collection = new OrderCollection();
collection.Load(oq);

Now might be a good time to point out that we are not in the complex query creation business. If you post a bunch of SQL, and ask, "How do I do this in EntitySpaces?", you may, or may not, get much of a response. The Peer to Peer forums are your best bet for that kind of post. On the other hand, if you post an EntitySpaces DynamicQuery that is throwing an exception, or producing inaccurate results, you will find us eager to resolve the issue.

We are going to need your help, however. As always, post the database and EntitySpaces version, the relevant code, the exception (if any), and the stack trace. We do not have your database schema, so if you can narrow down the issue using Northwind or pubs, that is a tremendous help. Posting obj.Query.es.LastQuery can provide valuable clues.

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, a Mono application, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net

posted on Sunday, March 02, 2008 10:40:32 PM (Eastern Standard Time, UTC-05:00)  #   
 Thursday, February 07, 2008
kick it on DotNetKicks.com

We're busy working on EntitySpaces 2008 (ES2008) and thought we would demonstrate to you how subqueries will work. If you are familiar with our Join syntax then subqueries will feel like a perfect extension. The EntitySpaces 2008 architecture allows subqueries on the Select, the Where, the From, and the Join statement. Before we look at subqueries take a moment to review how our join syntax works. The EntitySpaces DynamicQuery API has proven to be very popular, so much so that other architectures are following suit.

EntitySpaces Join Syntax Review

Notice that we create three query objects and then relate them through InnerJoins and finally ask the CustomerCollection to load the query.

CustomerQuery cust = new CustomerQuery ("c");
OrderQuery order = new OrderQuery ("o");
OrderItemQuery item = new OrderItemQuery ("oi");

cust.Select(cust.CustomerName, (item.Quantity * item.UnitPrice).Sum().As("TotalSales"));
cust.InnerJoin(order).On(order.CustID == cust.CustomerID);
cust.InnerJoin(item).On(item.OrderID == order.OrderID);
cust.GroupBy(cust.CustomerName);
cust.OrderBy("TotalSales", esOrderByDirection.Descending);

CustomerCollection coll = new CustomerCollection ();
coll.Load(cust);

Pretty sweet and that is supported in ES2007 which is shipping now. But we're just getting started. Now let's look at some subquery samples.

EntitySpaces SubQuery in the Select Statement

This subquery determines the Maximum Unit Price for each order in the system.

OrdersQuery orders = new OrdersQuery("orders");
OrderDetailsQuery details = new OrderDetailsQuery("details");

orders.Select
(
    orders.OrderID,
    orders.OrderDate,
    details.Select(details.UnitPrice.Max()).Where(orders.OrderID == details.OrderID).As("MaxUnitPrice")
);
OrdersCollection coll = new OrdersCollection();
coll.Load(orders);

The resulting SQL generated by our SqlClientProvider is as follows.

SELECT orders.[OrderID],orders.[OrderDate],
(
    SELECT MAX(details.[UnitPrice]) AS 'UnitPrice' 
    FROM [Northwind].[dbo].[Order Details] details
    WHERE orders.[OrderID] = details.[OrderID]
) AS MaxUnitPrice 
FROM [Northwind].[dbo].[Orders] orders

The resulting data is as follows:

OrderID     OrderDate               MaxUnitPrice
----------- ----------------------- --------------
10248       1996-07-04 00:00:00.000    34.80
10249       1996-07-05 00:00:00.000    42.40
10250       1996-07-08 00:00:00.000    42.40
10251       1996-07-08 00:00:00.000    16.80
10252       1996-07-09 00:00:00.000    64.80
10253       1996-07-10 00:00:00.000    16.00
10254       1996-07-11 00:00:00.000    19.20
10255       1996-07-12 00:00:00.000    44.00
10256       1996-07-15 00:00:00.000    26.20

EntitySpaces SubQuery in the From Clause

Now let's look at using a subquery used in the From clause which returns the average freight per company.

OrdersQuery o = new OrdersQuery("o");

CustomersQuery c = new CustomersQuery("c");
c.Select(c.CompanyName, c.Country, o.Freight);
c.From
    (
        o.Select(o.CustomerID, o.Freight.Avg()).GroupBy(o.CustomerID)
    ).As("s");
c.InnerJoin(c).On(c.CustomerID == o.CustomerID);

CustomersCollection coll = new CustomersCollection();
coll.Load(c);

The resulting SQL generated by our SqlClientProvider is as follows.

SELECT c.[CompanyName],c.[Country],s.[Freight] 
FROM
(
    SELECT o.[CustomerID],AVG(o.[Freight]) AS 'Freight' 
    FROM [Northwind].[dbo].[Orders] o
    GROUP BY o.[CustomerID]
) AS s
INNER JOIN [Northwind].[dbo].[Customers] c
ON c.[CustomerID] = s.[CustomerID]

The resulting rows look like this:

CompanyName                              Country         Freight
---------------------------------------- --------------- -------
Alfreds Futterkiste                      Germany         37.5966
Ana Trujillo Emparedados y helados       Mexico          24.355
Antonio Moreno Taquería                  Mexico          38.36
Around the Horn                          UK              36.3038
Berglunds snabbköp                       Sweden          86.64
Blauer See Delikatessen                  Germany         24.0371
Blondesddsl père et fils                 France          56.6963
Bólido Comidas preparadas                Spain           63.7233
Bon app'                                 France          79.8747
Bottom-Dollar Markets                    Canada          56.7107

EntitySpaces SubQuery in the Where Clause

Here we have returned a list of countries where customers live where there is no supplier located in that country. Here we pass a subquery to the NotIn() operator.

SuppliersQuery supp = new SuppliersQuery("supp");
supp.es.Distinct = true;
supp.Select(supp.Country);

CustomersQuery cust = new CustomersQuery("cust");
cust.es.Distinct = true;
cust.Select(cust.Country);
cust.Where(cust.Country.NotIn(supp));

CustomersCollection coll = new CustomersCollection();
coll.Load(cust);

The resulting SQL generated by our SqlClientProvider is as follows:

SELECT DISTINCT cust.[Country] 
FROM [Northwind].[dbo].[Customers] cust
WHERE cust.[Country] NOT IN
(
    SELECT DISTINCT supp.[Country] 
    FROM [Northwind].[dbo].[Suppliers] supp
)

The resulting rows look like this:

Country
---------------
Argentina
Austria
Belgium
Ireland
Mexico
Poland
Portugal
Switzerland
Venezuela

Although not shown here it is also possible to use a subquery in the Join statement. Of course, subqueries can be nested in various ways too. We hope this gives you a glimpse of what is coming in ES2008. We are making great progress. The beauty of course is this will work on all of the databases we support.

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net

posted on Thursday, February 07, 2008 10:31:21 PM (Eastern Standard Time, UTC-05:00)  #   
 Saturday, January 19, 2008
kick it on DotNetKicks.com

This is only a preview of some of the big things planned for ES2008, this is not the roadmap. There are many items such as esDataSource improvements and various fixes that will be covered in the roadmap which will be published soon. ES2008 is going to roll out a little differently than have our past releases. Rather than rolling out releases at a rapid rate we are going to have two main releases for ES2008. This will result in greater productivity for the EntitySpaces team, and greater functionality for our customers, as the act of cutting a release itself burns a lot of cycles. We have dates in mind for these releases and they will published in the official roadmap. We have created a special place in our forums to talk about this preview and our upcoming roadmap when released, that is the place to make your wishes and concerns known. Let's take a very high level look at some of these key features.

New Code Generation Model

The EntitySpaces team has decided a new code generation model is needed for several reasons. One of the main reasons we have chosen a new model is to make it easier for customers to extend EntitySpaces. However, this new model will also make development much easier for the EntitySpaces team as well. The new EntitySpaces code generator will operate as an ASP.NET website hosted on your local machine or on a server within in your organization. However, even though we have chosen this approach we are not precluded from running these same templates under the CodeDOM model in the future as CodeSmith templates can use code behind pages. Here are some of the advantages of going to a pure ASP.NET website based code generation model.

  • No need to write our own code generator as ASP.NET itself can execute the templates naturally.
  • Developers are already familiar with this model.
  • Full Intellisense including Intellisense for our new esMetadataEngine.
  • Easy debugging, just step right into your templates while in Visual Studio (including Express versions).
  • Custom Objects can be formed that are different than what the database schema describes (true mapping).
  • Better template recording and playback capabilities.

Take a look at this high level diagram

image

We have received some very good input on our forums that had an impact on the feature set for ES2008, specifically, the concept of a template stack. The EntitySpaces Core templates will be required and generate the main classes. The EntitySpaces Optional templates are just that, optional, and will generate things such as our WCF proxy classes. The Custom User templates are templates written by our customers. These templates can be created, shared, even sold by 3rd party developers. We hope to have a place where customers can publish their templates, and our code generator will be able to display those for you and you can then optionally pull them down for inclusion into your template stack. The concept behind the template stack is that customers can both extend EntitySpaces with new functionality, and replace standard functionality that we provide in the optional templates, by swapping our templates out and theirs in. The idea is that users can really begin to customize EntitySpaces without the fear of losing edits to templates. These templates are written as .ASCX controls with code behind pages, and therefore, support for CodeSmith is one of our goals as well. Our templates will be written in C# as they are now, however EntitySpaces will continue to generate both C# and VB.NET architectures as always.

Our new esMetadataEngine will be different from the MyMeta engine in MyGeneration. It will not support JScript or VBScript and therefore will have no need for COM Interop, nor the need to be registered in the registry. In addition to reading the metadata from your database schema, our new esMetadataEngine will be able to serve up the metadata for custom designed objects, or mapped objects created by the customer that represent objects not specifically described in the database. Also, the esMetadataEngine will not be "hard-bound" to any 3rd party ADO.NET providers eliminating upgrade issues when vendors publish new providers. Of course, the esMetadataEngine will be based on the Factory pattern, and allow 3rd parties to add support to EntitySpaces for databases not supported by the EntitySpaces team.

Most likely the data behind our new ASP.NET code generation model will be saved in an SQL CE ".sdf" database. This will include things such as configuration data, connection strings, and template stack information.

EntitySpaces Class Streamlining

The generated EntitySpaces classes are going to be streamlined. A lot of the functionality that can be pushed down in the EntitySpaces "Core" will be, while many other things like the ".str" methods, and so on, are going to be made optional. Also, you will be able to have null constructors in your Custom classes. Basically, you will be able to generate bare bones EntitySpaces classes, and then, enhance them as you see fit using our new customizable template stack approach. Remember, 3rd parties can publish their templates for inclusion into your stack. There could be a few minor breaking changes as a result of this streamlining but we hope to keep them to a minimum, as we think we have one of the best APIs in the business.

DynamicQuery API Enhancements

We will be adding some powerful enhancements to further expand the power of the EntitySpaces DynamicQuery API.

We will be adding the ability to perform SubSelects at various places within the DynamicQuery API. A SubSelect will merely be another DynamicQuery inserted somewhere in the syntax. This is not unlike how our joins are performed. Let's take a look at a sample SubSelect statement.

ProductsQuery avg = new ProductsQuery("a");
avg.Select(avg.UnitPrice.Avg());

ProductsCollection p = new ProductsCollection("p");
p.Query.Select(p.Query.ProductName);

p.Query.Where(p.Query.UnitPrice > avg);  // <= SubSelect
p.Query.Load();

This will generate the following SQL:

SELECT p.ProductName
FROM Products p
WHERE p.UnitPrice>(SELECT AVG(a.UnitPrice) From Products a)

We will also be adding native language casting operations. There are many times when you need to coerce a data type from one type to another in SQL syntax, and currently, there is no way to express this in EntitySpaces. Below is an example of how this will work.

EmployeesCollection coll = new EmployeesCollection();
coll.Query.Select
(
    (coll.Query.LastName + " was hired on " +
        (string)coll.Query.HireDate).As("VirtualColumn")
);
coll.Query.Load();

In the sample above the (string) cast will result in the appropriate SQL being generated for the type of the database being accessed, for SQL this would be either the CAST or CONVERT function for instance to convert the SQL datetime to a varchar for concatenation.

EntitySpaces DataProvider Enhancements

Do not confuse these DataProviders with esMetadataEngine providers. EntitySpaces DataProviders are the providers used by EntitySpaces during runtime to access a database, not the providers used during code generation. These are the enhancements planned for ES2008.

  • IBM DB2 Express support.
  • VistaDB 3.3x support with VistaDB Stored Procedure Generation.
  • SQL CE Desktop Support.
  • 3rd Party Support will be enabled.

Summary

We know this is a lot to tackle for ES2008 and that is why we have chosen to roll this out in two releases rather than a bunch of smaller ones. We will be doing a lot of blogging on this, dive much further into various pieces, and hopefully elicit some feedback from our customer base. Also, we will publish a detailed roadmap for ES2008 in a few weeks.

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net

posted on Saturday, January 19, 2008 6:45:09 PM (Eastern Standard Time, UTC-05:00)  #   
 Wednesday, September 19, 2007

I just had to make another post, this is so much fun. Writing queries in our enhanced dynamic query language is totally awesome, and so powerful. I'm writing some unit tests to go into our massive NUnit test suite and just had to post these ...

public void JoinWithArithmeticExpressionOrderByCalulatedColumn()
{
    // Notice I create a calulated columns based on the TotalSales, then Order by it descending
    CustomerQuery cust = new CustomerQuery("c");
    OrderQuery order = new OrderQuery("o");
    OrderItemQuery item = new OrderItemQuery("oi");

    cust.Select(cust.CustomerName, (item.Quantity * item.UnitPrice).Sum().As("TotalSales"));
    cust.InnerJoin(order).On(order.CustID == cust.CustomerID);
    cust.InnerJoin(item).On(item.OrderID == order.OrderID);
    cust.GroupBy(cust.CustomerName);
    cust.OrderBy("TotalSales", esOrderByDirection.Descending);

    CustomerCollection coll = new CustomerCollection();
    coll.Load(cust);
}

The SQL produced:

SELECT c.[CustomerName],SUM(oi.[Quantity]) AS 'TotalSales'
FROM [ForeignKeyTest].[dbo].[Customer] c
JOIN [ForeignKeyTest].[dbo].[Order] o ON (o.[CustID] = c.[CustomerID])
JOIN [ForeignKeyTest].[dbo].[OrderItem] oi ON (oi.[OrderID] = o.[OrderID])
GROUP BY c.[CustomerName]
ORDER BY TotalSales DESC

The result set is CustomerName / TotalSales sorted in decending order by TotalSales ...

Another query I wrote merely concatenated the Employee FirstName and LastName columns, and upper cased them.

public void ArithmeticConcatenationNoJoinWithSubOperator()
{
    EmployeeCollection coll = new EmployeeCollection();
    EmployeeQuery q = coll.Query;

    q.Select( (q.LastName + "," + q.FirstName).ToUpper().As("FullName") );
    q.OrderBy(q.LastName.Ascending);

    coll.Query.Load();
}

I've seen many of our competitors query languages, I think by far EntitySpaces has the cleanest, most straight-forward syntax, by light years in most cases. We've got some really cool stuff coming, hang on ...

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net


kick it on DotNetKicks.com
posted on Wednesday, September 19, 2007 7:47:35 PM (Eastern Standard Time, UTC-05:00)  #   
 Tuesday, September 18, 2007

The EntitySpaces Dynamic Query API will soon be sporting Join’s and Arithmetic Expressions. The syntax is so elegant even LINQ enthusiasts will take pause. Let’s take a look at how you would do a join using the enhanced EntitySpaces DynamicQuery. Note that we are using all of your existing objects to build the query.

Performing Joins …

CustomersQuery cust = new CustomersQuery ("c");
OrdersQuery orders = new OrdersQuery ("o");
OrderDetailsQuery details = new OrderDetailsQuery ("d");

cust.Select(cust.ContactName, details.Quantity.Sum().As("TotalQuantity"));
cust.InnerJoin(orders).On(cust.CustomerID == orders.CustomerID);
cust.InnerJoin(details).On(orders.OrderID == details.OrderID);
cust.Where(cust.ContactName.Like("%Mike%"));
cust.GroupBy(cust.ContactName);

CustomersCollection coll = new CustomersCollection ();
coll.Load(cust);    // Load it …

Now that is a pretty sweet syntax …

Of course, RightJoin, LeftJoin, and FullJoin are also supported. The nice thing about this approach is that you are spoonfed the syntax via intellisense, no need to stop and create a view (although you can if you want to and generate your business entities off of the view). The "o", "c" and "c" shown above are merely the aliases used when building the SQL.

Arithmetic Expressions

You can now use arithmetic expressions in your query's. Notice how you can use the natural language syntax with * / + - and %. Take a look at this sample …

CustomersQuery cust = new CustomersQuery ("c");
OrdersQuery orders = new OrdersQuery ("o");
OrderDetailsQuery details = new OrderDetailsQuery ("d");

cust.Select(cust.ContactName, (details.Quantity * details.Price).Sum().As("TotalPrice"));
cust.InnerJoin(orders).On(cust.CustomerID == orders.CustomerID);
cust.InnerJoin(details).On(orders.OrderID == details.OrderID);
cust.Where(cust.ContactName.Like("%Mike%"));

CustomersCollection coll = new CustomersCollection ();
coll.Load(cust);    // Load it …

How Does this Effect Binding?

Your EntitySpaces collections will now provide a new method named LowLevelBind(). Normally, you bind directly to the properties in your EntitySpaces entities. However, when joins are in play you are bringing back columns that aren’t in your entities. Thus, the LowLevelBind will bind directly to the underlying DataTable. The are other ways of course using EntitySpaces but we wont cover those in this post.

grid.DataSource = coll.LowLevelBind();

Can I still Save an Entity that was Built off a Join?

The answer is "yes". Of course, it will only save to the main table, in the above case the Customer table.
We already have this implemented and are hoping to have a beta out with joins and arithmetic expressions around October 1st, 2007.

These new features will be available for all of the many databases supported by EntitySpaces.

EntitySpaces

From mobile devices to large scale enterprise solutions in need of serious transaction support, EntitySpaces can meet your needs. Whether you’re writing an ASP.NET application with medium trust requirements, or a Windows.Forms application, the EntitySpaces architecture is there for you. EntitySpaces is provider independent, which means that you can run the same binary code against any of the supported databases. EntitySpaces is available in both C# and VB.NET. EntitySpaces uses no reflection, no XML files, and sports a tiny foot print of less than 200k. Pound for pound, EntitySpaces is one tough, dependable .NET architecture.

The EntitySpaces Team
--

EntitySpaces LLC
Persistence Layer and Business Objects for Microsoft .NET
http://www.entityspaces.net

posted on Tuesday, September 18, 2007 11:22:28 AM (Eastern Standard Time, UTC-05:00)  #