Monday, January 4, 2010

Implementing and Using Repositories with NHibernate

Following the podcasts of the MVC Storefront Project at the time learning the ASP.NET MVC framework was for me really good introduction to the ideas of the Domain Driven Design (DDD).

Now, months later and still new to the DDD, but very enthusiastic, I’ll try to sketch some ideas I learned so far and in practice to implement a complete example. In particular I’ll be utilizing the Repository pattern and implement it with NHibernate as data access framework. I’m using NHibernate but I keep in mind to provide a layer of abstraction and to make it possible to switch to another data access provider (Entity Framework for example).

The Repository Pattern



Accordingly to Martin Fowler:

“A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers“.

The Repository encapsulates the actual storage and querying and keeps the client to focus on the model. We can use dependency injection and inversion of control to switch real and fake repositories for the needs of testing. We have also all the querying logic in centralized part of the application and it makes the support easier. Our application depends on simple in-memory collections, etc.


The Business Model



We have pretty simple bookstore model. We have only two kinds of objects: Books and Authors. A book can be written by one or many authors. One author can take part of writing one or more books. So, the relation between them is many-to-many.

The final database model could be similar to this:


Domain Entity Objects and Approval Services



In DDD Entity is an object fundamentally defined by thread of continuity and identity.

Value object is then an object that has properties (shapes) and does not have identity. Usually these are read-only object we use as data transfer object (DTO).

In an “ideal” world the entity objects does not have properties, but exposes operations (methods). To change the internal state of entity object we call its methods and send there value objects as parameters.

In this example our entity objects will have properties and we directly will access them to change the information contained in our entities.

When changing the properties of the entity objects we’ll need a possibility to validate the state of the entity object. We use the validation for example to show the end user an error message and prevent storing object to the database if it does not have valid state. The book object for example should have a title. We need a way the entity does validation of itself. Also, same entity object can be valid in one context, but in another context with same containing data to be invalid. We’ll inject a validator object that will implement the rules of the validation.

The validator object is kind of a Service object and is an operation. It does not belong to any object, but just checks if the properties of the entity object pass some rules. The dependency injection will be exposed via property which we name “Approval”.

The EntityBase class is from where all our entities will inherit:


/// <summary>
/// EntityBase represents objects primary defined by it's identity (Id).
/// The type of the identity, usually but not necessariily is of
/// type integer and is automatically assigned primary key.
///
/// All of the domain entities inherit this base class.
/// All of the domain entities have an identity and are able
/// to state if the data they contain is valid.
///
/// To check if the entity object contains valid data, they expose
/// property named Approval which is a concrete implementation of
/// ApprovalBase. The entity's Validate() method just wraps
/// the Approval.Validate(). The validation of entity returns list
/// of error messages and there is also a shortcut methnod IsValid()
/// which just gives back true if the validation was successful
/// or false if the validation has failed.
///
/// The entity objects implement IComparable interface and equality tests.
/// The have Equals() method to check if two variables point to same instance
/// of the same entity class and also they have equality operators that check
/// if the IDs are same.
/// </summary>
public abstract class EntityBase<TIdentity> where TIdentity : IComparable
{
// Gets or sets the identity.
public virtual TIdentity Id { get; set; }

protected EntityBase(TIdentity Id) { this.Id = Id; }

protected EntityBase() { }

// Approval object that does the validation.
public abstract ApprovalBase Approval { get; set; }

// Validation
public virtual IList<String> Validate()
{
if (Approval != null)
return Approval.Validate(this);
else
return new List<String>();
}

// Validation shortcut
public virtual bool IsValid()
{
return (Validate().Count == 0);
}


// Equality tests /////////////

// Determines whether the specified entity parameter
/// is equal to the current object (is the same reference)
public override bool Equals(object entity)
{
if (entity == null || !(entity is EntityBase<TIdentity>))
return false;

return (this == (EntityBase<TIdentity>)entity);
}

// Check if two entities have same ID
public static bool operator ==(EntityBase<TIdentity> entity1, EntityBase<TIdentity> entity2)
{
object obj1 = entity1 as object;
object obj2 = entity2 as object;

if ((obj1 == null) && (obj2 == null))
return true;

if ((obj1 == null) || (obj2 == null))
return false;

if (entity1.GetType() != entity2.GetType())
return false;

if (entity1.Id.CompareTo(entity2.Id) != 0)
return false;

return true;
}

// Check if two entities have different ID
public static bool operator !=(EntityBase<TIdentity> entity1, EntityBase<TIdentity> entity2)
{
return (!(entity1 == entity2));
}

// GetHashCode() if the Id.
public override int GetHashCode() { return this.Id.GetHashCode(); }
}




Then we inject an approval object to our entities and the approval objects inherit from ApprovalBase:


/// <summary>
/// The entity objects have property based
/// dependency injection with approval object.
///
/// If the entity has approval object,
/// the entity can be validated to prevent saving
/// entities with incorrect values, or to inform the
/// end user that the data he has entered is invalid.
///
/// The validation can be changed according to the context
/// the entity is in. In one situation the entity may be valid, but
/// in other situation the same entity with same properties
/// may not be valid.
///
/// The ApprovalBase is an abstract class from wich the concrete approval
/// classes will inherit. The ApprovalBase has method Validate wich returns
/// List of ApprovalErrorMessage error messages.
/// </summary>
public abstract class ApprovalBase
{
public abstract IList<String> Validate<TEntity>(TEntity entity);
}






Finder Objects



I found the ideas from Russell East’s blog "Implementing the Repository and Finder patterns" to be very promising and I think it is a very advanced way of implementing repositories.

For the original idea, please refer to:

http://russelleast.wordpress.com/2008/09/20/implementing-the-repository-and-finder-patterns/

I'm just following the same pattern here. The querying repositories depend on finder and persistence repositories.

One goals of Russell's idea is to keep independent from concrete Object Relational Mapper (ORM). Other goal is to have typed repositories - one per entity - and thereof make it possible to inject finder objects to the repositories. Also, he makes possible to inject the concrete persistance layer via inversion of control and provides fully testable implementation of repository.


All the entity finders extend a base IEntityFinder interface. The generic type is constrained by the EntityBase type.
The IEntityFinder interface is not to be used directly, but to be extended by typed Repository interfaces.



public interface IFinder<TEntity, TIdentity>
where TEntity : EntityBase<TIdentity>
where TIdentity : IComparable
{
// Has a DataSource property of IQueryable on which
// we use LINQ for quering.
IQueryable<TEntity> DataSource { get; set; }

/// Get count of objects/records.
int Count();

/// Find by id. We have entityConcrete = repositoryConcrete.Find.ById(123) like usage.
TEntity ById(TIdentity id);
}


All the finder object inherit from FinderBase class. The Finder objects implement search logic/functions using LINQ. The classes inherited from RepositoryBase (see below) have then object Find, which method names by convention look like "ById", "ByName", etc. The concrete finders have to implement at least ById method.


public abstract class FinderBase<TEntity, TIdentity>
: IFinder<TEntity, TIdentity>
where TEntity : EntityBase<TIdentity>
where TIdentity : IComparable
{
public IQueryable<TEntity> DataSource { get; set; }

/// Gets count of records.
public int Count()
{
return DataSource.Count();
}

/// Find one entity by ID.
public abstract TEntity ById(TIdentity id);
}



Base Repository Class



Let’s first define how the concrete persistence layer should look like.

The concrete ORM specific implementation is abstracted with interface IPersistor.

The repository takes care for querying; the persistence repository takes care for persistence and depends on ORM specific implementation.

The basic operations in the contract declared by this interface are: get entity by id; save entity to persist; delete entity from persist repository; get the query object on top of which to implement concrete LINQ queries.


public interface IPersistor<TEntity, TIdentity>
{
TEntity Get(TIdentity id);
void Save(TEntity entity);
void Delete(TEntity entity);
IQueryable<TEntity> Query();
}


Now we are about to finish the base repository class.

All the repositories extend a base IRepository interface. The generic type is restricted to be EntityBase type.
The basic operations in the contract are much the same like the defined in IPersistor, but we don't expose IQueryable to public, as we implement all the querying logic into the concrete repositories.


public interface IRepository<TEntity, TIdentity>
where TEntity : EntityBase<TIdentity>
where TIdentity : IComparable
{
TEntity Get(TIdentity id);
IList<String> Save(TEntity entity);
void Delete(TEntity entity);
// hidden // IQueryable<TEntity> Query();
}



The RepositoryBase is an abstract class from which all concrete repositories inherit from.
The repositories are injected with concrete entity types using the typed interface, so we have one repository per entity.
The repository class must be supplied an instance of IPersistor and an instance of IFinder.

public abstract class RepositoryBase<TEntity, TIdentity>
: IRepository<TEntity, TIdentity>
where TEntity : EntityBase<TIdentity>
where TIdentity : IComparable
{
private readonly IPersistor<TEntity, TIdentity> persistor;
public IPersistor<TEntity, TIdentity> Persistor { get { return persistor; } }

private readonly IFinder<TEntity, TIdentity> finder;
public IFinder<TEntity, TIdentity> Finder { get { return finder; } }

private RepositoryBase() { }

public RepositoryBase(
IPersistor<TEntity, TIdentity> persistor,
IFinder<TEntity, TIdentity> finder)
{
this.persistor = persistor;
this.finder = finder;
this.finder.DataSource = Query();
}

// Get entity by ID
public virtual TEntity Get(TIdentity id)
{
return persistor.Get(id);
}

/// <summary>
/// Validate and Save the entity. If the validation failed, will not save the entity,
/// but returns back list of error messages.
/// </summary>
public virtual IList<String> Save(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException("entity");

IList<String> errors = entity.Validate();

if (errors.Count == 0)
persistor.Save(entity);

return errors;
}

// Delete entity from persistance repository
public virtual void Delete(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException("entity");

persistor.Delete(entity);
}

/// Gets IQueryable which we use from the concrete
/// implementation of repository to implement our
/// query methods (FindBy).
protected IQueryable<TEntity> Query()
{
return persistor.Query();
}
}

The Book Store Model –Entities and Approvals




At this point I’ll define the book store model, its entities, and approval objects.

The Book object has along with title, publisher info, ISBN also a list of one or many authors.


public class Book : EntityBase<int>
{
public Book() : base()
{
this.Authors = new List<Author>();
}
public Book(int id) : this() { Id = id; }
public override ApprovalBase Approval { get; set; }

public virtual string Title { get; set; }
public virtual string Publisher { get; set; }
public virtual string ISBN { get; set; }

public virtual IList<Author> Authors { get; private set; }
}


An Author have a name and also eventually takes part on writing several books.


public class Author : EntityBase<int>
{
public Author() : base()
{
this.Books = new List<Book>();
}
public Author(int id) : this() { Id = id; }
public override ApprovalBase Approval { get; set; }

public virtual string Name { get; set; }
public virtual IList<Book> Books { get; private set; }
}


Please note that the properties of the entities are marked as “virtual” as it is required by NHibernate.

How let’s create some approval services. Again – we can switch the approval the entity has even at run time as we have property injection implemented in our entities and they don’t depend on concrete approval services. If we set the approval of the entity to null, the validation will be successful regardless the data the entity contains.


To check if Book object contains valid data we implement BookApproval. A Book should have title and at least one author.


public class BookApproval : ApprovalBase
{
public override IList<String> Validate<TEntity>(TEntity entity)
{
List<String> errors = new List<String>();
Book book = entity as Book;

if (book == null)
throw new ArgumentException("Entity not a book");

if (String.IsNullOrEmpty(book.Title))
errors.Add("Title can not be empty");

if ((book.Authors == null) || (book.Authors.Count == 0))
errors.Add("A book should have at least one author");

return errors;
}
}


To check if Author object contains valid data we implement AuthorApproval. An Author should have name.


public class AuthorApproval : ApprovalBase
{
public override IList<String> Validate<TEntity>(TEntity entity)
{
List<String> errors = new List<String>();
Author author = entity as Author;

if (author == null)
throw new ArgumentException("Entity is not an author");

if (String.IsNullOrEmpty(author.Name))
errors.Add("Name can not be empty");

return errors;
}
}



Okay, sometimes they are some “no name” authors. If you think so, you still can create your own implementation of AuthorApproval service :-)

The Book Store Model – Repositories and Finders



First we define interfaces which the client code will use when talking to the book store repositories.

The IBookFinder states that we should have one new method, not required by IFinder, and this is ByISBN().
The IBookRepository exposes Find property of type IBookRepository.


public interface IBookFinder : IFinder<Book, int>
{
Book ByISBN(string ISBN); // adds new find methiod
}
public interface IBookRepository : IRepository<Book, int>
{
IBookFinder Find { get; }
}


Similarly the same way for the Author:


public interface IAuthorFinder : IFinder<Author, int>
{
Author ByName(string name);
}
public interface IAuthorRepository : IRepository<Author, int>
{
IAuthorFinder Find { get; }
}


Then we create concrete BookFinder and BookRepository

public class BookFinder : FinderBase<Book, int>, IBookFinder
{
public override Book ById(int id)
{
return DataSource.Where(b => b.Id == id).FirstOrDefault();
}
public Book ByISBN(string ISBN)
{
return DataSource.Where(b => b.ISBN == ISBN).FirstOrDefault();
}
}

public class BookRepository : RepositoryBase<Book, int>, IBookRepository
{
public BookRepository(
IPersistor<Book, int> persistor,
IBookFinder entityFinder)
: base(persistor, entityFinder) { }

public IBookFinder Find
{
get { return (IBookFinder)Finder; }
}
}


and also concrete AuthorFinder and AuthorRepository


public class AuthorFinder : FinderBase<Author, int>, IAuthorFinder
{
public override Author ById(int id)
{
return DataSource.Where(a => a.Id == id).FirstOrDefault();
}
public Author ByName(string name)
{
return DataSource.Where(a => a.Name == name).FirstOrDefault();
}
}

public class AuthorRepository : RepositoryBase<Author, int>, IAuthorRepository
{
public AuthorRepository(
IPersistor<Author, int> persistor,
IAuthorFinder finder)
: base(persistor, finder) { }

public IAuthorFinder Find
{
get { return (IAuthorFinder)Finder; }
}
}





The Book Store Model – External Dependencies



Until now we have a model for our book store. This model is fully testable and we can test how it works and inject some stubs for finders, approvals, etc. if it is needed. Here we go further and finish our work as we implement the rest of the book store with some dependencies to external projects. We will use NHibernate as ORM and furthermore FluentNHibernate. We will use StructureMap for inversion of control. We will need few ADO.NET drivers for different databases, as we will be able to switch between them on demand. At the end we will create in memory SQLite database and test our book store..

Download and unpack these files and add references from the solution explorer:

Download: fluentnhibernate-binary-1.0.0.595.zip from http://fluentnhibernate.org/
Referenced assemblies: Antlr3.Runtime.dll, Castle.Core.dll, Castle.DynamicProxy2.dll, FluentNHibernate.dll, Iesi.Collections.dll, NHibernate.dll, NHibernate.ByteCode.Castle.dll

Download: NHibernate.Linq-1.0.0.GA-bin.zip from http://sourceforge.net/projects/nhibernate/files/
Referenced assemblies: NHibernate.Linq.dll

Download: SQLite-1.0.65.0-binaries.zip from http://sqlite.phxsoftware.com
Referenced assemblies: System.Data.SQLite.DLL, System.Data.SQLite.Linq.dll

Download: Npgsql2.0.7-bin-ms.net3.5sp1.zip from http://npgsql.projects.postgresql.org/
Referenced assemblies: Npgsql.dll, Mono.Security.dll

Download: StructureMap_2.5.3.zip from http://structuremap.sourceforge.net/Default.htm
Referenced assemblies: StructureMap.dll

Download: xunit-1.5.zip from http://www.codeplex.com/xunit
Referenced assemblies: xunit.dll, xunit.extensions.dll



Place the following “using” directives at the beginning of the code file:


using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Mapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using StructureMap;







The Book Store Model – FluentNHibernate mappings



From the “Getting started” wiki page of the of the Fluent NHibernate project:
“Fluent NHibernate offers an alternative to NHibernate's standard XML mapping files. Rather than writing XML documents (.hbm.xml files), Fluent NHibernate lets you write mappings in strongly typed C# code. This allows for easy refactoring, improved readability and more concise code“.

We create dedicated classes to map the book store entity objects to tables into the database. For the entity’s identity NHibernate will automatically create automatically generated (auto incremented) values. The mapper class derive from FluentNHibernate.Mapping .ClassMap


public sealed class BooksMap : ClassMap<Book>
{
public BooksMap()
{
Id(x => x.Id);
Map(x => x.Title).Length(256).Default("").Not.Nullable();
Map(x => x.Publisher).Length(256).Default("").Nullable();
Map(x => x.ISBN).Length(16).Default("").Not.Nullable();
HasManyToMany(x => x.Authors).LazyLoad();
}
}
public sealed class AuthorsMap : ClassMap<Author>
{
public AuthorsMap()
{
Id(x => x.Id);
Map(x => x.Name).Length(128).Default("").Not.Nullable();
HasManyToMany(x => x.Books).LazyLoad();
}
}



The Book Store Model –NHibernate Persistors



As we defined concrete repositories and finders for the book and author entities, still we don’t have concrete persistors. First we define base persistor which depends on NHibernate (uses NHibernate.ISession, etc). Then we define the persistors for our entities.



// Concrete NHibernate based persistance repository.
public abstract class PersistorBase<TEntity, TIdentity>
: IPersistor<TEntity, TIdentity>
{
protected ISession Session { get; set; }

public PersistorBase(ISession session)
{
Session = session;
}

public TEntity Get(TIdentity id)
{
return (TEntity)Session.Get(typeof(TEntity), id);
}

public void Save(TEntity entity)
{
Session.SaveOrUpdate(entity);
}

public void Delete(TEntity entity)
{
Session.Delete(entity);
}

public IQueryable<TEntity> Query()
{
var qry = from t in Session.Linq<TEntity>()
select t;

return qry.AsQueryable();
}
}

// Persistance repository for books.
public class BookPersistor : PersistorBase<Book, int>
{
public BookPersistor(ISession session) : base(session) { }
}

// Persistance repository for authors.
public class AuthorPersistor : PersistorBase<Author, int>
{
public AuthorPersistor(ISession session) : base(session) { }
}





The Book Store Model –NHibernate initialization



How comes stuff completely related to NHibernate and not directly to the book store model.

BaseDatabase is wrapper class for NHibernate initialization - a class from which concrete per database driver/server implementations will be created.

From this one we create implementation for Postgres, for MySQL, for SQLite, etc.
Into the concrete implementations we switch drivers, connection string, etc.

In Init() we open connection to the database.
The BuildSchema() method creates the database tables, relations, etc.


public abstract class BaseDatabase : IDisposable
{
protected static Configuration configuration;
protected static ISessionFactory sessionFactory;

public ISession Session { get; protected set; }

/// Does initialization, run from the constructors of the inherited classes.
protected void Init()
{
sessionFactory = CreateSessionFactory();
Session = sessionFactory.OpenSession();
BuildSchema(Session);
}

protected abstract ISessionFactory CreateSessionFactory();

private static void BuildSchema(ISession session)
{
SchemaExport schemaExport = new SchemaExport(configuration);
schemaExport.Execute(true, true, false, session.Connection, null);
}

public void Dispose()
{
Session.Dispose();
}

}


And now I implement some concrete initializations per database driver/server:



/// Database configured as in file SQLite database.
public class SQLiteInFileDatabase : BaseDatabase
{
public string FileName { get; private set; }

/// Create SQLite database with specified file name.
public SQLiteInFileDatabase(string fileName)
{
// file name to store the database, "database.db" for example.
FileName = fileName;

// does not run the base constructor, but calls Init() by itself.
Init();
}

protected override ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile(FileName).ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<BooksMap>())
.ExposeConfiguration(Cfg => configuration = Cfg)
.BuildSessionFactory();
}
}

/// Database configured as in memory SQLite database.
public class SQLiteInMemoryDatabase : BaseDatabase
{
/// Create in memory SQLite database.
public SQLiteInMemoryDatabase()
{
Init();
}

protected override ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory().ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<BooksMap>())
.ExposeConfiguration(Cfg => configuration = Cfg)
.BuildSessionFactory();
}
}


/// Database configured as in memory SQLite database.
public class PostgresDatabase : BaseDatabase
{
public string ConnectionString { get; private set; }

/// Create in memory SQLite database.
public PostgresDatabase(string connectionString)
{
ConnectionString = connectionString;
Init();
}

protected override ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(PostgreSQLConfiguration.PostgreSQL81
.ConnectionString(ConnectionString).ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<BooksMap>())
.ExposeConfiguration(Cfg => configuration = Cfg)
.BuildSessionFactory();
}
}






The Book Store Model – the BookStore database class



It is time for the book store class. The BookStore class contains book and author repository and does the necessary initialization of those. Also contains concrete instance of BaseDatabase. We inject concrete instance of BaseDatabase from the parameterized constructor. We also have parameter less constructor which leans upon StructureMap to init this singleton class with concrete instance and implementation of BaseDatabase.



/// Is a singleton, use GetInstance()
public class BookStore
{
private static BookStore bookStore;

public static BookStore GetInstance()
{
if (bookStore == null)
bookStore = new BookStore();
return bookStore;
}

private BaseDatabase db;
public IBookRepository BookRepository { get; private set; }
public IAuthorRepository AuthorRepository { get; private set; }


/// Inject concrete instance of BaseDatabase.
public BookStore()
: this(ObjectFactory.GetInstance<BaseDatabase>())
{ }

public BookStore(BaseDatabase db)
{
this.db = db;
Init();
}


private void Init()
{
IBookFinder bookFinder = new BookFinder();
BookPersistor bookPersistor = new BookPersistor(db.Session);
BookRepository = new BookRepository(bookPersistor, bookFinder);


IAuthorFinder authorFinder = new AuthorFinder();
AuthorPersistor authorPersistor = new AuthorPersistor(db.Session);
AuthorRepository = new AuthorRepository(authorPersistor, authorFinder);
}

// can be used to implement the Unit of Work Pattern.
public void Flush()
{
db.Session.Flush();
}
}






The Book Store Model – Usage


For the usage and to run the final example I’ll put the rest into the Program.cs file.

First I create two concrete implementation of BaseDatabase: SQLite where we have specified file name for our SQLite database and Postgres where we have specified the concrete connection string.

Then we have method ConfigureDependencies() to initialize StructureMap. We say which concrete class to be created whenever we create instance of BaseDatabase using the default parameter less constructor. Please note that we have such situation in class BookStore in GetInstance() where it creates an instance of itself. BookStore depends only on BaseDatabase class, but not on the concrete implementations of BaseDatabase. Then StructureMap will decide for the concrete implementation of BaseDatabase.




public class Program
{
public class SQLiteInFileConcreteDatabase : SQLiteInFileDatabase
{
public SQLiteInFileConcreteDatabase() : base("test.db") { }
}

public class PostgresConcreteDatabase : PostgresDatabase
{
public PostgresConcreteDatabase()
: base("Server=127.0.0.1;Port=5432;Database=bookstore;User Id=postgres;Password=;")
{ }
}

/// <summary>
/// Configure StructureMap dependencies.
/// </summary>
private static void ConfigureDependencies()
{
ObjectFactory.Initialize(x =>
{
x.ForRequestedType<BaseDatabase>()
.TheDefaultIsConcreteType<SQLiteInMemoryDatabase>();
// .TheDefaultIsConcreteType<SQLiteInFileDatabaseConcrete>();
// .TheDefaultIsConcreteType<PostgresConcreteDatabase>();
// whatever concrete database and settings
});
}



static void Main(string[] args)
{
ConfigureDependencies();

BookStore bookStore = BookStore.GetInstance();

Author JohnDoe = new Author() { Name = "John Doe" };
bookStore.AuthorRepository.Save(JohnDoe);

Author JaneDoe = new Author() { Name = "Jane Doe" };
bookStore.AuthorRepository.Save(JaneDoe);

Book book;

// test approval:
book = new Book() { };
book.Approval = new BookApproval();
Console.WriteLine("Approval of book: {0}", String.Join(System.Environment.NewLine, book.Validate().ToArray()));


// test saving to database
book = new Book() { Title = "Meet John Doe", ISBN = "1234567890123" };
book.Authors.Add(JohnDoe);
book.Authors.Add(JaneDoe);
bookStore.BookRepository.Save(book);
bookStore.Flush(); // moan on error

Book selected = bookStore.BookRepository.Find.ByISBN("1234567890123");
Console.WriteLine(String.Format("Author one: {0}", selected.Authors[0].Name));
Console.WriteLine(String.Format("Author two: {0}", selected.Authors[1].Name));
Console.ReadLine();
}
}



When I ran the program, I’ve got the following output:



I also gave a try of the BookApproval. The validation has failed with two error messages. Saving that book definition – with bookStore.BookRepository.Save(book); - wouldn’t be possible as the validation did not pass.



At the end I place some links related to the theme.

An excellent blog on implementing repository and finder pattern:
http://russelleast.wordpress.com/2008/09/20/implementing-the-repository-and-finder-patterns/

Part 2 from the Blog attending the MVC Storefront Project where the Repository Pattern is used:
http://blog.wekeroad.com/mvc-storefront/asp-net-mvc-mvc-storefront-part-2/

Fluent mapping for NHibernate
http://fluentnhibernate.org/

To download NHibernate.Linq
http://sourceforge.net/projects/nhibernate/files/

ADO.NET SQLite Data Provider:
http://sqlite.phxsoftware.com

ADO.NET Postgresql Data Provider:
http://npgsql.projects.postgresql.org/

StructureMap Inversion of Control Framework:
http://structuremap.sourceforge.net/Default.htm

Atanas Hristov

kick it on DotNetKicks.com
Shout it

9 comments:

  1. Any chance of getting a download of all this in a visual studio project? Very nice design.

    ReplyDelete
  2. Hi Atanas,

    I haven't read the article fully but have one comment: the implementation of equality of EntityBase is not correct. Comparison of two different entity types with same id would result with TRUE value.

    Example:
    var book = new Book(1); // id = 1
    var author = new Author(1); // id = 1

    if (book == author) throw new Exception();

    You should fix that by taking type of the entity into the count when doing comparison.

    Anyhow good work and keep writing!

    br,
    skippovic

    ReplyDelete
  3. Thank you Slafco for prompting this. I added checking with GetType() just before comparing the IDs.

    ReplyDelete
  4. Thanks Atanas for the insightful article.
    I'm new with NHibernate and was wondering how you could encolse in transactions the repository actions?

    ReplyDelete
  5. Why open session in BaseDatabase and in bookstore (that is singleton) inject this...
    the session is open always?

    ReplyDelete
  6. Looks great..is there a download for this? Would love to see it in action.

    ReplyDelete
  7. Created code repository here:
    https://bitbucket.org/atanashristov/fluentnhibernaterepository

    ReplyDelete
  8. Hey man, nice post.
    Can you send the source of this project for me? it'll help me a lot, take my email: rafa.aspx@gmail.com

    ReplyDelete