使用 Unity 框架管理 LINQ 数据上下文






3.44/5 (6投票s)
一种使用依赖注入管理业务逻辑类之间 LINQ Data Context 对象的新颖方法。
引言
本文介绍了一种管理业务逻辑类之间 LINQ Data Context 对象的新颖方法。业务逻辑对象不需要了解数据上下文本身,它们应该只做它们应该做的事情,即使它们可能依赖于数据上下文。我认为这可以是一种使用 Microsoft Unity Framework 进行依赖注入的绝佳方式。
背景
关于管理 LINQ Data Context 对象,网上有很多文章。一些人认为每次开发人员想处理数据时都应该实例化一个新的上下文,一些人认为它应该全局存在,还有一些人有更好的管理方法。在研究了所有不同的上下文对象管理方法之后,我发现您想要持久化该对象的唯一原因是当您一次执行大量数据操作(插入、更新、删除)并且只想对数据库执行一次调用来完成所有更改时。这可以通过一个简单的函数和一个 Data Context 对象一次性完成,但这并没有将关注点分离到业务对象中。相反,最好将成员、销售、产品等的业务逻辑放在它们自己的业务逻辑类中。问题在于在这些业务逻辑类之间维护 Data Context 对象,而不必担心业务逻辑类本身会关心使用哪个上下文对象。依赖注入来帮忙!
Using the Code
附带的解决方案基于 NUnit,因此您需要使用 NUnit 来查看结果。代码的细分如下:
- LinqUnity.Business...
- LINQ to SQL 生成的对象。
- LinqUnity.Logic...
- 我自定义的业务逻辑对象,包含数据库访问逻辑。这些类依赖于包含数据上下文对象的 `Factory` 对象。所有这些对象都实现了 `IFactoryData` 接口,该接口定义了一个 `Factory` 属性。
- LinqUnity.Business.Factory
- 用于解析所有业务逻辑对象的对象。该对象的构造函数创建一个新的 Unity 容器,该容器根据 app.config 中的配置进行构建。然后,它将自身的一个实例注册到容器中,以便容器中的所有依赖对象都能接收到自身作为 `Factory` 实例。
- LinqUnity.Test
- 包含数据操作测试和示例的 NUnit 测试类。
数据库结构非常简单,由以下表组成:
- 成员
- 文章
- 注释
我不确定此应用程序可能用于何种用途,但其理念是文章可以有评论,成员可以创建这些评论。
`Factory` 对象是所有 `IFactoryData` 对象所依赖的对象。Factory 对象包含一个只读属性,该属性返回 LINQ 数据上下文对象,以及一个 Resolve 方法来返回任何 `IFactoryData` 对象。每个 `IFactoryData` 对象依赖于 `Factory` 而不是仅仅依赖于 LINQ 数据上下文对象的原因是,如果其中一个 `IFactoryData` 对象需要解析另一个 `IFactoryData` 对象,它可以使用自己的 `Factory` 对象来实现。
`TestObjects()` 测试方法中的示例展示了这一点,该方法测试确保从工厂解析的每个对象始终是引用相同数据上下文对象的同一个对象。
private Factory m_factory = new Factory();
private MemberData m_memberData;
private ArticleData m_articleData;
private CommentData m_commentData;
// This create our private objects before running tests
[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()
{
//prove that the data factory is always the same
Assert.AreEqual(m_memberData.DataFactory, m_factory);
Assert.AreEqual(m_articleData.DataFactory, m_factory);
Assert.AreEqual(m_commentData.DataFactory, m_factory);
//resolve objects out of the factory
MemberData memberData = m_factory.Resolve<memberdata>();
ArticleData articleData = m_factory.Resolve<articledata>();
CommentData commentData = m_factory.Resolve<commentdata>();
//prove that the above objects are the same as our private members
Assert.AreEqual(memberData, m_memberData);
Assert.AreEqual(articleData, m_articleData);
Assert.AreEqual(commentData, m_commentData);
//prove that the context object is always the same
Assert.AreEqual(memberData.DataFactory.DBContext, m_factory.DBContext);
Assert.AreEqual(articleData.DataFactory.DBContext, m_factory.DBContext);
Assert.AreEqual(commentData.DataFactory.DBContext, m_factory.DBContext);
}
为了将这一点付诸实践,此方法将创建一个文章,创建一个成员,并为新成员/文章创建 20 条评论,并在一处数据库连接中全部完成。
[Test]
public void CreateMemberArticleComments()
{
string unique = Guid.NewGuid().ToString();
//create a new member and article with some random data
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));
//we're creating the new comment with the reference
//to our new member and new article objects. LINQ will
//take care of wiring up the primary keys in the backend on submit
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));
//SAVE ALL THE CHANGES TO THE DATABASE
m_factory.DBContext.SubmitChanges();
//ensure that everything has worked...
//ensure that we have a new member ID
Assert.Greater(newMember.memberID, 0);
//ensure that we have a new article ID
Assert.Greater(newArticle.articleID, 0);
//ensure that all of the new comments has the correct member and article ids
foreach (Comment comment in comments)
{
Assert.Greater(comment.commentID, 0);
Assert.AreEqual(comment.articleID, newArticle.articleID);
Assert.AreEqual(comment.memberID, newMember.memberID);
}
}
这一切都能正常工作,因为每个 `IFactoryData` 都依赖于 `Factory` 对象,而 `Factory` 对象又包含 LINQ Data Context 对象。创建工厂时,它会创建一个新的 Unity 容器并将其自身注册到其中。
其中一个依赖逻辑对象的示例:
public class MemberData : IFactoryData, IMemberData
{
#region IFactoryData Members
/// <summary>
/// Flag this property as dependant so the Unity Framework passes this object
/// the current Factory/Context
/// </summary>
[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
}
`Factory` 非常简单。它创建一个新的 Unity 容器,根据配置文件配置容器,并将自身插入到容器中。这确保所有 `IFactoryData` 对象都存在于容器中,并且它们都拥有当前的 `Factory` 实例作为它们的 `Factory` 对象。
public class Factory : IFactory
{
/// <summary>
/// This constructor adds itself to the Unity Container
/// </summary>
public Factory()
{
container = new UnityContainer();
//Get the "DataLayer" container definition from the config file and
//configure our container with it
UnityConfigurationSection section = (
UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers["DataLayer"].Configure(container);
//Register this object instance in the container
container.RegisterInstance<Factory>(this,
new ContainerControlledLifetimeManager());
}
private IUnityContainer container;
/// <summary>
/// Returns the IDataContext object requested from the container
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Resolve<T>() where T : IFactoryData
{
T objData = container.Resolve<T>();
return objData;
}
/// <summary>
/// Return the current DataContext object for the factory
/// </summary>
public TestDataContext DBContext
{
get { return container.Resolve<TestDataContext>(); }
}
}
配置部分也非常简单。唯一的“技巧”是确保它使用默认的无参数构造函数来创建 LINQ 数据上下文对象。
<unity>
<!-- Define all of the types that we want to put into our container -->
<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>
<!-- Define a container that contains all of the data logic objects -->
<container name="DataLayer">
<types>
<!-- The LINQ data context object -->
<type type="LinqUnity.Business.TestDataContext, LinqUnity">
<lifetime type="singleton" />
<typeConfig
extensionType=
"Microsoft.Practices.Unity.Configuration.TypeInjectionElement,
Microsoft.Practices.Unity.Configuration">
<constructor/>
<!-- Ensure it is created with the default empty parameter constructor -->
</typeConfig>
</type>
<!-- Our custom logic classes: -->
<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>
关注点
我认为这是一种管理 LINQ 数据上下文对象的好方法,因为它使得创建业务逻辑类变得容易,这些类仅包含它们应该执行的操作的逻辑,同时又保持了相同的上下文。使用此模型的开发人员可以拥有任意数量的并发工厂实例,每个实例都将拥有自己的 LINQ 上下文对象。
如果您想了解更多关于 Unity 应用程序块的信息,这里有一些链接: