65.9K
CodeProject 正在变化。 阅读更多。
Home

使用泛型 Fluent NHibernate 简化数据库操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (6投票s)

2012年5月7日

CPOL

7分钟阅读

viewsIcon

79502

downloadIcon

2897

本文介绍使用泛型 Fluent NHibernate 实现数据库通信。

介绍 

由于存在多种需求,为大型企业应用程序开发功能丰富的数据库库可能很困难。这些说明已使用以下技术进行了全面测试:

  • .NET Framework 4.0
  • ASP.NET MVC 3 (带 Razor)
  • Fluent NHibernate 1.3 和 NHibernate 3.2。
  • Microsoft SQL Server 2008 R2 和 SQL CE 数据库。

本文提供了一个完整的泛型 Fluent NHibernate 实现,但没有深入探讨其附加功能的详细信息。访问 Fluent NHibernate 的网站以获取有关实现的更多信息。 

背景  

NHibernate 是一个面向 .NET 平台的面向对象关系映射软件包,它提供将面向对象类映射到关系数据库表的代码。它抽象了与数据库交互所需的 SQL,因此开发人员可以专注于应用程序的业务需求。Fluent NHibernate 为 NHibernate 提供无 XML 映射,允许使用标准的泛型实现提供更简洁的界面,实现相同的功能。可以使用 NuGet 直接将 Fluent NHibernate 下载到您的项目中,NuGet 会检查依赖项,然后下载并引用开始所需的文件。可以在程序包管理器控制台中执行以下命令来检索 Fluent NHibernate 及其依赖项。  

Install-Package FluentNHibernate –Project Your.Project.Name

步骤 1:创建会话管理器

Fluent NHibernate 会话管理器用于管理开发人员将用于连接数据库的会话。管理器的必需功能是持有对会话的引用、实现单元工作设计模式、管理到数据库的事务以及在需要时处理会话的清理和处置。下面显示了会话管理器的接口,其中包含将要使用的会话、回滚和提交函数以及清理函数。

public interface IFNHSessionManager
{
    ISession Session { get; }

    void CleanUp();
    void Rollback();
    void Commit();
}
 

会话管理器需要几个属性才能正常运行。它需要持有对会话工厂、会话和事务的引用。事务可以设为私有,因为我们将在该类的后续部分公开 Rollback 和 Commit 函数,这些函数允许实现单元工作设计模式。添加到 Session 属性的代码是将它绑定到 HttpContext。这样,清理和处置函数就可以检索绑定的 Session 来完成请求。

private readonly ISessionFactory _sessionFactory;

public ISessionFactory SessionFactory
{
    get { return _sessionFactory; }
}

public ISession Session
{
    get
    {
        if (!ManagedWebSessionContext.HasBind(HttpContext.Current, SessionFactory))
        {
            ManagedWebSessionContext.Bind(HttpContext.Current, SessionFactory.OpenSession());
        }
        return _sessionFactory.GetCurrentSession();
    }
}

private readonly ITransaction _transaction;

接下来,我们需要一个用于连接所有内容的会话管理器构造函数。配置代码可以在单独的类(通常称为 FNHibernateHelper)中实现,但为了简单起见,我已将其包含在构造函数中。使用 Fluent NHibernate 和 ASP.NET Web 应用程序时,另一个重要步骤是,当您将 Session 绑定到 HttpContext 时,还要添加一行将 Cleanup 函数绑定到 EndRequest 事件。

public FNHSessionManager(string dbConfigKey, DatabaseType dbType)
{
    switch (dbType)
    {
        case DatabaseType.MsSql:
            _sessionFactory = Fluently.Configure()
                .Database(
                    MsSqlConfiguration.MsSql2008
                        .ConnectionString(dbConfigKey)
                )
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<T>())
                .CurrentSessionContext(typeof(ManagedWebSessionContext).FullName)
                .BuildSessionFactory();
            break;
        case DatabaseType.MsSqlCe:
            _sessionFactory = Fluently.Configure()
                .Database(
                    MsSqlConfiguration.MsSql2008
                        .ConnectionString(c => c.FromConnectionStringWithKey(dbConfigKey))
                )
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<T>())
                .CurrentSessionContext(typeof(ManagedWebSessionContext).FullName)
                .BuildSessionFactory();
            break;
    }

    if (HttpContext.Current != null && HttpContext.Current.ApplicationInstance != null)
    {
        HttpContext.Current.ApplicationInstance.EndRequest += (sender, args) => CleanUp();
    }

    _transaction = Session.BeginTransaction();
}

Rollback 和 Commit 函数是单元工作设计模式的实现。如下所示,它允许开发人员将一个过程(创建、更新、删除和检索是最常见的)作为单个事务运行,该事务可以合并到对数据库的一个请求中。

public void Rollback()
{
    if (_transaction.IsActive)
        _transaction.Rollback();
}

public void Commit()
{
    if (_transaction.IsActive)
        _transaction.Commit();
}

最后,管理器实现清理/处置函数,在请求结束时完成事务。Dispose 函数实现 Cleanup 函数并处置当前会话。此函数应仅在应用程序的结束事件处调用,而不是在请求结束时调用。有两个清理函数,一个泛型函数允许将 Session 绑定到任何上下文,而另一个则将上下文指定为当前的 HttpContext。这将是 Cleanup 函数更常见的用法。

/// <span class="code-SummaryComment"><summary>
</span>/// Clean up the session.
/// <span class="code-SummaryComment"></summary>
</span>public void CleanUp()
{
    CleanUp(HttpContext.Current, _sessionFactory);
}

/// <span class="code-SummaryComment"><summary>
</span>/// Static function to clean up the session.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="context">The context to which the session has been bound.</param>
</span>/// <span class="code-SummaryComment"><param name="sessionFactory">The session factory that contains the session. </param>
</span>public static void CleanUp(HttpContext context, ISessionFactory sessionFactory)
{
    ISession session = ManagedWebSessionContext.Unbind(context, sessionFactory);

    if (session != null)
    {
        if (session.Transaction != null && session.Transaction.IsActive)
        {
            session.Transaction.Rollback();
        }
        else if (context.Error == null && session.IsDirty())
        {
            session.Flush();
        }
        session.Close();
    }
}

/// <span class="code-SummaryComment"><summary>
</span>/// Dispose of the session factory.
/// <span class="code-SummaryComment"></summary>
</span>public void Dispose()
{
    CleanUp();
    _sessionFactory.Dispose();
}

至此,实现了 NHibernate 会话管理器,该管理器实现了一个类来管理会话和单元工作。此代码可以在名为 IFNHSessionManager.cs 和 FNHSessionManager.cs 的文件中找到。

步骤 2:创建泛型存储库

FNHRepository 用于管理数据库操作(例如,创建、检索、更新和删除)。它包含对先前(步骤 1)开发的 FNHSessionManager 的引用,以便它可以访问可以执行指定操作的会话。下面显示的接口显示了 NHibernate 在与数据库通信时将提供的主要操作。NHibernate 的其他一些功能也可以在这里利用,包括利用 LINQ 的功能(本文不讨论)。请注意,实现是泛型的,因此我们不必为我们创建的每个对象实现存储库。

public interface IFNHRepository<T>
{
    void Create(T objectToAdd);
    T RetrieveById(int id);
    void Update(T objectToUpdate);
    void Delete(T objectToDelete);
}

以下是 FNHRepository 中包含的唯一属性 - 对步骤 1 中开发的会话管理器的引用。此属性是只读的,因为它可以在构造函数中设置,并且在其生命周期的其余时间都应该保持不变。

public readonly IFNHSessionManager _sessionManager;

接下来,我们需要一个构造函数来创建存储库,并提供一个对使用数据库类型和连接字符串创建的会话管理器对象的引用,该字符串对应于请求的操作。

public FNHRepository(IFNHSessionManager sessionManager)
{
    _sessionManager = sessionManager;
}

最后,存储库需要实现 NHibernate API 以与数据库通信。此处实现的基本功能包括 CRUD 操作(创建、检索、更新和删除)。

/// <span class="code-SummaryComment"><summary>
</span>/// Retrieve an object instance from the database by id.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="id">Id of the object to retrieve.</param>
</span>/// <span class="code-SummaryComment"><returns>The object instance to use in the application.</returns>
</span>public T RetrieveById(int id)
{
    return _sessionManager.Session.Get<T>(id);
}

/// <span class="code-SummaryComment"><summary>
</span>/// Update an object in the database.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="objectToUpdate">Object instance containing the information to change in the database.</param>
</span>public void Update(T objectToUpdate)
{
    _sessionManager.Session.Update(objectToUpdate);
    _sessionManager.Commit();
}

/// <span class="code-SummaryComment"><summary>
</span>/// Create an object in the database.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="objectToAdd">Object instance containing the information to add to the database.</param>
</span>public void Create(T objectToAdd)
{
    _sessionManager.Session.Save(objectToAdd);
    _sessionManager.Commit();
}

/// <span class="code-SummaryComment"><summary>
</span>/// Delete an object from the database.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="objectToDelete">Object instance containing the information to delete from the database.</param>
</span>public void Delete(T objectToDelete)
{
    _sessionManager.Session.Delete(objectToDelete);
    _sessionManager.Commit();
}

至此,实现了 NHibernate 存储库,该存储库允许执行与数据库通信的操作。此步骤中包含的代码可以在 IFNHRepository.cs 和 FNHRepository.cs 文件中找到。

步骤 3:创建对象

应用程序运行所需的任何业务对象都必须接下来创建,以便有一个要工作的域。这些对象是 Web 应用程序中将使用的模型,因此可能需要使用数据属性和其他功能进一步定义这些对象。在此示例中,我创建了三个类:Employee、Role 和 Task。

Employee.cs

public class Employee
{

    public virtual int Id { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual string Position { get; set; }
    public virtual ICollection<Role> EmployeeRoles { get; set; }

    public Employee()
    {
        EmployeeRoles = new HashSet<Role>();
    }
}

Role.cs

public class Role
{

    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Employee> Employees { get; set; }
    public virtual ICollection<Task> Tasks { get; set; }

    public Role()
    {
        Employees = new HashSet<Employee>();
        Tasks = new HashSet<Task>();
    }
}

Task.cs

public class Task
{

    public virtual int Id { get; set; }
    public virtual string TaskName { get; set; }
    public virtual string Description { get; set; }

    public Task()
    {
    }
}

这里有一点需要注意,您希望映射的每个属性都需要设置为 virtual。其次,每当使用集合时,属性都应使用接口定义,并在构造函数中初始化为具体的类。

步骤 4:创建对象映射

在实现用户界面之前的最后一步是定义对象映射,以便 NHibernate 知道如何处理属性。

EmployeeMap.cs

public class EmployeeMap : ClassMap<Employee>
{

    public EmployeeMap()
    {
        Schema("dbo");
        Table("Employees");

        Id(x => x.Id).Column("EmployeeId");
        Map(x => x.FirstName);
        Map(x => x.LastName);
        Map(x => x.Position);
        HasManyToMany<Role>(x => x.EmployeeRoles).Table("xrEmployeeRoles")
		.ParentKeyColumn("EmployeeId").ChildKeyColumn("RoleId").AsSet()
		.Not.LazyLoad();
    }
}

RoleMap.cs

public class RoleMap : ClassMap<Role>
{

    public RoleMap()
    {
        Schema("dbo");
        Table("Roles");

        Id(x => x.Id).Column("RoleId");
        Map(x => x.Name);
        HasManyToMany<Employee>(x => x.Employees).Table("xrEmployeeRoles")
		.ParentKeyColumn("RoleId").ChildKeyColumn("EmployeeId")
		.Inverse().AsSet().Not.LazyLoad().Cascade.All();
        HasManyToMany<Task>(x => x.Tasks).Table("xrRoleTasks")
		.ParentKeyColumn("RoleId").ChildKeyColumn("TaskId")
		.AsSet().Not.LazyLoad().Cascade.All();
    }
}

TaskMap.cs

public class TaskMap : ClassMap<Task>
{

    public TaskMap()
    {
        Schema("dbo");
        Table("Tasks");

        Id(x => x.Id).Column("TaskId");
        Map(x => x.TaskName);
        Map(x => x.Description);
    }
}

关于映射有几点需要注意。我已显式指定了模式(Schema)和表(Table)以减轻对数据来源的任何混淆。接下来,我的数据库实体具有与我的 C# 对象中实现的标识不同的列名。因此,必须将列名作为映射的一部分进行指定。由于有两个交叉引用表,这在这些对象之间创建了多对多关系。映射必须反映要引用的表以及父键和子键,才能正确执行。多对多关系最终成为一个集合(set),因此也必须在映射中指定。

步骤 5:利用 Fluent NHibernate

一旦实现了上面的类库,剩下的就是用它来开发用户界面。在 Employee 表中输入一条记录,并替换 RetrieveById 调用中的 1 以获取您的记录。

HomeController.cs

FNHSessionManager<Employee> sessionManager = new FNHSessionManager<Employee>(_connString, FNHSessionManager<Employee>.DatabaseType.MsSql);
FNHRepository<Employee> repository = new FNHRepository<Employee>(sessionManager);

Employee emp = repository.RetrieveById(1);

上面显示的示例代码创建了一个 SessionManager 实例,并将其传递给 Repository。创建 Repository 后,就可以使用它与数据库进行交互。

关注点

Fluent NHibernate 使得从数据库获取数据变得无比简单。使用 Fluent NHibernate 创建一个具有通用数据库 CRUD 工具的 Web 项目,就像本文所记录的那样简单,并且可以快速轻松地进行开发。如果您对本文代码的测试解决方案感兴趣,我很乐意撰写一篇后续文章。

历史

截至目前无更改。

© . All rights reserved.