Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Merlin Framework 2013

3.68/5 (8 votes)
30 Oct 2013CPOL2 min read 22.1K   281  
ORM, databinding, asynchronous data access, and transactions

Introduction

Merlin is a collection of .NET classes that augments the .NET framework. The library is filled with classes that cover everything from asynchronous data access to complex WinForms databinding.

Data Access

Before executing the examples below, you will need to attach the AdventureWorks2012 database found at CodePlex.

The following code can be found in the Examples\DataAccessExamples project. In the first example, DbManager will retrieve the first record of the Person.Person table.

C#
//
// Retrieve first row of Person.Person table.
//

using (var db = new DbManager())
{
   using (var reader = db.SetCommand("SELECT TOP 1 * FROM Person.Person").ExecuteReader()) 
   {
     while (reader.Read())
        Console.WriteLine("{0} {1} {2}", reader["BusinessEntityID"], 
        reader["FirstName"], reader["LastName"]);
   }
}

In one line, we've created a SqlConnection and a SqlCommand command with CommandType.Text, finally resulting in an IMerlinDataReader. IMerlinDataReader is simply an extended IDataReader.

Now, onto executing a stored procedure.

C#
//
// Executing a stored procedure with required parameter (No Default).
//

using (var db = new DbManager())
{
   using (var reader = db.SetSpCommand("uspGetEmployeeManagers", 2).ExecuteReader())
   {
     while (reader.Read())
        Console.WriteLine("{0} {1} {2}", reader["BusinessEntityID"], 
        reader["FirstName"], reader["LastName"]);
   }
}

The stored procedure, uspGetEmployeeManagers, requires one parameter, BusinessEntityID. The console window should display:

2 Terri Duffy
Press any key to exit.

Executing a stored procedure asynchronously.

C#
//
// Executing a stored procedure asynchronously.
//

using (var db = new DbManager())
{
  db.SetSpCommand("uspGetEmployeeManagers", 2).BeginExecuteReader(ar =>
  {
   using (var reader = DbManager.EndExecuteReader(ar))
   {
    while (reader.Read())
      Console.WriteLine("{0} {1} {2}", reader["BusinessEntityID"], 
      reader["FirstName"], reader["LastName"]);
   }
 });
}

The console window should display:

2 Terri Duffy
Press any key to exit.

Executing a SQL statement asynchronously with explicitly delaying 5 seconds:

C#
using (var db = new DbManager())
{
  var ar = db.SetCommand("WAITFOR DELAY '00:00:05';
  SELECT TOP 1 * FROM Person.Person").BeginExecuteReader();
  while (!ar.IsCompleted)
  {
    Thread.Sleep(100);
    Console.WriteLine("Sleeping...");
  }
  using (var reader = DbManager.EndExecuteReader(ar))
   while (reader.Read())
     Console.WriteLine("{0} {1} {2}", reader["BusinessEntityID"], 
     reader["FirstName"], reader["LastName"]);
}

ORM

C#
//
// ExecuteObject.
//

using (var db = new DbManager())
{
  var pv = db.SetCommand("SELECT TOP 1 * FROM Person.Person").ExecuteObject<PersonView>();
  Console.WriteLine("{0} {1} {2} {3}", pv.Id, pv.FirstName, pv.LastName, pv.ModifiedDate);
}
//
// ExecuteList.
//

using (var db = new DbManager())
{
  var list = db.SetCommand("SELECT TOP 10 * FROM Person.Person").ExecuteList<PersonView>();
  list.ForEach(p => Console.WriteLine("{0} {1} {2}", p.Title, p.FirstName, p.LastName));
}

How do I implement mapping from a table, view or just about anything? If we look at the PersonView class, it implements IMapMerlinDataRecord.

C#
#region Mapping

void IMapMerlinDataRecord.Map(IMerlinDataRecord record)
{
  _id = record.GetInt32("BusinessEntityID");
  _personType = record.GetString("PersonType");
  _nameStyle = record.GetBoolean("NameStyle");
  _title = record.GetNullableString("Title");
  _firstName = record.GetString("FirstName");
  _middleName = record.GetNullableString("MiddleName");
  _lastName = record.GetString("LastName");
  _suffix = record.GetNullableString("Suffix");
  _emailPromotion = record.GetInt32("EmailPromotion");
  _modifiedDate = record.GetDateTime("ModifiedDate");
}

#endregion

By implementing IMerlinDataRecord, the mapping occurs AFTER any SQL statement.

TransactionScope Support

We all know and have come to love dealing with TransactionScope and multiple open connections. Multiple open connections within a TransactionScope considers the transaction to be a distributed transaction. DbManager is equipped with the ability to only use one open connection. So, the following transaction never kicks off DTS.

C#
var dtcCount = 0;
TransactionManager.DistributedTransactionStarted += ((sender, args) => ++dtcCount);
using (new TransactionScope())
{
 using (var db = new DbManager())
 {
  var rowsAffected = db.SetSpCommand("Transactions_Insert", "Marc", "Leger", "M").ExecuteNonQuery();
  Debug.Assert(rowsAffected == 1, "Failed.");
  rowsAffected = db.SetSpCommand("Transactions_Insert", "Marc", "Leger", "M").ExecuteNonQuery();
  Debug.Assert(rowsAffected == 1, "Failed.");
  rowsAffected = db.SetSpCommand("Transactions_Insert", "Marc", "Leger", "M").ExecuteNonQuery();
  Debug.Assert(rowsAffected == 1, "Failed.");
  rowsAffected = db.SetSpCommand("Transactions_Insert", "Marc", "Leger", "M").ExecuteNonQuery();
  Debug.Assert(rowsAffected == 1, "Failed.");
  rowsAffected = db.SetSpCommand("Transactions_Insert", "Marc", "Leger", "M").ExecuteNonQuery();
  Debug.Assert(rowsAffected == 1, "Failed.");
 }
}
Debug.Assert(dtcCount == 0);

The following example in Examples/TransactionExamples demonstrates using TransactionScope in different scenarios.

C#
Console.Write("Executing ShouldDiscardChangesOnDispose.");
ShouldDiscardChangesOnDispose();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldNotPromoteToDTC.");
ShouldNotPromoteToDTC();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldKeepInnerChangesForNestedTransaction.");
ShouldKeepInnerChangesForNestedTransaction();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldDiscardInnerChangesWhenOuterNotCompleted.");
ShouldDiscardInnerChangesWhenOuterNotCompleted();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldAddRowsWhenNoTransactionActive.");
ShouldAddRowsWhenNoTransactionActive();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldAllowCommandsAfterInnerScopeDisposed.");
ShouldAllowCommandsAfterInnerScopeDisposed();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldDisposeTransactionConnectionOnComplete.");
ShouldDisposeTransactionConnectionOnComplete();
Console.WriteLine("Passed!");
Cleanup();
Console.Write("Executing ShouldDisposeTransactionConnectionOnRollback.");
ShouldDisposeTransactionConnectionOnRollback();
Console.WriteLine("Passed!");
Complete();
Console.WriteLine("Done!");

WinForms Databinding

In the System.ComponentModel namespace, the following interfaces can be found.

C#
IEditableObject
INotifyPropertyChanged
INotifyPropertyChanging
IDataErrorInfo
IRevertibleChangeTracking

Located in Merlin.Core.ComponentModel is BindingBase.cs, which implements the above interfaces.

In Examples/BindingBaseExamples, the following example demonstrates IEditableObject.

C#
public static void Sample03()
{
  var person = PersonInfo.New("Marc", "Leger", 44);
  person.BeginEdit();
  person.FirstName = "John";
  person.LastName = "Doe";
  person.Age = 40;
  person.CancelEdit();
  Console.WriteLine("First Name: " + person.FirstName);
  Console.WriteLine("Last Name: " + person.LastName);
  Console.WriteLine("Age: " + person.Age);
  Console.WriteLine("IsChanged: " + person.IsChanged);
  Console.WriteLine("IsValid: " + person.IsValid);
}

An example of INotifyPropertyChanged.

C#
public static void Sample02()
{
  var person = PersonInfo.New("Marc", "Leger", 44);
  person.PropertyChanged += (sender, e) => Console.WriteLine("Property changed: " + e.PropertyName);
  person.FirstName = "John";
  person.LastName = "Doe";
  person.Age = 40;
  Console.WriteLine("IsChanged: " + person.IsChanged);
  Console.WriteLine("IsValid: " + person.IsValid);
  Console.WriteLine("Error: " + person.Error);
}

IAsyncResult + Task<T> = Fire And Forget

In the Examples/DataAccessExamples, you will find Sample07.

C#
public static void Sample07()
{
  Task<IMerlinDataReade> task =
  Task<IMerlinDataReader>.Factory.FromAsync(new DbManager().SetCommand
  ("WAITFOR DELAY '00:00:05';SELECT TOP 1 * FROM Person.Person").BeginExecuteReader(), 
  	DbManager.EndExecuteReader);
  task.ContinueWith(t =>
  {
   using (var reader = t.Result)
     while (reader.Read())
        Console.WriteLine("{0} {1} {2}", reader["BusinessEntityID"], 
        reader["FirstName"], reader["LastName"]);
     Console.WriteLine("Asynchronous execution complete.");
  });
 Console.WriteLine("Don't press any key yet!");
}

In the Examples/DataMappingExamples, you will find Sample05.

C#
public static void Sample05()
{
  Task<PersonView> task = Task<PersonView>.Factory.FromAsync
  (new DbManager().SetCommand("WAITFOR DELAY '00:00:05';SELECT TOP 1 * 
  FROM Person.Person").BeginExecuteReader(), DbManager.EndExecuteObject<PersonView>);
  task.ContinueWith(pv =>
  {
    Console.WriteLine("{0} {1} {2} {3}", pv.Result.Id, 
    pv.Result.FirstName, pv.Result.LastName, pv.Result.ModifiedDate);
    Console.WriteLine("Asynchronous execution complete.");
  });
  Console.WriteLine("Don't press any key yet!");
}

Points of Interest

Merlin Framework 2013 targets .NET 4.5.1 framework.

History

  • 28th September, 2014:  Refactored Merlin.Data. Added new examples.
  • 30th October, 2013: Added two Task examples
  • 25th October, 2013: Added TransactionScope examples
  • 23rd October, 2013: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)