this.Blog.Find(entry => entry.IsHelpful);
 Sunday, April 06, 2008
Miado is out on CodePlex

I have been refining some data access code that I have used on a bunch of my projects to the point where I've made it fairly generic.  I decided to publish it as a project on CodePlex.  I just put the initial release out today.  If you are interested, please take a moment to check it out and give me any feedback.  Keep in mind, I wrote this specifically for those people who have (or want) to use straight SQL and ADO.Net in their data access layer.  So if you want to use LINQ, Nhibernate, Subsonic, etc... they are absolutely great tools, but they try to hide/remove the SQL from your code, whereas this project just makes it much easier for those who use SQL.

Here is an example of how to use it to create a collection of custom business objects:

// ideally this declaration would be buried in your base DAO class
// or injected via an IoC container
IDbConfigurable dbConfig = new DbConfiguration(SqlClientFactory.Instance, connString);
IDbStatement dbStmt = new DbStatement(dbConfig);

ICollection<Address> addresses = 
    dbStmt.LoadSql("SELECT AddressId, Address1, Address2, City, State, ZipCode " + 
                   "FROM Address WHERE City = @City")
          .AddParameter("City", "Atlanta")
          .QueryForResults<Address>(
              row => new Address() 
                  {
                      AddressId = row.GetValue<int>("AddressId"), 
                      Address1 = row.GetValue<string>("Address1"),
                      Address2 = row.GetValue<string>("Address2"),
                      City = row.GetValue<string>("City"),
                      State = row.GetValue<string>("State"),
                      ZipCode = row.GetValue<string>("ZipCode")
                  });

Notice how it removes all the usual boiler plate code from a normal ADO.Net implementation.  The only things you really need to do are provide the SQL, set the parameters, and then map the result set to properties on your business object.  I'm using lambda expressions in these examples, but if you want to re-use the result set-to business object mappings, I normally create a method in my DAOs to use as a delegate:

ICollection<Address> addresses = 
dbStmt.LoadSql("SELECT AddressId, Address1, Address2, City, State, ZipCode " +
"FROM Address WHERE City = @City")
.AddParameter("City", "Atlanta")
.QueryForResults<Address>(CreateAddress);
...
private Address CreateAddress(IResultSetRow row)
{
// full details not provided
return new Address() { AddressId = row.AddressId };
}

If you embed the creation of the IDbStatement interface in a base class (or use the one provided in Miado.Integration), you can see how the code reads even more like a Domain-Specific-Language:

// CreateSqlStatment() method is provided in Miado.Integration.MiadoDao
ICollection<Product> products = 
    this.CreateSqlStatement("SELECT ProductId, Name, Description, Color, Quantity " +
                            "FROM Product WHERE Color = @Color and Quantity > @Qty")
        .AddParameter("Color", "Blue")
        .AddParameter("Qty", 20)
        .QueryForResults<Product>(
              row => new Product() 
                  {
                      ProductId = row.GetValue<int>("ProductId"), 
                      Name = row.GetValue<string>("Name"),
                      Description = row.GetValue<string>("Description"),
                      Color = row.GetValue<string>("Color"),
                      Quantity = row.GetValue<int>("Quantity")
                  });

There is also a SimpleSql object that can be used similar to IDbStatement but the syntax is a little more terse since you don't have to map the parameters - they are merely replaced in the order you provide them:

IDbConfigurable dbConfig = new DbConfiguration(SqlClientFactory.Instance, connString);
SimpleSql sql = new SimpleSql(dbConfig);
sql.ExecuteNonQuery("UPDATE Person SET FirstName = @FirstName, " + 
                    "LastName = @LastName " + 
                    "WHERE PersonId = @PersonId", 
                    "John", "Smith", 1001);
// again, I like to embed this in a base class so it reads:
// this.CreateSimpleSql().ExecuteNonQuery(sql, param1, param2);

For this first release, SimpleSql is not supported for ODBC SQL statements (since it uses pattern matching to substitute the SQL parameters).

Here's another example where you can see how easy it is to get a count:

int count = 
    this.CreateSimpleSql()
        .QueryForOne<int>("SELECT count(*) FROM Employee WHERE YearsOfService > @YearsOfSvc",
                           new object[] { 15 },
                           row => row.GetValue<int>(0));

And yes, it supports the creation of DataSets and DataTables:

DataSet ds = 
    this.CreateSimpleSql()
        .QueryForDataSet("SELECT * FROM ProductType WHERE Price > @Price", 10.0);

This is the first code drop, so treat it like a beta release.


Kick it on DotNetKicks.com
Sunday, April 06, 2008 9:35:57 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  .Net | Miado

Tuesday, April 08, 2008 1:46:03 PM (Eastern Standard Time, UTC-05:00)
Nice! I've been working on something kinda-sorta similar in one of my projects, to simplify or eliminate the distinction between different database systems. I will download the code on CodePlex and have a peek.
Comments are closed.