NHibernate
NHibernateは成熟した高柔軟性エンタープライズORM for .NETです。Java HibernateのC#移植として開発され、15年以上の実績を持つオープンソースOR/Mフレームワーク。精密なSQL制御、マルチデータベースサポート、エンタープライズグレードキャッシング、豊富なマッピング手法を提供し、複雑な企業システム構築に最適化されています。Data Mapperパターンにより、ドメインモデルとデータベーススキーマの分離を実現し、レガシーシステム統合や複雑なビジネスロジック実装において抜群の柔軟性を発揮します。
GitHub概要
nhibernate/nhibernate-core
NHibernate Object Relational Mapper
トピックス
スター履歴
ライブラリ
NHibernate
概要
NHibernateは成熟した高柔軟性エンタープライズORM for .NETです。Java HibernateのC#移植として開発され、15年以上の実績を持つオープンソースOR/Mフレームワーク。精密なSQL制御、マルチデータベースサポート、エンタープライズグレードキャッシング、豊富なマッピング手法を提供し、複雑な企業システム構築に最適化されています。Data Mapperパターンにより、ドメインモデルとデータベーススキーマの分離を実現し、レガシーシステム統合や複雑なビジネスロジック実装において抜群の柔軟性を発揮します。
詳細
NHibernate 2025年版は.NET Core・.NET 5+対応により現代的プラットフォームで継続利用されている成熟ORMです。HQL(Hibernate Query Language)、Criteria API、LINQ to NHibernateによる多様なクエリ記述手法を提供し、複雑な業務要件に対応。XMLマッピング、Fluent NHibernate、Attributeベースマッピングなど、柔軟な設定アプローチをサポート。First-Level Cache、Second-Level Cache、Query Cacheによる多層キャッシングシステム、Lazy Loading、Batch Fetching、Connection Poolingなど、エンタープライズ環境での性能最適化機能を包括的に提供します。
主な特徴
- 成熟したData Mapperパターン: ドメインモデルとデータベースの完全分離
- 豊富なクエリ手法: HQL、Criteria API、LINQ to NHibernateの三位一体
- マルチデータベース対応: Oracle、SQL Server、MySQL、PostgreSQL等20+のRDBMS
- 柔軟なマッピング: XML、Fluent、Attributeによる多様な設定手法
- エンタープライズキャッシング: 多層キャッシングとクエリ最適化
- 高度な継承マッピング: Table per Hierarchy/Class/Subclassの包括サポート
メリット・デメリット
メリット
- エンタープライズ分野での確固たる地位と15年以上の安定実績
- 高度なSQL制御と複雑な業務ロジックへの柔軟な対応力
- レガシーデータベーススキーマとの統合における圧倒的優位性
- 豊富なドキュメント、書籍、コミュニティリソース
- オープンソースによる透明性と自由度(Apache License 2.0)
- .NET 5/6/7/8対応により最新プラットフォームでの継続利用可能
デメリット
- Entity Framework Coreと比較して学習コストと設定複雑性が高い
- マッピング設定が冗長になりがちで、小規模プロジェクトではオーバーヘッド
- 現代的な開発手法(Code First、Migrations)のサポートが限定的
- Microsoft公式サポートがなくコミュニティベースのサポート体制
- LINQ実装がEntity Frameworkほど完全ではない場面有り
- 新人開発者の習得コストが高くチーム教育負荷が大きい
参考ページ
書き方の例
インストールとプロジェクトセットアップ
# .NET CLI での NuGet パッケージインストール
dotnet add package NHibernate
dotnet add package NHibernate.Mapping.Attributes
dotnet add package FluentNHibernate # Fluent マッピング用
# SQL Server用ドライバー
dotnet add package System.Data.SqlClient
# MySQL用ドライバー
dotnet add package MySql.Data
# PostgreSQL用ドライバー
dotnet add package Npgsql
# コンソールアプリケーション作成例
dotnet new console -n NHibernateDemo
cd NHibernateDemo
<!-- プロジェクトファイル (.csproj) での参照例 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NHibernate" Version="5.5.2" />
<PackageReference Include="NHibernate.Mapping.Attributes" Version="5.5.2" />
<PackageReference Include="FluentNHibernate" Version="3.3.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.6" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Mappings\*.hbm.xml" />
</ItemGroup>
</Project>
基本的なエンティティ定義とマッピング
// Domain/User.cs - ドメインエンティティ
using System;
using System.Collections.Generic;
namespace NHibernateDemo.Domain
{
public class User
{
public virtual int Id { get; set; }
public virtual string Username { get; set; }
public virtual string Email { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime CreatedAt { get; set; }
public virtual bool IsActive { get; set; }
// One-to-Many関係
public virtual ISet<Post> Posts { get; set; } = new HashSet<Post>();
// Computed property
public virtual string FullName => $"{FirstName} {LastName}";
// Business logic
public virtual void Activate()
{
IsActive = true;
}
public virtual void Deactivate()
{
IsActive = false;
}
}
public class Post
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual DateTime CreatedAt { get; set; }
public virtual DateTime? UpdatedAt { get; set; }
public virtual bool Published { get; set; }
// Many-to-One関係
public virtual User Author { get; set; }
// One-to-Many関係
public virtual ISet<Comment> Comments { get; set; } = new HashSet<Comment>();
}
public class Comment
{
public virtual int Id { get; set; }
public virtual string Content { get; set; }
public virtual DateTime CreatedAt { get; set; }
// Many-to-One関係
public virtual User Author { get; set; }
public virtual Post Post { get; set; }
}
}
XMLマッピング設定
<!-- Mappings/User.hbm.xml -->
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="NHibernateDemo.Domain"
assembly="NHibernateDemo">
<class name="User" table="Users">
<id name="Id" column="UserId" type="int">
<generator class="identity" />
</id>
<property name="Username" column="Username" type="string"
length="50" not-null="true" unique="true" />
<property name="Email" column="Email" type="string"
length="255" not-null="true" unique="true" />
<property name="FirstName" column="FirstName" type="string" length="50" />
<property name="LastName" column="LastName" type="string" length="50" />
<property name="CreatedAt" column="CreatedAt" type="datetime" not-null="true" />
<property name="IsActive" column="IsActive" type="boolean" not-null="true" />
<!-- One-to-Many関係 -->
<set name="Posts" table="Posts" cascade="all-delete-orphan"
lazy="true" inverse="true">
<key column="AuthorId" />
<one-to-many class="Post" />
</set>
</class>
</hibernate-mapping>
<!-- Mappings/Post.hbm.xml -->
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="NHibernateDemo.Domain"
assembly="NHibernateDemo">
<class name="Post" table="Posts">
<id name="Id" column="PostId" type="int">
<generator class="identity" />
</id>
<property name="Title" column="Title" type="string"
length="200" not-null="true" />
<property name="Content" column="Content" type="text" />
<property name="CreatedAt" column="CreatedAt" type="datetime" not-null="true" />
<property name="UpdatedAt" column="UpdatedAt" type="datetime" />
<property name="Published" column="Published" type="boolean" not-null="true" />
<!-- Many-to-One関係 -->
<many-to-one name="Author" class="User" column="AuthorId"
not-null="true" cascade="none" />
<!-- One-to-Many関係 -->
<set name="Comments" table="Comments" cascade="all-delete-orphan"
lazy="true" inverse="true">
<key column="PostId" />
<one-to-many class="Comment" />
</set>
</class>
</hibernate-mapping>
NHibernate設定とセッションファクトリー
// Configuration/NHibernateHelper.cs
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Driver;
using System.Reflection;
namespace NHibernateDemo.Configuration
{
public static class NHibernateHelper
{
private static ISessionFactory _sessionFactory;
private static readonly object Lock = new object();
public static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
lock (Lock)
{
if (_sessionFactory == null)
{
_sessionFactory = BuildSessionFactory();
}
}
}
return _sessionFactory;
}
}
private static ISessionFactory BuildSessionFactory()
{
try
{
var configuration = new NHibernate.Cfg.Configuration();
// データベース設定
configuration.SetProperty("connection.driver_class",
"NHibernate.Driver.SqlClientDriver");
configuration.SetProperty("connection.connection_string",
"Server=localhost;Database=NHibernateDemo;Integrated Security=true;TrustServerCertificate=true");
configuration.SetProperty("dialect",
"NHibernate.Dialect.MsSql2012Dialect");
// 基本設定
configuration.SetProperty("show_sql", "true");
configuration.SetProperty("format_sql", "true");
configuration.SetProperty("hbm2ddl.auto", "update"); // create, update, validate
// キャッシュ設定
configuration.SetProperty("cache.use_second_level_cache", "true");
configuration.SetProperty("cache.provider_class",
"NHibernate.Cache.HashtableCacheProvider");
configuration.SetProperty("cache.use_query_cache", "true");
// バッチ処理設定
configuration.SetProperty("adonet.batch_size", "20");
// マッピングファイル追加
configuration.AddAssembly(Assembly.GetExecutingAssembly());
return configuration.BuildSessionFactory();
}
catch (Exception ex)
{
throw new Exception("SessionFactory作成エラー", ex);
}
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
public static void CloseSessionFactory()
{
if (_sessionFactory != null)
{
_sessionFactory.Close();
}
}
}
}
// appsettings.json での設定管理例
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=NHibernateDemo;Integrated Security=true;TrustServerCertificate=true"
},
"NHibernate": {
"ShowSql": true,
"FormatSql": true,
"CacheEnabled": true,
"BatchSize": 20
}
}
基本的なCRUD操作とセッション管理
// Services/UserService.cs
using NHibernate;
using NHibernateDemo.Configuration;
using NHibernateDemo.Domain;
namespace NHibernateDemo.Services
{
public class UserService
{
// Create - ユーザー作成
public int CreateUser(User user)
{
using (var session = NHibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
user.CreatedAt = DateTime.Now;
var id = (int)session.Save(user);
transaction.Commit();
return id;
}
catch
{
transaction.Rollback();
throw;
}
}
}
// Read - ユーザー取得
public User GetUser(int id)
{
using (var session = NHibernateHelper.OpenSession())
{
return session.Get<User>(id);
}
}
public User GetUserByUsername(string username)
{
using (var session = NHibernateHelper.OpenSession())
{
return session.Query<User>()
.FirstOrDefault(u => u.Username == username);
}
}
public IList<User> GetActiveUsers()
{
using (var session = NHibernateHelper.OpenSession())
{
return session.Query<User>()
.Where(u => u.IsActive)
.OrderBy(u => u.Username)
.ToList();
}
}
// Update - ユーザー更新
public void UpdateUser(User user)
{
using (var session = NHibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
session.Update(user);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
// Delete - ユーザー削除
public void DeleteUser(int id)
{
using (var session = NHibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
var user = session.Get<User>(id);
if (user != null)
{
session.Delete(user);
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
// 複雑なクエリ例
public IList<User> SearchUsers(string searchTerm, bool includeInactive = false)
{
using (var session = NHibernateHelper.OpenSession())
{
var query = session.Query<User>()
.Where(u => u.Username.Contains(searchTerm) ||
u.Email.Contains(searchTerm) ||
u.FirstName.Contains(searchTerm) ||
u.LastName.Contains(searchTerm));
if (!includeInactive)
{
query = query.Where(u => u.IsActive);
}
return query.OrderBy(u => u.Username).ToList();
}
}
}
}
HQLとCriteria APIを使った高度なクエリ
// Services/PostService.cs
using NHibernate;
using NHibernate.Criterion;
using NHibernateDemo.Configuration;
using NHibernateDemo.Domain;
namespace NHibernateDemo.Services
{
public class PostService
{
// HQL (Hibernate Query Language) を使ったクエリ
public IList<Post> GetPostsByAuthorHQL(string username)
{
using (var session = NHibernateHelper.OpenSession())
{
var hql = @"
FROM Post p
INNER JOIN FETCH p.Author a
WHERE a.Username = :username
AND p.Published = true
ORDER BY p.CreatedAt DESC";
return session.CreateQuery(hql)
.SetParameter("username", username)
.List<Post>();
}
}
// 名前付きクエリ(マッピングファイルで定義)
public IList<Post> GetRecentPosts(int days)
{
using (var session = NHibernateHelper.OpenSession())
{
return session.GetNamedQuery("GetRecentPosts")
.SetParameter("days", days)
.List<Post>();
}
}
// Criteria API を使った動的クエリ
public IList<Post> SearchPosts(string title = null, string authorName = null,
DateTime? fromDate = null, DateTime? toDate = null, bool? published = null)
{
using (var session = NHibernateHelper.OpenSession())
{
var criteria = session.CreateCriteria<Post>("p")
.CreateAlias("p.Author", "a");
// 動的条件追加
if (!string.IsNullOrEmpty(title))
{
criteria.Add(Restrictions.Like("p.Title", title, MatchMode.Anywhere));
}
if (!string.IsNullOrEmpty(authorName))
{
criteria.Add(Restrictions.Like("a.Username", authorName, MatchMode.Anywhere));
}
if (fromDate.HasValue)
{
criteria.Add(Restrictions.Ge("p.CreatedAt", fromDate.Value));
}
if (toDate.HasValue)
{
criteria.Add(Restrictions.Le("p.CreatedAt", toDate.Value));
}
if (published.HasValue)
{
criteria.Add(Restrictions.Eq("p.Published", published.Value));
}
return criteria
.AddOrder(Order.Desc("p.CreatedAt"))
.List<Post>();
}
}
// ページネーション付きクエリ
public IList<Post> GetPostsPaged(int pageNumber, int pageSize)
{
using (var session = NHibernateHelper.OpenSession())
{
return session.Query<Post>()
.Where(p => p.Published)
.OrderByDescending(p => p.CreatedAt)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
}
}
// 統計クエリ
public object GetPostStatistics()
{
using (var session = NHibernateHelper.OpenSession())
{
var hql = @"
SELECT
COUNT(*) as TotalPosts,
COUNT(CASE WHEN p.Published = true THEN 1 END) as PublishedPosts,
AVG(SIZE(p.Comments)) as AvgCommentsPerPost,
MAX(p.CreatedAt) as LatestPostDate
FROM Post p";
return session.CreateQuery(hql).UniqueResult();
}
}
}
}
Fluent NHibernateによるマッピング
// Mappings/UserMap.cs
using FluentNHibernate.Mapping;
using NHibernateDemo.Domain;
namespace NHibernateDemo.Mappings
{
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
// Primary Key
Id(x => x.Id, "UserId").GeneratedBy.Identity();
// Properties
Map(x => x.Username).Column("Username").Length(50).Not.Nullable().Unique();
Map(x => x.Email).Column("Email").Length(255).Not.Nullable().Unique();
Map(x => x.FirstName).Column("FirstName").Length(50);
Map(x => x.LastName).Column("LastName").Length(50);
Map(x => x.CreatedAt).Column("CreatedAt").Not.Nullable();
Map(x => x.IsActive).Column("IsActive").Not.Nullable();
// Relationships
HasMany(x => x.Posts)
.KeyColumn("AuthorId")
.Cascade.AllDeleteOrphan()
.Inverse()
.LazyLoad();
}
}
public class PostMap : ClassMap<Post>
{
public PostMap()
{
Table("Posts");
Id(x => x.Id, "PostId").GeneratedBy.Identity();
Map(x => x.Title).Column("Title").Length(200).Not.Nullable();
Map(x => x.Content).Column("Content").CustomType("StringClob");
Map(x => x.CreatedAt).Column("CreatedAt").Not.Nullable();
Map(x => x.UpdatedAt).Column("UpdatedAt");
Map(x => x.Published).Column("Published").Not.Nullable();
References(x => x.Author)
.Column("AuthorId")
.Not.Nullable()
.LazyLoad();
HasMany(x => x.Comments)
.KeyColumn("PostId")
.Cascade.AllDeleteOrphan()
.Inverse()
.LazyLoad();
}
}
}
// Configuration/FluentNHibernateHelper.cs
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
namespace NHibernateDemo.Configuration
{
public static class FluentNHibernateHelper
{
private static ISessionFactory _sessionFactory;
public static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
_sessionFactory = CreateSessionFactory();
}
return _sessionFactory;
}
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012
.ConnectionString("Server=localhost;Database=NHibernateDemo;Integrated Security=true;TrustServerCertificate=true")
.ShowSql()
.FormatSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>())
.Cache(c => c
.UseSecondLevelCache()
.ProviderClass<NHibernate.Cache.HashtableCacheProvider>()
.UseQueryCache())
.BuildSessionFactory();
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
}
トランザクションとバッチ処理
// Services/BatchService.cs
using NHibernate;
using NHibernateDemo.Configuration;
using NHibernateDemo.Domain;
namespace NHibernateDemo.Services
{
public class BatchService
{
// バッチ挿入処理
public void BulkInsertUsers(IEnumerable<User> users)
{
using (var session = NHibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
int count = 0;
foreach (var user in users)
{
user.CreatedAt = DateTime.Now;
session.Save(user);
// バッチサイズごとにフラッシュ
if (++count % 20 == 0)
{
session.Flush();
session.Clear();
}
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
// バッチ更新処理
public int BulkUpdateUserStatus(bool isActive, DateTime beforeDate)
{
using (var session = NHibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
var hql = @"
UPDATE User u
SET u.IsActive = :isActive
WHERE u.CreatedAt < :beforeDate";
var result = session.CreateQuery(hql)
.SetParameter("isActive", isActive)
.SetParameter("beforeDate", beforeDate)
.ExecuteUpdate();
transaction.Commit();
return result;
}
catch
{
transaction.Rollback();
throw;
}
}
}
// 複雑なトランザクション処理
public void TransferPostOwnership(int fromUserId, int toUserId)
{
using (var session = NHibernateHelper.OpenSession())
using (var transaction = session.BeginTransaction())
{
try
{
var fromUser = session.Get<User>(fromUserId);
var toUser = session.Get<User>(toUserId);
if (fromUser == null || toUser == null)
{
throw new ArgumentException("Invalid user ID");
}
// 投稿の所有者変更
var posts = session.Query<Post>()
.Where(p => p.Author.Id == fromUserId)
.ToList();
foreach (var post in posts)
{
post.Author = toUser;
session.Update(post);
}
// ログ記録
var log = new SystemLog
{
Message = $"Transferred {posts.Count} posts from user {fromUserId} to {toUserId}",
CreatedAt = DateTime.Now
};
session.Save(log);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
}
}