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.
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.
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.
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
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);
}
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
- 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