Showing changes from revision #7 to #8: Added | Removed
All the source can be found in the main Fluent NHibernate solution, in the Example.FirstProject project.
You need to have Fluent NHibernate already downloaded and compiled to follow this guide, if you haven't done that yet then please refer to GettingStarted: Install.
For this example we're going to be mapping a simple domain for a retail company. The company has a couple of stores, each with products in (some products are in both stores, some are exclusive), and each with employees. In database terms, that's a Store
, Employee
, and Product
table, with a many-to-many table between Store
and Product
.
First, create a console application and reference the FluentNHibernate.dll
you built earlier, and whichever version of NHibernate.dll
you built against (if you're unsure, just use the one that's output with FluentNHibernate.dll
); also, because for this example we're going to be using a SQLite database, you'll need the System.Data.SQLite library which is distributed with Fluent NHibernate.
You can see the project structure that I used to the left. The Entities
folder is for your actual domain objects, while the Mappings
folder is where we're going to put your fluent mapping classes.
For the rest of this guide I'm going to assume you've used the same structure.
Now that we've got our project setup, lets start by creating our entities. We've got three tables we need to map (we're ignoring the many-to-many join table right now), so that's one entity per table. Create the following classes in your Entities
folder.
Entities/Employee.cs
public class Employee { public virtual int Id { get; private set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual Store Store { get; set; } }
Our Employee
entity has an Id
, the person's name (split over FirstName
and LastName
), and finally a reference to the Store
that they work in.
There's two things that may stand out to you if you're unfamiliar with NHibernate. Firstly, the Id
property has a private setter, this is because it's only NHibernate that should be setting the value of that Id
. Secondly, all the properties are marked virtual
; this is because NHibernate creates "proxies" of your entities at run time to allow for lazy loading, and for it to do that it needs to be able to override the properties.
Entities/Product.cs
public class Product { public virtual int Id { get; private set; } public virtual string Name { get; set; } public virtual double Price { get; set; } public virtual IList<Store> StoresStockedIn { get; private set; } public Product() { StoresStockedIn = new List<Store>(); } }
Product
has an Id
, it's Name
, Price
, and a collection of Store
s that stock it.
Entities/Store.cs
public class Store { public virtual int Id { get; private set; } public virtual string Name { get; set; } public virtual IList<Product> Products { get; set; } public virtual IList<Employee> Staff { get; set; } public Store() { Products = new List<Product>(); Staff = new List<Employee>(); } public virtual void AddProduct(Product product) { product.StoresStockedIn.Add(this); Products.Add(product); } public virtual void AddEmployee(Employee employee) { employee.Store = this; Staff.Add(employee); } }
Finally, we've got our Store
entity. This has an Id
and a Name
, along with a collection of Product
s that are stocked in it, as well as a collection of Employee
s (in the Staff
list) that work there. This entity has a little bit of logic in it to make our code simpler, that's the AddProduct
and AddEmployee
methods; these methods are used to add items into the collections, and setup the other side of the relationships.
If you're an NHibernate veteran then you'll recognise this; however, if it's all new to you then let me explain: in a relationship where both sides are mapped, NHibernate needs you to set both sides before it will save correctly. So as to not have this extra code everywhere, it's been reduced to these extra methods in
Store
.
Now that we've got our entities created, it's time to map them using Fluent NHibernate. We'll start with the simplest class, which is Employee
. All the following mappings should be created inside the Mappings
folder.
To map an entity, you have to create a dedicated mapping class (typically this follows the naming convention of EntityNameMap), so we'll create an EmployeeMap
class; these mapping classes have to derive from ClassMap<T>
where T
is your entity.
Mappings/EmployeeMap.cs
public class EmployeeMap : ClassMap<Employee> { }
The mappings themselves are done inside the constructor for EmployeeMap
, so we need to add a constructor and write the mappings.
public class EmployeeMap : ClassMap<Employee> { public EmployeeMap() { Id(x => x.Id); } }
To start with we've mapped the Id
column, and told Fluent NHibernate that it's actually an identifier. The x
in this example is an instance of Employee
that Fluent NHibernate uses to retrieve the property details from, so all you're really doing here is telling it which property you want your Id to be. Fluent NHibernate will see that your Id
property has a type of int
, and it'll automatically decide that it should be mapped as an auto-incrementing identity in the database - handy!
For NHibernate users, that means it automatically creates the
generator
element asidentity
.
Lets map the rest of Employee
.
public class EmployeeMap : ClassMap<Employee> { public EmployeeMap() { Id(x => x.Id); Map(x => x.FirstName); Map(x => x.LastName); References(x => x.Store); } }
There are a couple of new methods we've used there, Map
and References
; Map
creates a mapping for a simple property, while References
creates a many-to-one relationship between two entities. In this case we've mapped FirstName
and LastName
as simple properties, and created a many-to-one to Store
(many Employee
s to one Store
) through the Store
property.
NHibernate users:
Map
is equivalent to theproperty
element andReferences
tomany-to-one
.
Lets carry on by mapping the Store
.
Mappings/StoreMap.cs
public class StoreMap : ClassMap<Store> { public StoreMap() { Id(x => x.Id); Map(x => x.Name); HasMany(x => x.Staff).Inverse();.Inverse() .Cascade.All(); HasManyToMany(x => x.Products) .Cascade.All() .WithTableName("StoreProduct"); } }
Again, there's a couple of new calls here. If you remember back to Employee
, we created a many-to-one relationship with Store
, well now that we're mapping Store
we can create the other side of that relationship. So HasMany
is creating a one-to-many relationship with Employee
(one Store
to many Employee
s), which is the other side of the Employee.Store
relationship. The other new method is HasManyToMany
, which creates a many-to-many relationship with Product
.
You've also just got your first taste of the fluent interface Fluent NHibernate provides. The HasMany
method has a second call directly from it's return type (Inverse()
), and HasManyToMany
has Cascade.All()
and WithTableName
; this is called method chaining, and it's used to create a more natural language in your configuration.
Inverse
on HasMany
is an NHibernate term, and it means that the other end of the relationship is responsible for saving.Cascade.All
on HasManyToMany
tells NHibernate to cascade events down to the entities in the collection (so when you save the Store
, all the Product
s are saved too).WithTableName
sets the many-to-many join table name.The
WithTableName
call is currently only required if you're doing a bidirectional many-to-many, because Fluent NHibernate currently can't guess what the name should be; for all other associations it isn't required.For NHibernaters:
HasMany
maps to abag
by default, and has aone-to-many
element inside;HasManyToMany
is the same, but with amany-to-many
element.
Finally, lets map the Product
.
Mappings/ProductMap.cs
public class ProductMap : ClassMap<Product> { public ProductMap() { Id(x => x.Id); Map(x => x.Name); Map(x => x.Price); HasManyToMany(x => x.StoresStockedIn) .Cascade.All() .Inverse() .WithTableName("StoreProduct"); } }
That's the Product
mapped; in this case we've used only methods that we've already encountered. The HasManyToMany
is setting up the other side of the bidirectional many-to-many relationship with Store
.
In this section we'll initialise some data and output it to the console.
Program.cs
static void Main() { var sessionFactory = CreateSessionFactory(); using (var session = sessionFactory.OpenSession()) { using (var transaction = session.BeginTransaction()) { // create a couple of Stores each with some Products and Employees var barginBasin = new Store { Name = "Bargin Basin" }; var superMart = new Store { Name = "SuperMart" }; var potatoes = new Product { Name = "Potatoes", Price = 3.60 }; var fish = new Product { Name = "Fish", Price = 4.49 }; var milk = new Product { Name = "Milk", Price = 0.79 }; var bread = new Product { Name = "Bread", Price = 1.29 }; var cheese = new Product { Name = "Cheese", Price = 2.10 }; var waffles = new Product { Name = "Waffles", Price = 2.41 }; var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" }; var jack = new Employee { FirstName = "Jack", LastName = "Torrance" }; var sue = new Employee { FirstName = "Sue", LastName = "Walkters" }; var bill = new Employee { FirstName = "Bill", LastName = "Taft" }; var joan = new Employee { FirstName = "Joan", LastName = "Pope" }; // add products to the stores, there's some crossover in the products in each // store, because the store-product relationship is many-to-many AddProductsToStore(barginBasin, potatoes, fish, milk, bread, cheese); AddProductsToStore(superMart, bread, cheese, waffles); // add employees to the stores, this relationship is a one-to-many, so one // employee can only work at one store at a time AddEmployeesToStore(barginBasin, daisy, jack, sue); AddEmployeesToStore(superMart, bill, joan); // save both stores, this saves everything else via cascading session.SaveOrUpdate(barginBasin); session.SaveOrUpdate(superMart); transaction.Commit(); } // retreive all stores and display them using (session.BeginTransaction()) { var stores = session.CreateCriteria(typeof(Store)) .List<Store>(); foreach (var store in stores) { WriteStorePretty(store); } } Console.ReadKey(); } } public static void AddProductToStore(Store store, params Product[] products) { foreach (var product in products) { store.AddProduct(product); } } public static void AddEmployeeToStore(Store store, params Employee[] employees) { foreach (var employee in employees) { store.AddEmployee(employee); } }
For brevity i've left out the definition of
WritePrettyStore
which simply callsConsole.Write
for the various relationships on aStore
(but you can see it in the full code).
This is the Main
method from your Program.cs
. It's a bit lengthy, but what we're doing is creating a couple of Store
instances, then adds some Employee
s and Product
s to them, then saves; finally, it re-queries them from the database and writes them out to the console.
You won't be able to run this yet, because there's one thing left to do. We need to implement the CreateSessionFactory
method; that's where our configuration goes to tie NHibernate and Fluent NHibernate together.
Lets implement the CreateSessionFactory
method.
private static ISessionFactory CreateSessionFactory() { }
That's the method signature sorted, you'll note it's returning an NHibernate ISessionFactory
. Now we're going to use the Fluent NHibernate Fluently.Configure
API to configure our application. You can see more examples on this in the Fluent Configuration wiki page.
private static ISessionFactory CreateSessionFactory() { return Fluently.Configure() .BuildSessionFactory(); }
That's not quite right yet, we're creating a SessionFactory
, but we haven't configured anyting yet; so lets configure our database.
private static ISessionFactory CreateSessionFactory() { return Fluently.Configure() .Database( SQLiteConfiguration.Standard .UsingFile("firstProject.db") ) .BuildSessionFactory(); }
There we go, we've specified that we're using a file-based SQLite database. You can learn more about the database configuration API in the Database Configuration wiki page.
Just one more thing to go, we need to supply NHibernate with the mappings we've created. To do that, we add a call to Mappings
in our configuration.
private static ISessionFactory CreateSessionFactory() { return Fluently.Configure() .Database( SQLiteConfiguration.Standard .UsingFile("firstProject.db") ) .Mappings(m => m.FluentMappings.AddFromAssemblyOf<Program>()) .BuildSessionFactory(); }
That's it, that's your application configured!
You should now be able to run the application and see the results of your query output to the console window.
Bargin Basin Products: Potatoes Fish Milk Bread Cheese Staff: Daisy Harrison Jack Torrance Sue Walkters SuperMart Products: Bread Cheese Waffles Staff: Bill Taft Joan Pope
There you go, that's your first Fluent NHibernate project created and running!
If you haven't manually created the schema for this application, then it will fail on the first time you run it. There's something you can do about that, but it needs to be done directly against the NHibernate Configuration
object; we can do that using the ExposeConfiguration
method. Combine that call with a method to generate the schema, then you're able to create your schema at runtime.
private static ISessionFactory CreateSessionFactory() { return Fluently.Configure() .Database( SQLiteConfiguration.Standard .UsingFile("firstProject.db") ) .Mappings(m => m.FluentMappings.AddFromAssemblyOf<Program>()) .ExposeConfiguration(BuildSchema) .BuildSessionFactory(); } private static void BuildSchema(Configuration config) { // delete the existing db on each run if (File.Exists(DbFile)) File.Delete(DbFile); // this NHibernate tool takes a configuration (with mapping info in) // and exports a database schema from it new SchemaExport(config) .Create(false, true); }
You can read more about this in the Fluent Configuration wiki page.
What next? GettingStarted: Existing Application