C# NHibernate Основы или ваше первое NHibernate приложение

Добро пожаловать в NHibernate


Если вы читаете это, то я надеюсь вы уже скачали NHibernate скачать и готовы начать работать с данной технологией.

Это статья поможет вам разобраться со следующими этапами изучения NHibernate:


  • Установка NHibernate
  • Создание простого бизнес класса
  • Создание NHibernate mapping для загрузки и сохранения бизнес объектов
  • Конфигурация NHibernate для доступа к вашей локальной Базе Данных
  • Автоматическая генерация Базы Данных
  • Написание простого CRUD кода используя Repository Pattern
  • Использование Юнит-тестов для проверки корректности работы нашего созданного кода
Вот наша цель:

Но давайте рассмотрим всё по порядку.

Для начала давайте разберемся со скачанным нами ZIP архивом.

Установка NHibernate

Если вы скачали архив с NHibernate, то всё что вам нужно это распаковать его куда-нибудь. Я всегда создаю папку SharedLibs c:\Code\SharedLibs\NHibernate и распаковываю архив в эту папку. Но можете делать конечно как вам удобней. Это ваша SharedLibs папка из которой вы впоследствии добавите ссылки на сборки NHibernate и NUnit. Добавьте ссылки на NHibernate в оба ваших демо проекта.

На этом всё. NHibernate установлен. Далее мы поговорим как использовать NHibernate в Visual Studio. Но для начала посмотрим как создать проект. Ниже представленный код актуален для Visual Studio 2008 и .Net Framework 3.5.

Создайте ваш проект

Перед тем как мы начнем создавать наше приложение и бизнес объекты, мы должны создать пустой проект. Запустите Visual Studio и создайте новый Class Library проект. А теперь давайте посмотрим на что то более интересное - это Бизнес Объекты.

Создание простого бизнес класса


Давайте начнем с определения очень простого домена. На данный момент он включает только один класс Product. У Product есть три свойства Name, Category и Discontinued.
Добавьте папку Domain в проект FirstSample из вашего решения. Добавьте новый класс Product.cs в эту папку. Код очень простой и использует автоматические свойства (новшество C# 3.0 компилятора)

namespace FirstSolution.Domain
{
    public class Product
    {
        public string Name { get; set; }
        public string Category { get; set; }
        public bool Discontinued { get; set; }
    }
}

Сейчас нам надо создать возможность, чтобы наши созданные классы сохранялись в БД. Для решения этой задачи мы и выбрали NHibernate. Строка в созданном классе будет соответствовать строке в таблице в БД. Для этого мы определим mapping между созданным нами классом и соответствуйщей таблицей в базе данных. Этот mapping может быть сделан посредством mapping файла (xml-документ) или можно сделать с тоже самое с помощью атрибутов. Я начну с mapping файла.

Создание NHibernate mapping для загрузки и сохранения бизнес объектов

Создайте папку Mappings в проекте FirstSample. Добавьте новый xml-документ в эту папку и назовите его Product.hbm.xml. Не забудьте "hbm" часть имени файла. Это соглашение имен используется в  NHibernate для автоматического определения файла как mapping файла. Задайте "Embedded Resource" как Build Action для xml файла.

Найдите файл nhibernate-mapping.xsd в папке NHibernate'а и скопируйте его в SharedLibs папку. Теперь мы можем использовать эту xml схему для более удобного создания наших mapping файлов. VS будет нам предоставлять средства intellisense и validation при редактировании xml mapping документа. В VS добавьте схему в Product.hbm.xml файл
И так давайте начнем. Каждый mapping файл содержит корневой node <hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
                   assembly="FirstSolution" 
                   namespace="FirstSolution.Domain">
 
  <!-- more mapping info here -->
  
</hibernate-mapping>

В mapping файле когда мы ссылаемся на класс-домен вы всегда должны задать корректное полное имя класса (например FirstSample.Domain.Product, FirstSample). 

Сейчас для начала нам надо задать primary key для класса product. Технически мы могли бы взять свойство Name и могли бы задать его как уникальное. Но в основном так не делается и мы сделаем по другому. Для этого мы добавим в наш класс новое свойство и назовем его Id. Будем использовать Guid тип, но также могут быть использованы вместо него и int и long.

using System;
 
namespace FirstSolution.Domain
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public bool Discontinued { get; set; }
    }
}

Завершаем создание mapping файла

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
                   assembly="FirstSolution" 
                   namespace="FirstSolution.Domain">
  
  <class name="Product">
    <id name="Id">
      <generator class="guid" />
    </id>
    <property name="Name" />
    <property name="Category" />
    <property name="Discontinued" />
  </class>
  
</hibernate-mapping>

NHibernate не поступает как мы, он делает много чего по умолчанию. Так если вы не задали имя колонки в "property", то имя будет взято из свойств.  Или NHibernate  может автоматически взять название таблицы из заданного класса. Как следствие - мой xml mapping файл не загроможден лишней информацией. Пожалуйста посмотрите документацию для более детального объяснения mapping. Вы можете найти её здесь.

Ваш solution explorer должен выглядеть так сейчас (Domain.cd содержит диаграмму классов вашего домена). Вы можете добавить папку Design и создать диаграмму классов самостоятельно, это просто хорошая практика для таких приложений, но данный вопрос не относится к рассматриваемой нами теме.

Конфигурация NHibernate для доступа к вашей локальной Базе Данных

Сейчас мы должны решить какую БД мы будем использовать и нам надо определить детали подключения в connection string. NHibernate поддерживает множество БД!

Добавьте новый xml файл в FirstSolution проект and назовите его hibernate.cfg.xml. Установите свойство "Copy to Output" в "Copy always". Так как мы в нашем примере будем использовать SQL Server Compact Edition то вам необходимо добавить следующую информацию в xml файл

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
    <property name="connection.connection_string">Data Source=FirstSample.sdf</property>
    
    <property name="show_sql">true</property>
  </session-factory>
</hibernate-configuration>

С помощью этого файла конфигурации мы говорим NHibernate'у что мы будем использовать MS SQL Server Compact Edition как нашу основную БД и имя БД будет FirstSample.sdf (что эквивалентно connection string). Мы также определяем что нам надо видеть SQL который NHibernate генерирует и посылает в БД (это рекомендовано сделать, так это нам поможет в дальнейшем в дебаге в процессе разработки). Дважды проверьте чтобы у вас не было опечаток в коде!

Добавьте пустую БД в ваш FirstSample проект и назовите её FirstSample.sdf(выберите Local Database в шаблонах)
Нажмите Добавить и проигнорируйте Мастер создания Dataset, просто нажмите Cancel.

Тестируем наш Setup

Пришло время протестировать наш setup. Прежде всего проверьте что у вас есть соответствующие файлы в папке SharedLibs
Последние 8 файлов вы можете найти в директории "Microsoft SQL Server Compact Edition" в вашей папке со всеми программами.
Обратите внимание: System.Data.SqlServerCe.dll находится в подпапке Desktop.

Все остальные файлы вы можете найти в папке NHibernate

Добавьте ссылку на проект FirstSample в ваш тестовый проект. Также добавьте ссылки на NHibernate.dll, nunit.framework.dll и Systm.Data.SqlServerCe.dll (также не забудьте добавить ссылки на файлы находящиеся в вашей папке SharedLibs!). Обратите внимание что свойство "Copy Local" необходимо установить в true для сборки System.Data.SqlServerCe.dll так как по умолчанию это свойство false! Добавьте копию файла hibernate.cfg.xml в корневую папку вашего unit test проекта. Для взаимодействия с NHibernate в проекте NUnit необходим доступ к этому файлу.

Добавьте класс GenerateSchema_Fixture в ваш тестовый проект. Ваш тестовый проект теперь должен выглядеть так
Далее нам необходимо получить 7 файлов sqce*.dll в output директории. Мы можем добиться этого используя post-build event в VS. 

Добавьте следующую команду в "Post-build event command line"

copy $(ProjectDir)..\..\SharedLibs\sqlce*.dll $(ProjectDir)$(OutDir)
Теперь добавьте следующий код в файл GenerateSchema_Fixture
using FirstSolution.Domain;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using NUnit.Framework;
 
namespace FirstSolution.Tests
{
    [TestFixture]
    public class GenerateSchema_Fixture
    {
        [Test]
        public void Can_generate_schema()
        {
            var cfg = new Configuration();
            cfg.Configure();
            cfg.AddAssembly(typeof (Product).Assembly);
            
            new SchemaExport(cfg).Execute(false, true, false, false);
        }
    }
}
Первая строка тестового метода создает новый экземпляр класса конфигурации NHibernate. Этот класс служит для настройки NHibernate. На второй строке мы говорим NHibernate настроить себя. NHibernate будет брать информацию из вне для настройки так как в тестовом методе мы никакой информации не указали. Так NHibernate будет искать файл hibernate.cfg.xml в output директории. Это и есть чего мы хотели добиться когда создавали наш файл настроек.

На третьей строчке мы говорим NHibernate что можно найти mapping информацию в сборке, которая также содержит класс Product. На данный момент NHibernate сможет найти только один файл (Product.hbm.xml)  как встроенный ресурс.

Четвертая строка кода использует NHibernate класс-помощник SchemaExport  для того чтобы автоматически сгенерировать для нас схему в БД. SchemaExport создаст таблицу table в БД и каждый раз когда вы будете обращаться, она будет удалять таблицу и все её данные и заново пересоздавать всё.

Обратите внимание: С этим тестовым методом мы не желаем знать как NHibernate выполняет эту работы (мы можем быть конечно уверены что она это делает верно), но лучше конечно всё изначально настроить правильно. Однако посмотрите в БД и вы увидите новую таблицу 'product'.

Если у вас установлен TestDriven.Net вы можете кликнуть правой кнопкой по тесту и выбрать "Run Test(s)" для запуска тестов.
Если всё в порядке то вы должны увидеть такой результат в output окне
Если у вас установлен ReSharper вы можете запустить тест нажав на желто-зеленые кружки в левой стороне окна и выбрать Run.
В окне появятся следующие результаты

В случае возникновения проблем

Если ваши тесты выдают ошибку, то ещё раз проверьте файлы в вашей папке (здесь: m:dev\projects\FirstSolution\src\FirstSolution.Tests\bin\debug)
Дважды проверьте нет ли у вас опечаток в файле конфигурации (hibernate.cfg.xml) или в mapping файле (Product.hbm.xml). Ну и наконец проверьте где вы задали "Build Action" для mapping файла (Product.hbm.xml) в "Embedded Resource". Переходите к следующим действиям если только тесты завершились удачно.

Наши первые CRUD операторы

Теперь очевидно что наша система готова к запуску. Мы успешно создали наш Домен, mapping файлы и настроили NHibernate. В итоге мы можем использовать NHibernate для автоматической генерации схемы БД из нашего Домена(и нашего mapping файла).

Согласно DDD (смотрите Domain Driven Design by Eric Evans) мы создали репозитарий для всех crud операторов (create, read, update and delete). Интерфейс репозитария это часть домена где не находится никакой реализации! Реализация характерна для инфраструктуры. Нам же надо сохранить наш домен как "persistence ignorant (PI)".

Добавьте новый интерфейс к нашему проекту FirstSolution. Назовите его IProductRepository. Давайте определим следующий интерфейс
using System;
using System.Collections.Generic;
 
namespace FirstSolution.Domain
{
    public interface IProductRepository
    {
        void Add(Product product);
        void Update(Product product);
        void Remove(Product product);
        Product GetById(Guid productId);
        Product GetByName(string name);
        ICollection<Product> GetByCategory(string category);
    }
}
Добавьте класс ProductRepository_Fixture в тестовый проект, в него добавьте следующий код
[TestFixture]
    public class ProductRepository_Fixture
    {
        private ISessionFactory _sessionFactory;
        private Configuration _configuration;
 
        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            _configuration = new Configuration();
            _configuration.Configure();
            _configuration.AddAssembly(typeof (Product).Assembly);
            _sessionFactory = _configuration.BuildSessionFactory();
        }
    }
На четвертой строке метода TestFixtureSetUp мы создали фабрику сессий (session factory). Это очень тяжелый процесс с точки зрения производительности и он должен быть вызван всего лишь раз. Это причина по которой я добавил его в этот метод, потому что метод выполняется один раз за весь процесс тестирования.

Для того чтобы сделать наши тесты независимыми от внешних факторов мы пересоздали схему нашей БД перед вызовом каждого тестового метода. Для этого мы добавим следующий код

[SetUp]
        public void SetupContext()
        {
            new SchemaExport(_configuration).Execute(false, true, false, false);
        }

Сейчас мы можем реализовать тестовый метод для добавления новый записи product в БД. Начните с добавления новой папки Repositories в ваш проект FirstSolution. Добавьте класс ProductRepository в эту папку. Наследуйте ProductRepository от интерфейса IProductRepository.
using System;
using System.Collections.Generic;
using FirstSolution.Domain;
 
namespace FirstSolution.Repositories
{
    public class ProductRepository : IProductRepository
    {
        public void Add(Product product)
        {
            throw new NotImplementedException();
        }
 
        public void Update(Product product)
        {
            throw new NotImplementedException();
        }
 
        public void Remove(Product product)
        {
            throw new NotImplementedException();
        }
 
        public Product GetById(Guid productId)
        {
            throw new NotImplementedException();
        }
 
        public Product GetByName(string name)
        {
            throw new NotImplementedException();
        }
 
        public ICollection<Product> GetByCategory(string category)
        {
            throw new NotImplementedException();
        }
    }
}
Работаем с данными
Теперь вернемся к тестовому классу ProductRepository_Fixture  и реализуем первый тестовый метод
      [Test]
        public void Can_add_new_product()
        {
            var product = new Product {Name = "Apple", Category = "Fruits"};
            IProductRepository repository = new ProductRepository();
            repository.Add(product);
        }
Первый запуск покажет ошибку, так как мы не реализовали метод Add в классе репозитария. Давайте сделаем это. Но подождите, мы должны создать небольшой вспомогательный класс который позволит нам выполнять сессии объектов по требованию.
using FirstSolution.Domain;
using NHibernate;
using NHibernate.Cfg;
 
namespace FirstSolution.Repositories
{
    public class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;
 
        private static ISessionFactory SessionFactory
        {
            get
            {
                if(_sessionFactory == null)
                {
                    var configuration = new Configuration();
                    configuration.Configure();
                    configuration.AddAssembly(typeof(Product).Assembly);
                    _sessionFactory = configuration.BuildSessionFactory();
                }
                return _sessionFactory;
            }
        }
 
        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }
    }
}
Этот класс создает фабрику сессий только в первый раз когда клиенту нужна сессия.

Сейчас мы можем определить метод Add в ProductRepository так
 public void Add(Product product)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Save(product);
                    transaction.Commit();
                }
        }
Второй запуск тестовом опять выдаст нам ошибку со следующим сообщением
Это потому что NHibernate по умолчанию настроена использовать ленивую загрузку для всех объектов. Эта рекомендация существенна и я бы не рекомендовал вам изменять данную настройку для максимальной гибкости вашего приложения.

Как мы решим данную проблему? Это просто - нам надо задать все свойства и методы обхектов домена как виртуальные для нашего класса Product.
    public class Product
    {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string Category { get; set; }
        public virtual bool Discontinued { get; set; }
    }
Сейчас запустите тест сновы. Он должен показать положительный результат и мы получим следующее на выходе
Обратите внимание что NHibernate выдал нам код sql.

Сейчас мы считаем что мы успешно добавили наш продукт в БД. Но давайте протестируем так ли это. Давайте доработаем наш тестовый метод
        [Test]
        public void Can_add_new_product()
        {
            var product = new Product {Name = "Apple", Category = "Fruits"};
            IProductRepository repository = new ProductRepository();
            repository.Add(product);
 
            // use session to try to load the product
            using(ISession session = _sessionFactory.OpenSession())
            {
                var fromDb = session.Get<Product>(product.Id);
                // Test that the product was successfully inserted
                Assert.IsNotNull(fromDb);
                Assert.AreNotSame(product, fromDb);
                Assert.AreEqual(product.Name, fromDb.Name);
                Assert.AreEqual(product.Category, fromDb.Category);
            }
        }
Запустите тест заново. Надеюсь всё прошло успешно...

Сейчас мы готовы реализовать остальные методы репозитория. Для тестирования было бы неплохо иметь репозиторий (который является таблицей БД) который уже содержит несколько продуктов. Ничего нет проще. Просто добавьте метол CreateInitialData в тестовый класс как показано ниже
private readonly Product[] _products = new[]
                 {
                     new Product {Name = "Melon", Category = "Fruits"},
                     new Product {Name = "Pear", Category = "Fruits"},
                     new Product {Name = "Milk", Category = "Beverages"},
                     new Product {Name = "Coca Cola", Category = "Beverages"},
                     new Product {Name = "Pepsi Cola", Category = "Beverages"},
                 };
 
        private void CreateInitialData()
        {
            
            using(ISession session = _sessionFactory.OpenSession())
                using(ITransaction transaction = session.BeginTransaction())
                {
                    foreach (var product in _products)
                        session.Save(product);
                    transaction.Commit();
                }
        }
Вызовите этот метод из метода SetupContext (после вызова метода создания схемы) и всё будет готово. Теперь при каждом создании схемы БД в базу добавляются несколько записей.

Давайте протестируем метод Update репозитария с помощью следующего кода
        [Test]
        public void Can_update_existing_product()
        {
            var product = _products[0];
            product.Name = "Yellow Pear";
            IProductRepository repository = new ProductRepository();
            repository.Update(product);
 
            // use session to try to load the product
            using (ISession session = _sessionFactory.OpenSession())
            {
                var fromDb = session.Get<Product>(product.Id);
                Assert.AreEqual(product.Name, fromDb.Name);
            }
        }
При первом запуске этого кода будет выдавать ошибка, так как метод Update не реализован в репозитории. Обратите внимание: Это принцип TDD при первом запуске теста он всегда должен выдавать ошибку! Аналогично методу Add мы реализуем метод Update. Единственное различие состоит в том что мы вызываем у NHibernate объекта session метод update вместо save.
        public void Update(Product product)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            using (ITransaction transaction = session.BeginTransaction())
            {
                session.Update(product);
                transaction.Commit();
            }
        }
Запустите тесты заново и вы увидите что всё прошло удачно.
Реализация метода удаления описана ниже. При тестировании для того чтобы убедиться действительно ли запись удалена мы должны проверить значения метода get объекта session, значение должно быть равно null. Вот этот тестовый метод
       [Test]
        public void Can_remove_existing_product()
        {
            var product = _products[0];
            IProductRepository repository = new ProductRepository();
            repository.Remove(product);
 
            using (ISession session = _sessionFactory.OpenSession())
            {
                var fromDb = session.Get<Product>(product.Id);
                Assert.IsNull(fromDb);
            }
        }
и реализация метода Remove в репозитарии
        public void Remove(Product product)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Delete(product);
                    transaction.Commit();
                }
        }
Запросы к Базе Данных
Мы всё ещё должны реализовать три метода для запросов к объектам базы данных. Давайте начнем с самого простого GetById. Для начала мы напишем тест
        [Test]
        public void Can_get_existing_product_by_id()
        {
            IProductRepository repository = new ProductRepository();
            var fromDb = repository.GetById(_products[1].Id);
            Assert.IsNotNull(fromDb);
            Assert.AreNotSame(_products[1], fromDb);
            Assert.AreEqual(_products[1].Name, fromDb.Name);
        }
и теперь добавим код в репозитарии чтобы закончить с этим тестом
        public Product GetById(Guid productId)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                return session.Get<Product>(productId);
        }
Сейчас это легко. Для следующих двух методов мы используем новый метод объекта session. Давайте начнем с метода GetByName. Как всегда мы сначала пишем тест
       [Test]
        public void Can_get_existing_product_by_name()
        {
            IProductRepository repository = new ProductRepository();
            var fromDb = repository.GetByName(_products[1].Name);
 
            Assert.IsNotNull(fromDb);
            Assert.AreNotSame(_products[1], fromDb);
            Assert.AreEqual(_products[1].Id, fromDb.Id);
        }
Реализовать данный метод можно двумя путями. Первое это использовать HQL (Hibernate Query Language) и второе это HCQ (Hibernate Criteria Query). Давайте начнем с HQL. HQL это объектно-ориентированный язык запросов похожий (но не идентичный) на SQL.

Что необходимо добавить: реализацию GetByName используя HQL. Реализацию HCQ, она работает как показано ниже и в итоге возвращает объект product.

В следующем примере я представил общую технику реализации для NHibernate. Она называется fluent interfaces. Как результат код компактен и прост для понимания. Вы можете увидеть что HQL запрос - это строка которая может содержать параметры. Префиксы этих параметров ':'. NHibernate определяет множество вспомогательных методов (таких как SetString, используется в примере) для связи значений разных типов с параметрами. В итоге используя UniqueResult я сообщаю NHibernate что мне необходимо вернуть только одно значение из БД. Если больше одной записи будет возвращено посредством запроса HQL то оно выдаст исключение. Для получения большой информации об HQL пожалуйста прочитайте онлайн документацию.

Вторая версия использует запрос с критериями для поиска запрашиваемой записи product. Вам необходимо добавить ссылку на NHibernate.Criterion в вашу страницу репозитария.
        public Product GetByName(string name)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                Product product = session
                    .CreateCriteria(typeof(Product))
                    .Add(Restrictions.Eq("Name", name))
                    .UniqueResult<Product>();
                return product;
            }
        }
Многие пользователи NHibernate считают что это решение более объектно-ориентировано. С другой стороны составные запросы с синтаксисом содержащем критерии могут быть очень сложны для понимания.

Последний метод реализует GetByCategory. Этот метод возвращает список записей products. Тест может быть реализован так
        [Test]
        public void Can_get_existing_products_by_category()
        {
            IProductRepository repository = new ProductRepository();
            var fromDb = repository.GetByCategory("Fruits");
 
            Assert.AreEqual(2, fromDb.Count);
            Assert.IsTrue(IsInCollection(_products[0], fromDb));
            Assert.IsTrue(IsInCollection(_products[1], fromDb));
        }
 
        private bool IsInCollection(Product product, ICollection<Product> fromDb)
        {
            foreach (var item in fromDb)
                if (product.Id == item.Id)
                    return true;
            return false;
        }
и сам метод должен содержать следующий код
        public ICollection<Product> GetByCategory(string category)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                var products = session
                    .CreateCriteria(typeof(Product))
                    .Add(Restrictions.Eq("Category", category))
                    .List<Product>();
                return products;
            }
        }

Подведем итоги

В этой статье я показал как реализовать простой базовый домен, mapping файл к БД и как настроить NHibernate с возможностью сохранения объектов домена в БД. Я показал вам как создавать и тестировать CRUD методы для объектов домена. Я взял как простой пример БД MS SQL Compact Edition, но другие поддерживаемые БД также могут использоваться (для этого вам всего лишь надо изменить hibernate.cfg.xml файл соответственно). Мы не использовали никаких инструментов кроме БД и самой NHibernate (ну и .NET конечно, его не берем в расчет). 


источник

Ещё посоветую пользоваться генераторами NHibernate. Ни один из них до конца верно не работает, но если генерировать и там и там а потом доделывать руками, то всё получится.
http://sourceforge.net/projects/ngennhibernatec/
http://nmg.codeplex.com/

Также посоветую посмотреть как соотносятся типы в маппинге с типа SQL Server
http://devio.wordpress.com/2012/08/08/nhibernate-3-data-type-mapping-for-sql-server/

с# WCF отправляем сообщения

Сначала конечно же генерируем Service client . Для этого заходим в ServiceReference и там жмем "Добавить ссылку на службу"

Далее мы можем отправить сообщение оборачивая в Envelope


ServiceReference1.ServiceSAClient service = new ServiceReference1.ServiceSAClient();
using (FileStream stream = new FileStream(textBox1.Text, FileMode.Open))
{
         mlReader reader = XmlReader.Create(stream);
         MessageVersion ver = MessageVersion.CreateVersion(EnvelopeVersion.Soap11, AddressingVersion.None);
         System.ServiceModel.Channels.Message m = service.async_getMessage(System.ServiceModel.Channels.Message.CreateMessage(ver, "http://tempuri.org/IServiceWCF/Message", reader));
}


Или не оборачивая в Envelope
XmlDocument d = new XmlDocument();
            d.PreserveWhitespace = false;
            d.Load(@"send.xml");
            MemoryStream ms = new MemoryStream();
            XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(ms);
            d.WriteTo(writer);
            writer.Flush();
 
            ms.Position = 0;

MessageVersion ver = MessageVersion.CreateVersion(EnvelopeVersion.Soap11, AddressingVersion.None);
System.ServiceModel.Channels.Message msend = System.ServiceModel.Channels.Message.CreateMessage(XmlDictionaryReader.CreateTextReader(ms, XmlDictionaryReaderQuotas.Max), int.MaxValue, ver);
msend.Headers.Action = "http://tempuri.org/IServiceWCF/Message";
System.ServiceModel.Channels.Message m =service.async_getMessage(msend);


Также я как то писал как ответное сообщения от сервиса сохранить в файл. Есть другое решение, более короткое.


System.ServiceModel.Channels.Message m =service.async_getMessage(msend);
                    MessageBuffer buf = m.CreateBufferedCopy(int.MaxValue);
 
 
                    using (var msstream = new MemoryStream())
                    {
                        using (FileStream file = new FileStream("WCF Responce.xml", FileMode.Create, System.IO.FileAccess.Write))
                        {
                            buf.WriteMessage(file);
                            file.Flush();
                        }
                    }

c# BLToolKit Основы или пошагово

Решил сделать инструкцию с картинками как работать с BLToolKit.
1 Скачать BLToolKit отсюда http://bltoolkit.net/Download.ashx . Качайте архив bltoolkit_dev.zip
2 Далее создаем проект в Visual Studio. Я создавал WinForms проект
3 Вот такие ссылки добавляем

4 Видим там есть папка Templates. Её можно взять из скачанного архива в bltoolkit.4.0.dev\Source\Templates

5 Далее добавляем файл *.tt . Это можно сделать если ткнуть по обозревателю решений. Там Добавить->Создать Элемент. В разделе "Код" есть соответствующий шаблон.

6 Далее заходим в Model1.tt . Там правим следующим образом (В моем случае модел генерируется для MSSQL):
<#@ template language="C#v3.5"         #>
<#@ output extension=".generated.cs"   #>
<#@ include file="TemplatesBLToolkit.ttinclude" #>
<#@ include file="TemplatesMSSQL.ttinclude"     #>
<#
    ConnectionString = @"Server=SQLEXPRESS;Database=ServerDB;Uid=myuser;Pwd=mypas";
 
    Namespace        = "Templates";
    DataContextName  = "DataModel";
 
    GenerateModel();
#>


7 Далее жмем компилировать проект и у нас генерируется необходимый класс, с которым мы уже и будем работать

Примеров работы со сгенерированным классом куча(Например вот), так что главное начало. Ссылка для скачивания примера данного проекта находится снизу.

https://www.box.com/s/bwkrhvfpvxbzjxvitdzp


с# Установить список отзыва CRL для сертификата X509 Certificate

Пропишите using, все не нужны, но данных хватит. Чистить этот список можно бы, но да ладно это не главное в данном посте

using System;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

Пишем всякое вспомогательное


        public struct CRYPTUI_WIZ_IMPORT_SRC_INFO
        {
            public Int32 dwSize;
            public Int32 dwSubjectChoice;
            [MarshalAs(UnmanagedType.LPWStr)]
            public String pwszFileName;
            public Int32 dwFlags;
            [MarshalAs(UnmanagedType.LPWStr)]
            public String pwszPassword;
        }
 
        [DllImport("CryptUI.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CryptUIWizImport(
            Int32 dwFlags,
            IntPtr hwndParent,
            IntPtr pwszWizardTitle,
            ref CRYPTUI_WIZ_IMPORT_SRC_INFO pImportSrc,
            IntPtr hDestCertStore
        );
 
        public const Int32 CRYPTUI_WIZ_IMPORT_SUBJECT_FILE = 1;
        public const Int32 CRYPT_EXPORTABLE = 0x00000001;
        public const Int32 CRYPT_USER_PROTECTED = 0x00000002;
        public const Int32 CRYPTUI_WIZ_NO_UI = 0x0001;


А теперь как это всё использовать


CRYPTUI_WIZ_IMPORT_SRC_INFO importSrc = new CRYPTUI_WIZ_IMPORT_SRC_INFO();
            importSrc.dwSize = Marshal.SizeOf(importSrc);
            importSrc.dwSubjectChoice = CRYPTUI_WIZ_IMPORT_SUBJECT_FILE;
            importSrc.pwszFileName = "Cert.crl";
            importSrc.pwszPassword = null;
            //bool z = File.Exists("C:\CenterInformMskf (1).crl");
            importSrc.dwFlags = CRYPT_EXPORTABLE | CRYPT_USER_PROTECTED;
 
            if (!CryptUIWizImport(
              CRYPTUI_WIZ_NO_UI,
                  IntPtr.Zero,
                  IntPtr.Zero,
                  ref importSrc,
                  IntPtr.Zero
            ))
            {
                MessageBox.Show("Error!");
                return;
            }
 
            MessageBox.Show("OK!");


c# How to Get URL of CRL (Revocation list) from x509 certificate

using section. 


using System;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

Main method is GetBaseCrlURL()

        private const string CERT_CRL_EXTENSION = "2.5.29.31";
        private const string CRL_CRL_EXTENSION = "2.5.29.46";
 
        public static string GetBaseCrlUrl(X509Certificate2 cert)
        {
            try
            {
                return (from X509Extension extension in cert.Extensions
                        where extension.Oid.Value.Equals(CERT_CRL_EXTENSION)
                        select GetCrlUrlFromExtension(extension)).Single();
            }
            catch
            {
                return null;
            }
        }
 
        private static string GetDeltaCrlUrl(WinCrypt32.CRL_INFO stCrlInfo)
        {
            IntPtr rgExtension = stCrlInfo.rgExtension;
            X509Extension deltaCrlExtension = null;
 
            for (int i = 0; i < stCrlInfo.cExtension; i++)
            {
                WinCrypt32.CERT_EXTENSION stCrlExt = (WinCrypt32.CERT_EXTENSION)Marshal.PtrToStructure(rgExtension, typeof(WinCrypt32.CERT_EXTENSION));
 
                if (stCrlExt.Value.pbData != IntPtr.Zero && stCrlExt.pszObjId == CRL_CRL_EXTENSION)
                {
                    byte[] rawData = new byte[stCrlExt.Value.cbData];
                    Marshal.Copy(stCrlExt.Value.pbData, rawData, 0, rawData.Length);
                    deltaCrlExtension = new X509Extension(stCrlExt.pszObjId, rawData, stCrlExt.fCritical);
                    break;
                }
 
                rgExtension = (IntPtr)((Int32)rgExtension + Marshal.SizeOf(typeof(WinCrypt32.CERT_EXTENSION)));
            }
            if (deltaCrlExtension == null)
            {
                return null;
            }
            return GetCrlUrlFromExtension(deltaCrlExtension);
        }
 
        private static string GetCrlUrlFromExtension(X509Extension extension)
        {
            try
            {
                Regex rx = new Regex("http://.*crl");
                string raw = new AsnEncodedData(extension.Oid, extension.RawData).Format(false);
                return rx.Match(raw).Value;
            }
            catch
            {
                return null;
            }
        }

It's class helper WinCrypt32



public static class WinCrypt32
    {
        #region APIs
 
        [DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CryptQueryObject(
            Int32 dwObjectType,
            IntPtr pvObject,
            Int32 dwExpectedContentTypeFlags,
            Int32 dwExpectedFormatTypeFlags,
            Int32 dwFlags,
            IntPtr pdwMsgAndCertEncodingType,
            IntPtr pdwContentType,
            IntPtr pdwFormatType,
            ref IntPtr phCertStore,
            IntPtr phMsg,
            ref IntPtr ppvContext
            );
 
        [DllImport("CRYPT32.DLL", EntryPoint = "CertFreeCRLContext", SetLastError = true)]
        public static extern Boolean CertFreeCRLContext(
            IntPtr pCrlContext
        );
 
        [DllImport("CRYPT32.DLL", EntryPoint = "CertNameToStr", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Int32 CertNameToStr(
            Int32 dwCertEncodingType,
            ref CRYPTOAPI_BLOB pName,
            Int32 dwStrType,
            StringBuilder psz,
            Int32 csz
        );
 
        [DllImport("CRYPT32.DLL", EntryPoint = "CertFindExtension", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CertFindExtension(
            [MarshalAs(UnmanagedType.LPStr)]String pszObjId,
            Int32 cExtensions,
            IntPtr rgExtensions
        );
 
        [DllImport("CRYPT32.DLL", EntryPoint = "CryptFormatObject", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CryptFormatObject(
            Int32 dwCertEncodingType,
            Int32 dwFormatType,
            Int32 dwFormatStrType,
            IntPtr pFormatStruct,
            [MarshalAs(UnmanagedType.LPStr)]String lpszStructType,
            IntPtr pbEncoded,
            Int32 cbEncoded,
            StringBuilder pbFormat,
            ref Int32 pcbFormat
        );
 
        #endregion APIs
 
        #region Structs
        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_OBJID_BLOB
        {
            public uint cbData;
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
            public byte[] pbData;
        }
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct CERT_PUBLIC_KEY_INFO
        {
            public CRYPT_ALGORITHM_IDENTIFIER Algorithm;
            public CRYPTOAPI_BLOB PublicKey;
        }
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct CERT_EXTENSION
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pszObjId;
            public bool fCritical;
            public CRYPTOAPI_BLOB Value;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct CERT_CONTEXT
        {
            public uint dwCertEncodingType;
            public IntPtr pbCertEncoded;
            public uint cbCertEncoded;
            public IntPtr pCertInfo;
            public IntPtr hCertStore;
        }
 
        public struct CERT_INFO
        {
            public int dwVersion;
            public CRYPTOAPI_BLOB SerialNumber;
            public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
            public CRYPTOAPI_BLOB Issuer;
            public FILETIME NotBefore;
            public FILETIME NotAfter;
            public CRYPTOAPI_BLOB Subject;
            public CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
            public CRYPTOAPI_BLOB IssuerUniqueId;
            public CRYPTOAPI_BLOB SubjectUniqueId;
            public int cExtension;
            public CERT_EXTENSION rgExtension;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct CRL_CONTEXT
        {
            public Int32 dwCertEncodingType;
            public IntPtr pbCrlEncoded;
            public Int32 cbCrlEncoded;
            public IntPtr pCrlInfo;
            public IntPtr hCertStore;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct CRL_INFO
        {
            public Int32 dwVersion;
            public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
            public CRYPTOAPI_BLOB Issuer;
            public FILETIME ThisUpdate;
            public FILETIME NextUpdate;
            public Int32 cCRLEntry;
            public IntPtr rgCRLEntry;
            public Int32 cExtension;
            public IntPtr rgExtension;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_ALGORITHM_IDENTIFIER
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public String pszObjId;
            public CRYPTOAPI_BLOB Parameters;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPTOAPI_BLOB
        {
            public Int32 cbData;
            public IntPtr pbData;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct FILETIME
        {
            public Int32 dwLowDateTime;
            public Int32 dwHighDateTime;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public struct CRL_ENTRY
        {
            public CRYPTOAPI_BLOB SerialNumber;
            public FILETIME RevocationDate;
            public Int32 cExtension;
            public IntPtr rgExtension;
        }
 
        #endregion Structs
 
        #region Consts
 
        public const Int32 CERT_QUERY_OBJECT_FILE = 0x00000001;
        public const Int32 CERT_QUERY_OBJECT_BLOB = 0x00000002;
        public const Int32 CERT_QUERY_CONTENT_CRL = 3;
        public const Int32 CERT_QUERY_CONTENT_FLAG_CRL = 1 << CERT_QUERY_CONTENT_CRL;
        public const Int32 CERT_QUERY_FORMAT_BINARY = 1;
        public const Int32 CERT_QUERY_FORMAT_BASE64_ENCODED = 2;
        public const Int32 CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED = 3;
        public const Int32 CERT_QUERY_FORMAT_FLAG_BINARY = 1 << CERT_QUERY_FORMAT_BINARY;
        public const Int32 CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED = 1 << CERT_QUERY_FORMAT_BASE64_ENCODED;
        public const Int32 CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED = 1 << CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED;
        public const Int32 CERT_QUERY_FORMAT_FLAG_ALL = CERT_QUERY_FORMAT_FLAG_BINARY | CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED | CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED;
 
        public const Int32 X509_ASN_ENCODING = 0x00000001;
        public const Int32 PKCS_7_ASN_ENCODING = 0x00010000;
 
        public const Int32 X509_NAME = 7;
 
        public const Int32 CERT_SIMPLE_NAME_STR = 1;
        public const Int32 CERT_OID_NAME_STR = 2;
        public const Int32 CERT_X500_NAME_STR = 3;
 
        public const String szOID_CRL_REASON_CODE = "2.5.29.21";
 
        public enum Disposition : uint
        {
            CERT_STORE_ADD_NEW = 1,
            CERT_STORE_ADD_USE_EXISTING = 2,
            CERT_STORE_ADD_REPLACE_EXISTING = 3,
            CERT_STORE_ADD_ALWAYS = 4,
            CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES = 5,
            CERT_STORE_ADD_NEWER = 6,
            CERT_STORE_ADD_NEWER_INHERIT_PROPERTIES = 7,
        }
 
        [Flags]
        public enum FindFlags : int
        {
            CRL_FIND_ISSUED_BY_AKI_FLAG = 0x1,
            CRL_FIND_ISSUED_BY_SIGNATURE_FLAG = 0x2,
            CRL_FIND_ISSUED_BY_DELTA_FLAG = 0x4,
            CRL_FIND_ISSUED_BY_BASE_FLAG = 0x8,
        }
 
        public enum FindType : int
        {
            CRL_FIND_ANY = 0,
            CRL_FIND_ISSUED_BY = 1,
            CRL_FIND_EXISTING = 2,
            CRL_FIND_ISSUED_FOR = 3
        }
 
        #endregion
    }

c# System.ServiceModel.Channels.Message to file

Можно сообщение прочитать через Message.ToString(), но так неверно оно будет скопировано и если вдруг вы при этом подписываете сообщение, то может нарушиться подпись. Данный способ был рабочим для меня.
                    
using (var msstream = new MemoryStream())
                    {
                        var xmlWriter = XmlDictionaryWriter.CreateTextWriter(msstream);
 
                        m.WriteMessage(xmlWriter);
 
                        xmlWriter.Flush();
                        msstream.Position = 0;
                        using (FileStream file = new FileStream("Responce.xml", FileMode.Create, System.IO.FileAccess.Write))
                        {
                            byte[] bytes = new byte[msstream.Length];
                            msstream.Read(bytes, 0, (int)msstream.Length);
                            file.Write(bytes, 0, bytes.Length);
                        }
                    }

с# Добавляем сертфикат в "Доверенные корневые центры сертификации"

void InstallRootCert(string certFileName)
{
    X509Certificate2 certificate = new X509Certificate2(cerFileName);
    X509Store store = new X509Store(StoreName.TrustedPublisher, StoreLocation.LocalMachine);

    store.Open(OpenFlags.ReadWrite);
    store.Add(certificate);
    store.Close();
}