Click here to Skip to main content
Click here to Skip to main content

Merlin Framework 2013

, 30 Oct 2013
Rate this:
Please Sign up or sign in to vote.
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.

//
// 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.

//
// 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.

//
// 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:

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

//
// 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.

#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.
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.

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.

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.

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.

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.

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.

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

  • 30 October, 2013: Added two Task examples
  • 25 October, 2013: Added TransactionScope examples
  • 23 October, 2013: Initial version

License

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

About the Author

Marc Leger
Web Developer Epsilon
United States United States
20+ years experience developing web solutions.

Comments and Discussions

 
GeneralMy vote of 1 PinprofessionalPaulo Zemek23-Oct-13 15:06 
GeneralRe: My vote of 1 PinmemberMarc Leger23-Oct-13 16:06 
GeneralRe: My vote of 1 PinprofessionalPaulo Zemek23-Oct-13 16:15 
AnswerRe: My vote of 1 PinmemberMarc Leger23-Oct-13 16:45 
GeneralRe: My vote of 1 PinprofessionalCanny Brisk23-Oct-13 20:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 30 Oct 2013
Article Copyright 2013 by Marc Leger
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid