NHibernate - Отношения
В этой главе мы рассмотрим отношения в NHibernate. Давайте обратим наше внимание на то, как мы можем понять отношения в NHibernate. Самый простой способ - подумать об отношениях с точки зрения базы данных.
Сначала мы создадим новое приложение, в котором мы создадим некоторые отношения между объектами клиента и заказа.
Первые отношения, которые мы рассмотрим, - это классические отношения коллекционирования.
У нас есть заказчик со сбором заказов.
Это отношение «один ко многим», оно представлено в базе данных двумя таблицами, в таблице заказов есть идентификатор клиента, и у нас есть отношение внешнего ключа к клиенту.
Для начала нам нужно создать базу данных и две таблицы Customer и Order. Вы можете создать его, указав следующий запрос в SQL Server Explorer.
USE [master]
GO
CREATE DATABASE [NHibernateDemo]
GO
USE [NHibernateDemo]
GO
CREATE TABLE [dbo].[Customer](
[Id] [uniqueidentifier] NOT NULL,
[FirstName] [nvarchar](100) NOT NULL,
[LastName] [nvarchar](100) NOT NULL,
[Points] [int] NULL, [HasGoldStatus] [bit] NULL,
[MemberSince] [date] NULL,
[CreditRating] [nchar](20) NULL,
[AverageRating] [decimal](18, 4) NULL,
[Street] [nvarchar](100) NULL,
[City] [nvarchar](100) NULL,
[Province] [nvarchar](100) NULL,
[Country] [nvarchar](100) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
)
GO
CREATE TABLE [dbo].[Order](
[Id] [uniqueidentifier] NOT NULL,
[CustomerId] [uniqueidentifier] NULL,
[Ordered] [datetime] NULL,
[Shipped] [datetime] NULL,
[Street] [nvarchar](100) NULL,
[City] [nvarchar](100) NULL,
[Province] [nvarchar](100) NULL,
[Country] [nvarchar](100) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
)
GO
Это создаст две таблицы в базе данных. На следующем изображении показана таблица клиентов.
На следующем изображении показана таблица заказов, в которой вы можете увидеть отношение внешнего ключа к клиенту.
Нам нужно определить строку подключения в app.config файл, вот реализация файла app.config.
<?xml version = "1.0" encoding = "utf-8" ?>
<configuration>
<connectionStrings>
<add name = "default" connectionString = "Data Source =
(localdb)\MSSQLLocalDB;Initial Catalog = NHibernateDemo;Integrated Security =
True;Connect Timeout = 30;Encrypt = False;TrustServerCertificate = False;
ApplicationIntent = ReadWrite;MultiSubnetFailover = False"/>
</connectionStrings>
</configuration>
Чтобы установить NHibernate в свое приложение, выполните следующую команду в окне консоли NuGet Manager.
install-package NHibernate
Чтобы настроить конфигурацию NHibernate, нам нужно определить конфигурацию в hibernate.cfg.xml файл, как показано в следующем коде.
<xml version = "1.0" encoding = "utf-8" ?>
<hibernate-configuration xmlns = "urn:nhibernate-configuration-2.2">
<session-factory>
<property name = "connection.connection_string_name">default</property>
<property name = "connection.driver_class">
NHibernate.Driver.SqlClientDriver
</property>
<property name = "dialect">
NHibernate.Dialect.MsSql2008Dialect
</property>
<property name = "show_sql">true</property>
</session-factory>
</hibernate-configuration>
В этом примере мы будем работать с двумя доменными классами: Customer и Order.
Вот реализация файла Customer.cs, в котором у нас есть два класса: один - это класс Customer, а другой - класс Location, в котором объект используется как адрес в классе Customer.
using System;
using System.Text;
using Iesi.Collections.Generic;
namespace NHibernateDemo {
public class Customer {
public Customer() {
MemberSince = DateTime.UtcNow;
Orders = new HashedSet<Order>();
}
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual double AverageRating { get; set; }
public virtual int Points { get; set; }
public virtual bool HasGoldStatus { get; set; }
public virtual DateTime MemberSince { get; set; }
public virtual CustomerCreditRating CreditRating { get; set; }
public virtual Location Address { get; set; }
public virtual ISet<Order> Orders { get; set; }
public virtual void AddOrder(Order order) { Orders.Add(order); order.Customer = this; }
public override string ToString() {
var result = new StringBuilder();
result.AppendFormat("{1} {2} ({0})\r\n\tPoints: {3}\r\n\tHasGoldStatus:
{4}\r\n\tMemberSince: {5} ({7})\r\n\tCreditRating: {6}\r\n\tAverageRating:
{8}\r\n", Id, FirstName, LastName, Points, HasGoldStatus, MemberSince,
CreditRating, MemberSince.Kind, AverageRating);
result.AppendLine("\tOrders:");
foreach(var order in Orders) {
result.AppendLine("\t\t" + order);
}
return result.ToString();
}
}
public class Location {
public virtual string Street { get; set; }
public virtual string City { get; set; }
public virtual string Province { get; set; }
public virtual string Country { get; set; }
}
public enum CustomerCreditRating {
Excellent,
VeryVeryGood,
VeryGood,
Good,
Neutral,
Poor,
Terrible
}
}
Вот файл сопоставления Customer.hbm.xml в котором класс Customer отображается в таблицу Customer.
<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo"
namespace = "NHibernateDemo">
<class name = "Customer">
<id name = "Id">
<generator class = "guid.comb"/>
</id>
<property name = "FirstName"/>
<property name = "LastName"/>
<property name = "AverageRating"/>
<property name = "Points"/>
<property name = "HasGoldStatus"/>
<property name = "MemberSince" type = "UtcDateTime"/>
<property name = "CreditRating" type = "CustomerCreditRatingType"/>
<component name = "Address">
<property name = "Street"/>
<property name = "City"/>
<property name = "Province"/>
<property name = "Country"/>
</component>
</class>
</hibernate-mapping>
У нас также есть класс заказа, и вот реализация Order.cs файл.
using System; using Iesi.Collections.Generic;
namespace NHibernateDemo {
public class Order {
public virtual Guid Id { get; set; }
public virtual DateTime Ordered { get; set; }
public virtual DateTime? Shipped { get; set; }
public virtual Location ShipTo { get; set; }
public virtual Customer Customer { get; set; }
public override string ToString() {
return string.Format("Order Id: {0}", Id);
}
}
}
Отношения многие-к-одному
Нам также необходимо сопоставить класс Order с таблицей Order в базе данных, так что вот реализация Order.hbm.xml файл.
<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo"
namespace = "NHibernateDemo">
<class name = "Order" table = "`Order`">
<id name = "Id">
<generator class = "guid.comb"/>
</id>
<property name = "Ordered"/>
<property name = "Shipped"/>
<component name = "ShipTo">
<property name = "Street"/>
<property name = "City"/>
<property name = "Province"/>
<property name = "Country"/>
</component>
<!--<many-to-one name = "Customer" column = "CustomerId" cascade =
"save-update"/>-->
</class>
</hibernate-mapping>
Отношения один-ко-многим
Здесь мы собираемся взглянуть на отношения «один ко многим», в данном случае между клиентом и заказами. У нас есть наш клиент, мы создаем нового, и вы можете видеть, что коллекция инициализируется следующей парой заказов.
private static Customer CreateCustomer() {
var customer = new Customer {
FirstName = "John",
LastName = "Doe",
Points = 100,
HasGoldStatus = true,
MemberSince = new DateTime(2012, 1, 1),
CreditRating = CustomerCreditRating.Good,
AverageRating = 42.42424242,
Address = CreateLocation()
};
var order1 = new Order {
Ordered = DateTime.Now
};
customer.AddOrder(order1);
var order2 = new Order {
Ordered = DateTime.Now.AddDays(-1),
Shipped = DateTime.Now,
ShipTo = CreateLocation()
};
customer.AddOrder(order2);
return customer;
}
Итак, мы создадим нового клиента, а затем сохраним его, после сохранения мы найдем идентификатор, а затем перезагрузим его в другом сеансе в методе Main, как показано в следующей программе.
private static void Main() {
var cfg = ConfigureNHibernate();
var sessionFactory = cfg.BuildSessionFactory();
Guid id;
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var newCustomer = CreateCustomer();
Console.WriteLine("New Customer:");
Console.WriteLine(newCustomer);
session.Save(newCustomer);
id = newCustomer.Id;
tx.Commit();
}
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var reloaded = session.Load<Customer>(id);
Console.WriteLine("Reloaded:");
Console.WriteLine(reloaded);
tx.Commit();
}
Console.WriteLine("Press <ENTER> to exit...");
Console.ReadLine();
}
Вот полный Program.cs реализация файла.
using System;
using System.Data;
using System.Linq;
using System.Reflection;
using HibernatingRhinos.Profiler.Appender.NHibernate;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Linq;
namespace NHibernateDemo {
internal class Program {
private static void Main() {
var cfg = ConfigureNHibernate();
var sessionFactory = cfg.BuildSessionFactory();
Guid id;
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var newCustomer = CreateCustomer();
Console.WriteLine("New Customer:");
Console.WriteLine(newCustomer);
session.Save(newCustomer);
id = newCustomer.Id;
tx.Commit();
}
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var reloaded = session.Load<Customer>(id);
Console.WriteLine("Reloaded:");
Console.WriteLine(reloaded);
tx.Commit();
}
Console.WriteLine("Press <ENTER> to exit...");
Console.ReadLine();
}
private static Customer CreateCustomer() {
var customer = new Customer {
FirstName = "John",
LastName = "Doe",
Points = 100,
HasGoldStatus = true,
MemberSince = new DateTime(2012, 1, 1),
CreditRating = CustomerCreditRating.Good,
AverageRating = 42.42424242,
Address = CreateLocation()
};
var order1 = new Order {
Ordered = DateTime.Now
};
customer.AddOrder(order1);
var order2 = new Order {
Ordered = DateTime.Now.AddDays(-1),
Shipped = DateTime.Now,
ShipTo = CreateLocation()
};
customer.AddOrder(order2);
return customer;
}
private static Location CreateLocation() {
return new Location {
Street = "123 Somewhere Avenue",
City = "Nowhere",
Province = "Alberta",
Country = "Canada"
};
}
private static Configuration ConfigureNHibernate() {
NHibernateProfiler.Initialize();
var cfg = new Configuration();
cfg.DataBaseIntegration(x =&ht; {
x.ConnectionStringName = "default";
x.Driver<SqlClientDriver>();
x.Dialect<MsSql2008Dialect>();
x.IsolationLevel = IsolationLevel.RepeatableRead;
x.Timeout = 10; x.BatchSize = 10;
});
cfg.SessionFactory().GenerateStatistics();
cfg.AddAssembly(Assembly.GetExecutingAssembly());
return cfg;
}
}
}
Когда вы запустите это приложение, вы увидите следующий результат.
New Customer:
John Doe (00000000-0000-0000-0000-000000000000)
Points: 100
HasGoldStatus: True
MemberSince: 1/1/2012 12:00:00 AM (Unspecified)
CreditRating: Good
AverageRating: 42.42424242
Orders:
Order Id: 00000000-0000-0000-0000-000000000000
Order Id: 00000000-0000-0000-0000-000000000000
Reloaded:
John Doe (9b0fcf10-83f6-4f39-bda5-a5b800ede2ba)
Points: 100
HasGoldStatus: True
MemberSince: 1/1/2012 12:00:00 AM (Utc)
CreditRating: Good
AverageRating: 42.4242
Orders:
Press <ENTER> to exit...
Как видите, изначально у покупателя 2 заказа, но когда мы его перезагружаем, заказов не видно. Если вы посмотрите наcustomer.hbm.xmlфайл, вы можете увидеть здесь, что мы не отображаем фактический сбор заказов. Итак, NHibernate ничего об этом не знает. Давайте продолжим и добавим это.
<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2"
assembly = "NHibernateDemo" namespace = "NHibernateDemo">
<class name = "Customer">
<id name = "Id">
<generator class = "guid.comb"/>
</id>
<property name = "FirstName"/>
<property name = "LastName"/>
<property name = "AverageRating"/>
<property name = "Points"/>
<property name = "HasGoldStatus"/>
<property name = "MemberSince" type = "UtcDateTime"/>
<property name = "CreditRating" type = "CustomerCreditRatingType"/>
<component name = "Address">
<property name = "Street"/>
<property name = "City"/>
<property name = "Province"/>
<property name = "Country"/>
</component>
<set name = "Orders" table = "`Order`">
<key column = "CustomerId"/>
<one-to-many class = "Order"/>
</set>
</class>
</hibernate-mapping>
Это набор, имя которого - «Заказы», которое хранится в таблице под названием порядок. Нам нужно указать ключ, который является именем внешнего ключа или для поиска заказов. Эти заказы идентифицируются или принадлежат клиенту через идентификатор клиента. И затем я должен отметить, что это отношение «один ко многим», и оно связано с классом порядка.
Нам также необходимо немного изменить метод Main, сохранив новые заказы клиентов в базе данных, как показано в следующей программе.
private static void Main() {
var cfg = ConfigureNHibernate();
var sessionFactory = cfg.BuildSessionFactory();
Guid id;
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var newCustomer = CreateCustomer();
Console.WriteLine("New Customer:");
Console.WriteLine(newCustomer);
session.Save(newCustomer);
foreach (var order in newCustomer.Orders) {
session.Save(order);
}
id = newCustomer.Id;
tx.Commit();
}
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var reloaded = session.Load<Customer>(id);
Console.WriteLine("The orders were ordered by: ");
foreach (var order in reloaded.Orders) {
Console.WriteLine(order.Customer);
}
tx.Commit();
}
Console.WriteLine("Press <ENTER> to exit..."); Console.ReadLine();
}
Мы также указали, какой клиент заказал этот конкретный продукт. Поэтому нам нужно создать отношения «многие к одному», чтобы связать этот заказ с этим клиентом.
Итак, давайте перейдем к Order.hbm.xml файл и добавьте многие к одному, а затем назовите поле клиента и столбец с идентификатором клиента.
<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping xmlns = "urn:nhibernate-mapping-2.2" assembly = "NHibernateDemo"
namespace = "NHibernateDemo">
<class name = "Order" table = "`Order`">
<id name = "Id">
<generator class = "guid.comb"/>
</id>
<property name = "Ordered"/>
<property name = "Shipped"/>
<component name = "ShipTo">
<property name = "Street"/>
<property name = "City"/>
<property name = "Province"/>
<property name = "Country"/>
</component>
<many-to-one name = "Customer" column = "CustomerId"/>
</class>
</hibernate-mapping>
Давайте снова запустим это приложение, и теперь вы увидите следующий результат.
New Customer:
John Doe (00000000-0000-0000-0000-000000000000)
Points: 100
HasGoldStatus: True
MemberSince: 1/1/2012 12:00:00 AM (Unspecified)
CreditRating: Good
AverageRating: 42.42424242
Orders:
Order Id: 00000000-0000-0000-0000-000000000000
Order Id: 00000000-0000-0000-0000-000000000000
Reloaded:
John Doe (660a6f29-650e-4380-99e0-a5b800febbde)
Points: 100
HasGoldStatus: True
MemberSince: 1/1/2012 12:00:00 AM (Utc)
CreditRating: Good
AverageRating: 42.4242
Orders:
Order Id: 57314deb-e023-4e55-ac1e-a5b800febbe3
Order Id: fc065683-d5f5-484b-ae42-a5b800febbe3
The orders were ordered by:
John Doe (660a6f29-650e-4380-99e0-a5b800febbde)
Points: 100
HasGoldStatus: True
MemberSince: 1/1/2012 12:00:00 AM (Utc)
CreditRating: Good
AverageRating: 42.4242
Orders:
Order Id: 57314deb-e023-4e55-ac1e-a5b800febbe3
Order Id: fc065683-d5f5-484b-ae42-a5b800febbe3
John Doe (660a6f29-650e-4380-99e0-a5b800febbde)
Points: 100
HasGoldStatus: True
MemberSince: 1/1/2012 12:00:00 AM (Utc)
CreditRating: Good
AverageRating: 42.4242
Orders:
Order Id: 57314deb-e023-4e55-ac1e-a5b800febbe3
Order Id: fc065683-d5f5-484b-ae42-a5b800febbe3
Press <ENTER> to exit...