使用存储库模式、Fluent NHibernate 和 LightCore 框架进行控制反转/依赖注入






4.89/5 (24投票s)
使用 LightCore 的 IOC/DI 和存储库工厂。
引言
我们在系统和应用程序中重用现有代码的越多,我们就越能更好地实现后续可能需要的修改、更改和增强。真正的挑战在于在不产生高度依赖的情况下有效地整合异构组件。本文无意灌输关于内聚与耦合、模式(尽管我们会包含一些)或控制反转/依赖注入的底层机制,因为互联网上已经有许多文章详细介绍了这些范式。本文的重点是提供一种实现控制反转的方法,使用存储库模式、Fluent NHibernate ORM 和 LightCore IoC/DI 框架来以直观的方式解决依赖问题。此外,本文主要面向那些在实现 IOC/DI 概念时遇到困难的软件工程师。我假设您从未编写过一行代码来解决依赖问题,也没有使用过任何相关框架。在对互联网上的 IOC 和 DI 进行详尽研究后,我发现很少有例子能详细说明如何使用 LightCore 来完成这项技术。
要点
第一部分 - 概念
第二部分 - 实操
- 创建新的 MVC 3 项目
- 设置 N 层
- 配置系统设置属性
- 创建服务定位器和容器
- 创建会话工厂
- 创建 LightCore 配置文件并注册依赖项
- 创建数据库表、映射、模型和实体(Fluent NHibernate)
- 构建存储库及其原因
- 整合所有内容
- 单元测试独立模块
背景
本文假设您至少精通 .NET 3.5/4.0 框架、ASP.NET、C#、面向对象编程和一些模式。此外,您还需要有一定的经验编写使用 ORM、SQL Server 2005/2008 和单元测试的 N 层架构。
以下是一些您在阅读本文时可能会觉得有用的链接
- http://www.dotnetspark.com/kb/266-inversion-control-ioc-and-dependency-injection.aspx
- LightCore
- http://fluentnhibernate.org/
- http://www.testdriven.net/
- http://www.nunit.org/
- http://www.microsoft.com/sqlserver/2008/en/us/express.aspx
问题
依赖。在您作为软件工程师、开发人员、测试人员或架构师的职业生涯中的某个时候,您很可能会遇到一个直接与依赖相关的问题,或者是由代码中高度依赖的状态和行为引起的副产品。依赖会给软件系统带来挑战,因为它引入了依赖或耦合,这使得在不影响应用程序其他部分的情况下更改相关模块变得困难,在某些情况下甚至不可能。让我们看下面的例子。
上面的依赖关系图(来自 Visual Studio 2010 可视化和建模)显示 `ClassA` 依赖于 `ClassB`,因为如果 `ClassA` 需要调用或执行 `ClassB` 上的 'DoSomething
' 方法,它就需要创建 `ClassB` 的一个实例。`ClassA` 控制着实例(对象)的创建,并“知道” `ClassB` 的内部细节。如果我们更改 `ClassB` 的方法或 `ClassB` 的任何相关结构,我们就有风险影响所有依赖于 `ClassB` 的类 - 这可不好!紧耦合带来的其他挑战包括通过分类法增加逆向工程的难度、测试的困难以及回溯到高级抽象的分解。
// Here is the source code for the illustration above
// showing the dependency
public class ClassA
{
private ClassB classBeesObject;
public ClassA()
{
classBeesObject = new ClassB();
}
}
public class ClassB
{
protected void DoSomething() { }
}
上面的代码清楚地显示 `ClassA` 控制着 `ClassB` 对象(`classBeesObject`)的创建,并通过本地引用 `ClassB` 进一步显示了依赖关系。这种紧密关系现在引入了一个异构架构,其中 **任何** 对 `ClassB` 的更改都可能影响 `ClassA`。由于 `ClassB` 实现的透明性现在存在于 `ClassA` 中,因此我们现在有了逻辑混合的配方,如果系统规模和复杂性不断增长,就会导致混乱。
解决方案:委托责任
控制反转 (IOC):这项技术几乎不需要介绍,因为在互联网上搜索一下就会得到大量解释控制反转的结果。我也会加入进来,并加上我的两点看法,即控制反转或 IOC 被称为流程控制或创建控制的反向,通过一种抽象设计。那么,这到底意味着什么呢?这个定义以及许多其他定义都指的是通常与过程式编程相关的程序流程,其中特定的代码块包含另一部分代码,从而了解被包含代码的实现(具体方法等)并控制流程。在上面的代码中,`ClassA` 创建了 `ClassB` 的实例,所以 `ClassA` 在控制中,并且**知道** `ClassB` 的实现,在本例中是 'DoSoemthing
' 方法。我们想要做的是放弃 `ClassA` 的控制权,并将其交给第三方。我们还希望 `ClassA` 对 `ClassB` 的实现一无所知,换句话说,`ClassA` 对 `ClassB` 没有任何透明度,甚至不在乎 `ClassB` 的“内部”是什么。控制反转的目标就是实现这一点,尽管“反转”这个词有点用词不当,因为我倾向于认为控制被“委托”给了一个抽象。使用 IOC 框架将控制权从本例中的 `ClassA` 转移到由 IOC 指定的另一个对象。这如何解决我们的依赖问题?嗯,`ClassB` 可以被修改,而对 `ClassA` 的影响很小或没有影响。我们现在可以在不影响 `ClassA` 的情况下测试 `ClassB`。例如,如果 `ClassA` 使用 `ClassB` 连接到 I/O 串行端口服务器并查找数据包信息,我们可以轻松地对 `ClassA` 运行单元测试,而无需 `ClassB` 完成的任何操作。这样我们就减少了 `ClassA` 因 `ClassB` 未能连接到串行端口或 against a packet 进行身份验证而失败的可能性。函数分解变得不那么麻烦,因为我们可以说明与对象依赖无关的函数关系。
依赖注入 (DI):您很少听到控制反转而不提及依赖注入。在某些情况下,我看到它们被解释为相同的东西,但事实远非如此。IOC 和 DI 并非相同,但关系密切。还记得我们在 `ClassA` 中创建的 `ClassB` 的引用变量(或依赖项)吗?如果我们有一个第三方在不知道是什么、何时、如何或为什么的情况下将此依赖项提供给我们(或注入)呢?依赖注入正是做到了这一点。DI “注入”,或者更具体地说,将您的引用变量(依赖项)交给您。我不会深入介绍依赖注入,因为您可以在互联网上研究 DI,或者购买许多详细描述 DI 的出版物。有很多方法可以“传递”依赖项,但为了本文的方便,我们将使用通常称为容器的方法。
容器:我们可以使用一个容器,或者更具体地说,使用 LightCore 框架(容器)来解决我们很大一部分的依赖问题。容器是利用抽象的代码,它管理对象、实例化和配置。容器最简单的形式决定了要实例化或注入的对象类型以及注入的位置。它之所以被称为容器,是因为它本质上“持有”稍后要注入的对象,当然还有一些生命周期管理属性。您可以通过互联网进一步研究 DI 容器。LightCore 将在此场景中为我们提供容器机制。
选择您的 IOC/DI 框架
选择您的 IOC/DI 框架取决于许多因素(假设您不想编写自己的自定义 IOC 和 DI)。我的理由在于易用性和实现性、我打算用它解决的特定问题,以及它在大小、性能和开销方面对我的软件系统的影响。项目的规模也可能是决定因素。LightCore 是一个轻量级框架,在所有这些方面都表现出色,但文档方面稍显不足。此外,LightCore 是一个德国的框架,所以网站上的大部分信息需要翻译,这通常通过谷歌的样板翻译机制来完成,这使得一些逻辑难以理解。有各种各样的框架可供选择,例如 StructureMap、Castle Windsor、Spring .NET、Ninject、AutoFac 和 Unity,仅举几例。本文的目的我将使用 LightCore 框架,但一旦您掌握了概念,任何框架的实现都会变得容易。
创建 MVC 3 项目
好了,概念讲完了,我们开始吧!您需要安装 Microsoft 的 MVC 3 才能阅读本文。如果您还没有安装,可以从 http://www.asp.net.mvc/mvc3 获取。安装 MVC 3 后,打开 Visual Studio 2010,选择 文件 > 新建 > 项目 > Visual C#,然后从列表中选择 ASP.NET MVC 3 Web 应用程序,如下图所示。将项目命名为 Kodiak。
选择“Internet 应用程序”和 ASPX 作为引擎。保持“单元测试”未选中,因为我们稍后将添加自定义单元测试。
您的解决方案资源管理器应该看起来像这样
设置 N 层
您可以以任何您想要的方式设置您的 N 层,只要它能说明最适合您需求的逻辑架构。在本文中,我将各层本身分成不同的项目;但是,如果这是一个商业应用程序,我肯定会为每个项目创建一个文件夹。
首先,使用 Visual Studio 10 添加 DataLayer 项目,选择 文件 > 添加项目 > Visual C#,然后选择类库。将默认名称重命名为 KDatalayer。既然我们正在设置应用程序的基础结构,那么让我们继续创建其他所需的项目。
就像我们为 KDatalayer 所做的那样,创建额外的类库项目并为它们命名:KLModel、KCore 和 KTests(为简单起见,我排除了 BLL)。您的解决方案资源管理器现在应该看起来像这样
现在右键单击 KDataLayer 项目并添加以下文件夹:Contracts、Entities、Maps、Repositories 和 Sessions。KDatalayer 现在应该看起来像这样
现在,让我们添加对 IOC 框架、Fluent NHibernate 和 LightCore 框架的一些引用。使用上面提供的链接下载这些组件,并将它们添加到 KDatalayer 项目的引用中。您的解决方案资源管理器应该看起来像这样(Elmah 和 log4Net 是可选的)
配置系统设置属性
让我们创建一个处理系统设置的契约(接口)和一个具体的类。此契约允许我们访问属性设置,以动态存储和检索系统设置属性,或者更简单地说,存储和检索包含在 web.config 或(如果适用)app.config 文件中的信息。右键单击 Solution Explorer 中的 KCore 项目,并添加两个新文件夹,将其中一个命名为“Contract”,另一个命名为“Settings”(不带单引号)。在 Contracts 文件夹中添加一个新接口,并插入以下代码
// Here is the source code for the Systems Settings contract
using System;
using System.Collections.Generic;
using System.IO;
namespace KCore.Contracts
{
interface ISystemSettings
{
/// <summary>
/// Connection string
/// </summary>
string KodiakConnectionString { get; }
}
}
在 KCore 项目的 Settings 文件夹中添加一个新类,并插入以下代码
// Here is the source code for the Systems Settings implementation
using KCore.Contracts;
namespace KCore.Settings
{
class SystemSettings : ISystemSettings
{
public string KodiakConnectionString
{
get { return Properties.Settings.Default.KodiakDatabase; }
}
}
}
Solution Explorer 中的 KCore 项目应该看起来像这样
现在,要创建设置属性文件,请右键单击 KCore 项目并选择“属性”。在左侧选择“Settings”选项卡,然后在 Name 部分键入 KodiakDatabase,在 Type 部分,从下拉列表中选择 (ConnectionString),并将 Scope 设置为默认的 Application。这是一个快速预览
创建服务定位器并配置容器
我们将使用的服务定位器不过是一个基于拉取的查找模式,它通过 LightCore 框架提供的规定方法来定位服务。更简单地说,服务是依赖项,可以预先注册并在请求时“拉取”。服务定位器可以实例化对象并根据传递给它的参数提供其他配置方案。更重要的是,服务定位器允许我们“包装”LightCore 的 IOC 框架的规定方法,因此如果我们想实现另一个框架,比如 StructureMap 或 Ninject,我们可以轻松地替换 LightCore。创建一个新类并将其命名为 ServiceLocator
,并将其添加到 KCore 项目的根目录。用下面提供的代码替换 ServiceLocator
类代码。
ContainerBuilder
对象(builder)用于简单地存储已注册的依赖项类型。容器是不可变的,它解析已注册的类型。出于我在这里不讨论的原因,已将无参数构造函数排除在注册之外。如果您想知道注册究竟意味着什么,请不用担心,我们将在本文稍后讨论。
// Service Locator Class
using LightCore;
using LightCore.Configuration;
using LightCore.Registration;
namespace KCore
{
public static class ServiceLocator
{
// Create the container
private static IContainer _container;
/// <summary>
/// Resolve a type for the container and return the type
/// </summary>
/// <typeparam name="T">Type to resolve</typeparam>
public static T Resolve<T>()
{
if (_container == null) Configure("LightCore.config");
return (T)_container.Resolve(typeof(T));
}
/// <summary>
/// Prepare container configuration
/// </summary>
public static void Configure(string filename)
{
// Check if the container is null;
if (_container != null)
{
// Do something; like log and return
//return;
}
// Here is hwere we intialize and build the LightCore IOC container
// using the LightCore configuration file
var builder = new ContainerBuilder();
RegistrationModule xamlModule = new XamlRegistrationModule(filename);
builder.RegisterModule(xamlModule);
_container = builder.Build();
}
}
}
好了,现在是时候构建会话工厂并描述它的必要性了。由于我们正在使用 Fluent NHibernate 对象关系映射 (ORM),因此我们需要管理何时为我们的数据库检索“暂存”数据。对于本文,我们使用的是 MS SQL Server 2008,但 SQL Server 2005 也可以。会话工厂是 Hibernate 的原子存储库概念。会话工厂是线程安全的,这意味着可以同时请求和执行多个并发会话。通常,整个应用程序只需要一个会话工厂,并且应该将其封装在单例中。LightCore DI 框架将提供单例实现,因此我们不必担心自己编写,尽管如果需要,我们也可以这样做。理解会话工厂的主要作用取决于我们对“会话”提供什么的了解。会话本身会向数据库发出查询,将数据返回到对象中,然后这些对象会被“暂存”或保留以供后续使用。“工厂”概念与任何其他工厂模式方案几乎没有区别:从对象创建对象。所以简而言之,会话工厂在请求时吐出填充了数据的对象。
在 KDatalayer 项目的 Contracts 文件夹中,添加一个接口并命名为 ISessionFactory
。此契约只是使用 Fluent NHibernate 的“样板”会话方法来管理会话。将以下代码放在您刚刚创建的 ISessionFactory
接口中。
// Here is the source code for the Session Factory contract
using NHibernate;
namespace KDatalayer.Contracts
{
interface ISessionFactory
{
ISession OpenSession();
}
}
让我们创建 ISessionFactory
契约的具体实现。在 KDatalayer 项目的 Sessions 文件夹中创建一个新类,并用下面提供的代码替换代码。
// Here is the source code for Sessions
using System.Reflection;
using System.Runtime.CompilerServices;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using System.IO;
using KDataLayer.Contracts;
using KSTCore.Settings;
namespace KDatalayer.Sessions
{
/// <summary>
/// Factory to create nhibernate session.
/// </summary>
public class SessionFactory : ISessionFactory
{
private readonly ISystemSettings _settings;
private readonly ISessionFactory _sessionFactory;
// Here is the Singleton session
private ISession _session;
public SessionFactory(ISystemSettings settings)
{
_settings = settings;
_sessionFactory = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.ConnectionString(settings.KodiakConnectionString))
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly
.GetExecutingAssembly()))
.BuildSessionFactory();
}
}
}
创建 LightCore 配置文件并注册依赖项
既然我们已经完成了依赖项、系统设置和会话工厂的契约和实现,我们就需要注册这些依赖项。LightCore 框架有几种注册依赖项的方法,但我只介绍其中一种。我们将通过在 VS2010 中创建一个简单的 web.config 文件并在此文件中编写 LightCore 处理程序来注册依赖项。右键单击 Kodiak 项目,然后选择 Add > New Item > Web > Configuration File。将文件名从默认名称更改为 LightCore.config。用下面的代码替换代码。重要提示:确保在 LightCore.config 文件的“属性”部分中将“Build Action”设置为 Content,“Copy to Output Directory”设置为 Copy Always。右键单击 LightCore.config 文件并选择“属性”来检查这一点。
<!--Here is the source code for your newly created web.config or app.config-->
<?xml version="1.0" encoding="utf-8" ?>
<LightCoreConfiguration
xmlns="clr-namespace:LightCore.Configuration;assembly=LightCore.Configuration">
<LightCoreConfiguration.Registrations>
<!-- Registrations -->
<Registration ContractType="KCore.Contracts.ISystemSettings, KCore"
ImplementationType="KCore.Settings.SystemSettings, KCore"/>
<Registration ContractType="KDatalayer.Contracts.IKSessionFactory, KDatalayer"
ImplementationType="KDatalayer.Sessions.SessionFactory, KDatalayer"/>
</LightCoreConfiguration.Registrations>
</LightCoreConfiguration>
还记得我们构建容器时说过它会存储注册的依赖项并解析类型吗?那么,存储的依赖项(在本例中)是使用上面的 LightCore.config 文件注册的。使用 LightCore.config 文件是注册依赖项的几种方法之一。正如您所看到的,我们注册了 SystemSettings 和 SessionFactory 依赖项的契约和实现的全限定名称。根据 LightCore 的指令,契约类型和实现类型的格式都是 Namespace.Type, Assembly,正如您在上文所见,我们遵循了此格式
例如,根据上面的代码,我们的 ISystemSettings
的契约类型是...
命名空间.类型是:KCore.Contracts.ISystemSettings
程序集是:KCore
下图展示了我们当前解决方案结构,代表了 LightCore 的注册
您需要在此处设置 LightCore.config 文件路径
<!--Place this line of code in the MvcApplication Class in the Global.asax.cs file -->
private const string ConfigurationFilePath = @"~\LightCore.config";
由于我们使用的是 Fluent NHibernate ORM,因此我们不会使用“传统”的 XML 映射方案。相反,我们将使用强类型 C# 代码映射到一个简单的数据库表。如果您还没有 Fluent NHibernate,请参考上面几个部分提供的 Fluent NHibernate 网站链接,并下载最新版本。有关如何使用此 ORM 的更详细说明,请参考其网站上的安装部分。我们将使用 LightCore 框架以及存储库来在一个表中编写、编辑和删除数据。表名为 AssignedPersons,您可以通过在 MS SQL Server Management Studio 2005/2008 中执行以下脚本来生成此表。
在此场景中,我们将一个简单的域映射到 Assigned Persons 表。
// Here is the SQL code for the AssignedPerson table
USE [Your Database Here]
GO
/****** Object: Table [dbo].[AssignedPerson]*******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[AssignedPerson](
[AssignedPersonId] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [nvarchar](255) NULL,
[MiddleName] [nvarchar](255) NULL,
[LastName] [nvarchar](255) NULL,
[AssignmentStart] [datetime] NULL,
[AssignmentEnd] [datetime] NULL,
[Assignment_id] [int] NULL,
[Installation_id] [int] NULL,
PRIMARY KEY CLUSTERED
(
[AssignedPersonId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
这是使用 Fluent NHibernate API 对上述表的映射。为了进一步解释 ORM 或对象关系映射,我将简而言之就是遍历一组对象和相关的底层数据源或存储库之间的数据。使用 ORM 的主要优点是能够将不断变化的数据与您的表示层和其他不相关的子系统进行封装。当然,使用 ORM 还有许多其他优点。
在此场景中使用 ORM 的一个更实际的原因是,它进一步加强了我们关于依赖的论点。正如我稍后将展示的,通过封装源自底层数据源的更改,只有 ORM 会受到影响,而应用程序的其余部分(主要)将不受更改的影响。我们可以轻松地对 ORM 执行单元测试,而不会影响应用程序的任何其他部分,也不会依赖于其他可能需要才能使测试成功的模块。您可以从本文中提供的几个部分之前的链接中进一步了解 Fluent NHibernate 的 ORM。
// Here is the source code for AssignedPerson Table Mapping
using FluentNHibernate.Mapping;
using KDatalayer.Entities;
namespace KDatalayer.Maps
{
public class AssignedPersonMap : ClassMap<AssignedPerson>
{
public AssignedPersonMap()
{
Id(pi => pi.Id).Column("AssignedPersonId");
Map(pi => pi.FirstName);
Map(pi => pi.MiddleName);
Map(pi => pi.LastName);
Map(pi => pi.AssignmentStart);
Map(pi => pi.AssignmentEnd);
Map(pi => pi.Assignment_id);
Map(pi => pi.Installation_id);
}
}
}
以下是 AssignedPerson
类的实体。它们是用于将数据持久化到数据库的对象集合。实体,例如下面的字符串属性 'FirstName
',是可变的,这意味着它的状态可以改变。通过创建 AssignedPerson
类的实例,例如 AssignedPerson aPerson = new AssignedPerson()
,aPerson
的状态是瞬态的。我将不涉及的其他实体状态包括持久化和分离。目前,只需了解实体是可操作并可以保存到数据库的对象。
// Here is the source code for the Fluent NHibernate Assigned Person entities
using System;
namespace KDatalayer.Entities
{
/// <summary>
/// AssignedPerson Entities
/// </summary>
public class AssignedPerson
{
public virtual Int32 Id { get; set; }
public virtual String FirstName { get; set; }
public virtual String MiddleName { get; set; }
public virtual String LastName { get; set; }
public virtual DateTime? AssignmentStart { get; set; }
public virtual DateTime? AssignmentEnd { get; set; }
public virtual Int32 Assignment_id { get; set; }
public virtual Int32 Installation_id { get; set; }
}
}
在这里,我使用了一个模型类来简单地绑定和映射已发布的表单值到我的实体类。这似乎会稍微增加架构的膨胀,但我认为这样做有助于解释一种表示域的简单方法,以及它将来可能如何用于基于模型的开发。
// Here is the source code for the Assigned Person model
using System;
namespace KModel
{
/// <summary>
/// AssignedPerson Model
/// </summary>
public class AssignedPersonModel
{
public virtual int Id { get; set; }
public virtual String FirstName { get; set; }
public virtual String MiddleName { get; set; }
public virtual String LastName { get; set; }
public virtual DateTime? AssignmentStart { get; set; }
public virtual DateTime? AssignmentEnd { get; set; }
public int AssignmentId { get; set; }
public int InstallationId { get; set; }
}
}
使用存储库模式,我们可以将 AssignedPerson
实体(FirstName
、MiddleName
、LastName
等)与任何数据访问逻辑解耦。解耦通过抽象来实现,该抽象封装了用于存储数据的任何实现知识。存储库类的逻辑编写方式使得我们可以将类与其关联的依赖项解耦,从而实现以最小的对其他类的影响进行修改。此外,任何依赖于另一个类的类的具体实现,在编译时是未知的。服务定位器用作中央引用存储库 - 引用和定位实例。然后,我们使用这些实例来执行 CRUD 操作,利用存储库模式访问数据存储。
// Here is the source code for the Repository
using System;
using System.Collections.Generic;
using System.Linq;
using KDatalayer.Contracts;
using KDatalayer.Entities;
using NHibernate.Linq;
using KCore;
namespace KDataLayer.Repositories
{
/// <summary>
/// Assigned Person's Repository
/// </summary>
public class AssignedPersonRepository
{
/// <summary>
/// Get Person based on sId
/// </summary>
/// <param name="sId"></param>
/// <returns></returns>
public AssignedPerson Find(int sId)
{
var s = ServiceLocator.Resolve<IKSessionFactory>().OpenSession();
var q = s.Query<AssignedPerson>()
.Where(p => p.Id == sId)
.ToList();
if (q.Count() != 1)
{
// Do Something: like log warning
}
return q.SingleOrDefault();
}
/// <summary>
/// Return Assigned Person based off filter
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public IEnumerable<AssignedPerson> Query(Func<AssignedPerson, bool> filter)
{
var s = ServiceLocator.Resolve<IKSessionFactory>().OpenSession();
var settings = s.Query<AssignedPerson>()
.Where(filter)
.ToList();
return settings;
}
/// <summary>
/// Save changes
/// </summary>
/// <param name="entities"></param>
public void SaveAll(IEnumerable<AssignedPerson> entities)
{
var s = ServiceLocator.Resolve<IKSessionFactory>().OpenSession();
var t = s.BeginTransaction();
try
{
foreach (var item in entities)
{
var cfg = item;
s.SaveOrUpdate(cfg);
}
t.Commit();
}
catch (Exception /*ex*/)
{
// Do Something: like log error
t.Rollback();
}
}
/// <summary>
/// Delete Assigned User of corresponding sId
/// </summary>
/// <param name="sId"></param>
public void Delete(int sId)
{
var s = ServiceLocator.Resolve<IKSessionFactory>().OpenSession();
var t = s.BeginTransaction();
try
{
var inbounds = s.Query<AssignedPerson>()
.Where(p => p.Id == sId)
.ToList();
foreach (var inbound in inbounds)
s.Delete(inbound);
t.Commit();
}
catch (Exception /*ex*/)
{
// Do Something: like log error
t.Rollback();
}
}
}
}
整合(MVC 3 控制器和视图)
从下面的代码可以看出,控制器如何充分利用存储库模式来遍历实体中的数据。为了保持代码的完整性,我没有包含视图,但您可以通过右键单击控制器操作然后选择“添加视图”菜单选项,轻松地将相应的视图添加到此项目中。视图包含在提供的演示解决方案中可供下载。
// Here is the source code for the Assigned Person model
using System;
using System.Linq;
using System.Web.Mvc;
using KDatalayer.Entities;
using KDataLayer.Repositories;
using KModel;
namespace Kodiak.Controllers
{
public class AssignedPersonController : Controller
{
public ActionResult Index()
{
var entityList = new AssignedPersonRepository().Query(
itm => true).OrderBy(p => p.AssignmentStart);
var data = entityList.Select(e => new AssignedPersonModel
{
Id = e.Id,
FirstName = e.FirstName,
MiddleName = e.MiddleName,
LastName = e.LastName,
AssignmentStart = e.AssignmentStart,
AssignmentEnd = e.AssignmentEnd,
AssignmentId = e.Assignment_id,
InstallationId = e.Installation_id
});
return View(data);
}
public ActionResult Details(int id)
{
var repo = new AssignedPersonRepository();
var entity = repo.Find(id);
var data = new AssignedPersonModel()
{
Id = entity.Id,
FirstName = entity.FirstName,
LastName = entity.LastName,
MiddleName = entity.MiddleName,
AssignmentStart = entity.AssignmentStart,
AssignmentEnd = entity.AssignmentEnd,
AssignmentId = entity.Assignment_id,
InstallationId = entity.Installation_id
};
return View(data);
}
public ActionResult Create()
{
var data = new AssignedPersonModel
{
// create a new entry
};
return View(data);
}
[HttpPost]
public ActionResult Create(AssignedPersonModel model)
{
try
{
var repo = new AssignedPersonRepository();
var entity = new AssignedPerson()
{
FirstName = model.FirstName,
LastName = model.LastName,
MiddleName = model.MiddleName,
AssignmentStart = model.AssignmentStart,
AssignmentEnd = model.AssignmentEnd,
Assignment_id = model.AssignmentId,
Installation_id = model.InstallationId
};
repo.SaveAll(new[] { entity });
return RedirectToAction("Index");
}
catch (Exception /*ex*/)
{
//Do something with the error
return View();
}
}
public ActionResult Edit(int id)
{
var repo = new AssignedPersonRepository();
var entity = repo.Find(id);
var data = new AssignedPersonModel()
{
Id = entity.Id,
FirstName = entity.FirstName,
LastName = entity.LastName,
MiddleName = entity.MiddleName,
AssignmentStart = entity.AssignmentStart,
AssignmentEnd = entity.AssignmentEnd,
AssignmentId = entity.Assignment_id,
InstallationId = entity.Installation_id
};
return View(data);
}
[HttpPost]
public ActionResult Edit(int id, AssignedPersonModel model)
{
try
{
var repo = new AssignedPersonRepository();
var entity = new AssignedPerson()
{
Id = model.Id,
FirstName = model.FirstName,
LastName = model.LastName,
MiddleName = model.MiddleName,
AssignmentStart = model.AssignmentStart,
AssignmentEnd = model.AssignmentEnd,
Assignment_id = model.AssignmentId,
Installation_id = model.InstallationId
};
repo.SaveAll(new[] { entity });
return RedirectToAction("Index");
}
catch
{
// Do something with the error
return View(model);
}
}
public ActionResult Delete(int id)
{
var repo = new AssignedPersonRepository();
var entity = repo.Find(id);
var data = new AssignedPersonModel()
{
Id = entity.Id,
FirstName = entity.FirstName,
LastName = entity.LastName,
MiddleName = entity.MiddleName,
AssignmentStart = entity.AssignmentStart,
AssignmentEnd = entity.AssignmentEnd,
AssignmentId = entity.Assignment_id,
InstallationId = entity.Installation_id
};
return View(data);
}
[HttpPost]
public ActionResult Delete(int id, AssignedPersonModel model)
{
try
{
var repo = new AssignedPersonRepository();
repo.Delete(id);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
单元测试
下面演示了我们如何轻松地测试存储库的查询和查找(lookup)方法,以及评估服务定位器是否解析了正确的依赖项进行注入。在这种情况下,解析是 SessionFactory,如果您还记得的话,我们在 LightCore.cfg 文件中“注册”了 SessionFactory 依赖项。会话工厂后面有大量的依赖项,例如连接字符串、映射和通过 Fluent NHibernate ORM 配置设置,此外,还有也依赖于会话的存储库 CRUD 操作。我们可以测试存储库方法,最大限度地减少对会话的影响,反之亦然。解决方案演示中还有更多单元测试示例,可供下载。
// Here is the source code for the Systems Settings implementation
using System.Linq;
using NHibernate.Linq;
using KDatalayer.Entities;
using KDatalayer.Contracts;
using KDataLayer.Repositories;
using KModel;
using NUnit.Framework;
using KCore;
namespace KTests.Controller_Test
{
[TestFixture()]
public class AssignedPersonRepositoryTests
{
[Test()] //Tests the assigned person Repository and Model
public void Get_AssignedPerson()
{
var entityList = new AssignedPersonRepository().Query(
itm => true).OrderBy(p => p.AssignmentStart);
var data = entityList.Select(e => new AssignedPersonModel
{
Id = e.Id,
FirstName = e.FirstName,
MiddleName = e.MiddleName,
LastName = e.LastName,
AssignmentStart = e.AssignmentStart,
AssignmentEnd = e.AssignmentEnd,
AssignmentId = e.Assignment_id,
InstallationId = e.Installation_id
});
Assert.That(entityList, Is.Not.Null);
Assert.That(data, Is.Not.Null);
}
[Test()] //Test the Repository lookup retrieval
public void Get_AssignedPerson1()
{
var gaPerson = new AssignedPersonRepository();
var rec = gaPerson.Find(3);
Assert.That(gaPerson, Is.Not.Null);
Assert.That(rec, Is.Not.Null);
}
[Test()] // Test Repository with Service Locator and Session Factory
public void Find()
{
var session = ServiceLocator.Resolve<iksessionfactory>().OpenSession();
var perAssigned = session.Query<assignedperson>()
.Where(p => p.Id == 1)
.ToList();
Assert.That(session, Is.Not.Null);
Assert.That(perAssigned, Is.Not.Null);
Assert.That(perAssigned.Count, Is.GreaterThan(0));
}
}
}