Introduction
This article looks at a unique way of managing the LINQ Data Context object between business logic classes. Business logic objects need not be aware of the data context itself, they should just do only what they're meant to do even though they may depend on the data context. I thought this could be a cool way to use Dependency Injection with Microsoft's Unity Framework.
Background
There are tons of posts out there about managing the LINQ Data Context object. Some say that a new one should be instantiated each time a developer wants to do anything with the data, some say it should exist globally, and others have better ways of managing it. After looking at all of the different ways to manage the context object, I found that the only reason why you would want to persist the object is if you are doing a lot of data manipulation (inserts, updates, deletes) at one time and only want to make one call to the database to execute all of the changes. This could be done all at once with a simple method and one Data Context object, but this doesn't separate concerns into business objects. Instead, it would be nicer to have the business logic for a member, a sale, a product, etc... to be in their own business logic classes. The problem is maintaining the Data Context object between these business logic classes without having to worry about the business logic classes themselves caring which context object to use. Dependency Injection to the rescue!
Using the Code
The solution attached is based on NUnit, so you'll need to use NUnit to see the outcome. The breakdown of the code is as follows:
- LinqUnity.Business...
- The generated objects created by LINQ to SQL.
- LinqUnity.Logic...
- My custom business logic objects containing the logic for database access. These classes have a dependency on the
Factory
object which contains the data context object. All of these objects implement the IFactoryData
interface which defines a property for the Factory
- LinqUnity.Business.Factory
- The object to use for resolving all business logic classes. This object's constructor creates a new Unity container which gets built based on the configuration in the app.config. It then registers an instance of itself into the container so that all dependant objects in container receeive the itself as the
Factory
instance.
- LinqUnity.Test
- The NUnit test class containing the tests and examples for data manipulation.
The database structure is quite simple. and is made up of the following tables:
I'm not sure what this application could be used for, but the idea is that Articles can have Comments and Members can create these Comments.
The Factory
object is what all IFactoryData
objects are dependant on. The Factory object contains a read only property that returns the LINQ data context object and a Resolve method to return any of the IFactoryData
objects out. The reason that each IFactoryData
object is dependant on the Factory
and not just the LINQ data context object is because if one of the IFactoryData
objects needed to resolve one of the other IFactoryData
objects, it could using it's own Factory
object.
An example of how this works is in the TestObjects()
test method which tests to ensure that each object resolved from the factory is always the same object referencing the same data context object.
private Factory m_factory = new Factory();
private MemberData m_memberData;
private ArticleData m_articleData;
private CommentData m_commentData;
[SetUp]
public void Init()
{
m_memberData = m_factory.Resolve<memberdata>();
m_articleData = m_factory.Resolve<articledata>();
m_commentData = m_factory.Resolve<commentdata>();
}
[Test]
public void TestObjects()
{
Assert.AreEqual(m_memberData.DataFactory, m_factory);
Assert.AreEqual(m_articleData.DataFactory, m_factory);
Assert.AreEqual(m_commentData.DataFactory, m_factory);
MemberData memberData = m_factory.Resolve<memberdata>();
ArticleData articleData = m_factory.Resolve<articledata>();
CommentData commentData = m_factory.Resolve<commentdata>();
Assert.AreEqual(memberData, m_memberData);
Assert.AreEqual(articleData, m_articleData);
Assert.AreEqual(commentData, m_commentData);
Assert.AreEqual(memberData.DataFactory.DBContext, m_factory.DBContext);
Assert.AreEqual(articleData.DataFactory.DBContext, m_factory.DBContext);
Assert.AreEqual(commentData.DataFactory.DBContext, m_factory.DBContext);
}
To put this into practice, this method will create an article, create a member, and create 20 comments for the new member/article, and do it all in one database connection.
[Test]
public void CreateMemberArticleComments()
{
string unique = Guid.NewGuid().ToString();
Member newMember = m_memberData.CreateNew(unique,
string.Format("{0}@{0}.com", unique), "00000000");
Article newArticle = m_articleData.CreateNew(unique,
"Some description...", string.Format("Some body text {0}", unique));
List<comment> comments = new List<comment>();
for (int i = 0; i < 20; i++)
comments.Add(m_commentData.CreateNew(string.Format("{0} - This is new comment",
i.ToString()), newMember, newArticle));
m_factory.DBContext.SubmitChanges();
Assert.Greater(newMember.memberID, 0);
Assert.Greater(newArticle.articleID, 0);
foreach (Comment comment in comments)
{
Assert.Greater(comment.commentID, 0);
Assert.AreEqual(comment.articleID, newArticle.articleID);
Assert.AreEqual(comment.memberID, newMember.memberID);
}
}
This all works because each IFactoryData
has a dependency on the Factory
object, which in turn contains the LINQ Data Context object. When the factory is created, it creates a new Unity container and registers itelf inside of it.
An example of one of the dependant logic objects:
public class MemberData : IFactoryData, IMemberData
{
#region IFactoryData Members
[Dependency]
public Factory DataFactory
{
get
{
return m_factory;
}
set
{
m_factory = value;
}
}
#endregion
private Factory m_factory;
#region IMemberData Members
public List<Member> SelectAll()
{
return (from m in m_factory.DBContext.Members select m).ToList();
}
public Member SelectByID(int ID)
{
return (from m in m_factory.DBContext.Members where m.memberID ==
ID select m).Single();
}
public Member CreateNew(string name, string email, string phone)
{
Member member = new Member();
member.name = name;
member.email = email;
member.phone = phone;
member.created = DateTime.Now;
m_factory.DBContext.Members.InsertOnSubmit(member);
return member;
}
#endregion
}
The Factory
is fairly simple. It creates a new Unity container, configures the container based on the configuration file and inserts itself into the container. This ensures that all of the IFactoryData
. objects exist in the container and all of them have the current Factory
instance as their Factory
object.
public class Factory : IFactory
{
public Factory()
{
container = new UnityContainer();
UnityConfigurationSection section = (
UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers["DataLayer"].Configure(container);
container.RegisterInstance<Factory>(this,
new ContainerControlledLifetimeManager());
}
private IUnityContainer container;
public T Resolve<T>() where T : IFactoryData
{
T objData = container.Resolve<T>();
return objData;
}
public TestDataContext DBContext
{
get { return container.Resolve<TestDataContext>(); }
}
}
The configuration section is also quite simple. The only "trick" was to ensure that it creates the LINQ data context object using the default parameterless constructor.
<unity>
<typeAliases>
<typeAlias alias="singleton"
type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
Microsoft.Practices.Unity" />
<typeAlias alias="IMemberData" type="LinqUnity.Logic.IMemberData, LinqUnity" />
<typeAlias alias="IArticleData" type="LinqUnity.Logic.IArticleData, LinqUnity" />
<typeAlias alias="ICommentData" type="LinqUnity.Logic.ICommentData, LinqUnity" />
</typeAliases>
<containers>
<container name="DataLayer">
<types>
<type type="LinqUnity.Business.TestDataContext, LinqUnity">
<lifetime type="singleton" />
<typeConfig
extensionType=
"Microsoft.Practices.Unity.Configuration.TypeInjectionElement,
Microsoft.Practices.Unity.Configuration">
<constructor/>
</typeConfig>
</type>
<type type="IMemberData" mapTo="LinqUnity.Logic.MemberData, LinqUnity">
<lifetime type="singleton" />
</type>
<type type="IArticleData" mapTo="LinqUnity.Logic.ArticleData, LinqUnity">
<lifetime type="singleton" />
</type>
<type type="ICommentData" mapTo="LinqUnity.Logic.CommentData, LinqUnity">
<lifetime type="singleton" />
</type>
</types>
</container>
</containers>
</unity>
Points of Interest
I thought this was a cool way to manage the LINQ data context object since it makes it easy to create business logic classes that contain the logic to only do what they're meant to do but while maintaining the same context. A developer using this model could have as many concurrent factories instances as they want that would each have their own LINQ context object.
If anyone wants to read more about the Unity application block, here are the links: