Background Knowledge
This is for those who are not familiar with the concept of a
query provider. It's all about
IQueryable<T>. By implementing this interface, you promise that you have a class (a query provider) that knows how to populate you (typically a collection) based on some domain specific data store. It can be a document database, a relational database, or even XML. In the case of Raven and NHibernate, we're dealing with document and relational databases. Raven's domain specific language is HTTP and REST, while NHibernate's is an abstraction layer atop SQL. The heart of any LINQ provider is expression trees. We call them quotations in F#, and they can be a nightmare for you when you want to use an existing LINQ implementation. Shame on you fsc. The c sharp compiler, csc, is a lot more friendly and compliant about emitting expression trees.
That being said, expression trees are where the magic happens. They are merely runtime representations of our code. The compiler will convert our calls against IQueryable like Where and Select to expression trees at compile time as opposed to delegates. Then it's up to you to implement an expression tree visitor and LINQ provider that knows how to parse each kind of expression supported by your API. You can find NHibernate's here and Raven's here. You'll be working with runtime representations of the standard LINQ query operators like Select, Where, OrderBy, and GroupBy. I'd like to assume everyone knows that there is a difference between IQueryable<T> and IEnumerable<T>, but I highly doubt that. What can be confusing for some is when they call
var five = new List<int> {3, 4, 5}.Where(n => n % 5 == 0).Single();
and it works. That's
LINQ to Objects. In that instance, we're working with
IEnumerable<T>. The key thing to remember is that both IQueryable<T> and IEnumerable<T> both have a set of extension methods that target them, and depending on which one you use you'll either love or hate the results. The extensions for IEnumerable<T> work with in memory collections as opposed to LINQ providers and expression trees. The extensions for IQueryable<T> are just an abstraction layer sitting atop your LINQ provider. You implement the LINQ provider, and .NET will invoke it at the proper time passing in the proper arguments (an expression tree). All you have to do is parse the tree and emit your domain specific output. Then you send that output to whatever backend you're encapsulating, fetch the results, and send them back to the client. I won't go any further into LINQ providers, but I figured I could clear up a little smoke by providing some concrete examples. The last thing I'll add is that IQueryable<T> is always lazily executed (just like IEnumerable<T>) and inherits from IEnumerable<T>. All IEnumerable<T> means is that you can iterate (for each) over its results. Now it's not that simple because the compiler generates this hidden class and a state machine but we won't get into that and monads. What makes it lazy is that your query won't be executed until the client tries to actually iterate. This is cool because it allows us to continually make calls on our IQueryable<T> without it hitting our data store each time. Obviously we're not ready to consume any results until we start to iterate so everything is deferred up until that point. And don't worry about the compiler accidentally choosing the wrong call to Where or Select. It's smart enough to know that IQueryable<T> is more specific that IEnumerable<T> and invoke the right set of extensions.
I'd also like to conclude with a low level deep dive into LINQ.
Implementing the UoW
Let's get started shall we. First thing's first; we need a common interface to wrap the NHibernate and Raven sessions respectively.
public interface ISession : IDisposable {
IQueryable<TEntity> Query<TEntity>() where TEntity : Entity;
void Add<TEntity>(TEntity entity) where TEntity : Entity;
void Update<TEntity>(TEntity entity) where TEntity : Entity;
void Delete<TEntity>(TEntity entity) where TEntity : Entity;
void SaveChanges();
#region Future Load Methods. Can't use now because Raven forces Id's to be strings. If were not for that, we could make this generic between NHibernate and RavenDb.
// TEntity Load<TEntity, TId>(TId id) where TEntity : Entity<TId>;
// IEnumerable<TEntity> Load<TEntity, TId>(IEnumerable<TId> ids) where TEntity : Entity<TId>;
#endregion
}
As you can see, we make each session promise to give us an
IQueryable<T>. We're also enforcing our sessions to implement
Unit Of Work, hence the
SaveChanges method. The rest of the functions are
CRUD based. Lastly we need to be able to shut the session down and free up resources so we make all sessions implement
IDisposable.
Now we'll make the concrete
RavenSession and it's wrapper class
UnitOfWork
public static class UnitOfWork {
public static void Start() {
CurrentSession = new RavenSession();
}
public static ISession CurrentSession {
get { return Get.Current<ISession>(); }
private set { Set.Current(value); }
}
}
internal class RavenSession : ISession {
readonly DocumentStore _documentStore;
readonly IDocumentSession _documentSession;
internal RavenSession() {
_documentStore = new DocumentStore { Url = "http://localhost:8080" };
_documentSession = _documentStore.Initialize().OpenSession();
}
public IQueryable<TEntity> Query<TEntity>() where TEntity : Entity {
/* may need to take indexing into consideration. raven will generat temps for us, but that may not be so efficient.
* i don't even know how long the temps stick around for. Raven will try and optimize for us best it can. */
return _documentSession.Query<TEntity>();
}
public void Add<TEntity>(TEntity entity) where TEntity : Entity {
_documentSession.Store(entity);
}
public void Update<TEntity>(TEntity entity) where TEntity : Entity {
_documentSession.Store(entity);
}
public void Delete<TEntity>(TEntity entity) where TEntity : Entity {
_documentSession.Delete(entity);
}
public void SaveChanges() {
_documentSession.SaveChanges();
}
public void Dispose() {
_documentStore.Dispose();
_documentSession.Dispose();
}
#region Future Load Methods. Can't use now because Raven forces Id's to be strings. If were not for that, we could make this generic between nHibernate and RavenDb.
public TEntity Load<TEntity, TId>(TId id) where TEntity : Entity<TId> {
throw new NotImplementedException();
}
public IEnumerable<TEntity> Load<TEntity, TId>(IEnumerable<TId> ids) where TEntity : Entity<TId> {
throw new NotImplementedException();
}
#endregion
}
I hard coded the url for now, but obviously I'd want it to be read from configuration somewhere.
Next I need a class to store the current session. I took an idea from a buddy of mine and made it
strongly typed and
reusable. It's just a wrapper around
HttpContext that falls back to an in memory
dictionary for
unit testing purposes.
public static class Ensure {
public static void That(bool condition) {
if(!condition)
throw new Exception("an expected condition was not met.");
}
public static void That<TType>(bool condition, string message) where TType : Exception {
if(!condition)
throw (TType)Activator.CreateInstance(typeof (TType), message);
}
}
public static class Get {
public static T Current<T>() where T : class {
var context = HttpContext.Current;
var key = typeof(T).FullName;
var value = context == null ? (T)Set.InMemoryValuesForUnitTesting[key] : (T)context.Items[key];
Ensure.That(value != null);
return value;
}
}
public static class Set {
internal static Dictionary<string, object> InMemoryValuesForUnitTesting = new Dictionary<string, object>();
public static void Current<T>(T value) {
var context = HttpContext.Current;
var key = typeof(T).FullName;
if (context == null)
InMemoryValuesForUnitTesting[key] = value;
else
context.Items[key] = value;
}
}
Implementing Core Domain Objects
It's nice to have a base structure in place from which our domain objects can derive. More specifically a base
entity and
repository class. The base repository is strongly typed and knows how to persist a specific type of entity. I created a
Raven specific repository because all ids in
Raven are strings (or so I thought. Raven actually supports POID generators just like
NHibernate). That's just the default implementation. It was implemented that way so the ids could be
RESTful and
human readable. Who wants to see a
GUID on the
query string? Not I...
public class Entity {}
public class Entity<TId> : Entity {
public TId Id { get; set; }
}
public class BaseRepository<T, TId> : IRepository<T, TId> where T : Entity<TId> {
public void Add(T entity) {
UnitOfWork.CurrentSession.Add(entity);
}
public IQueryable<T> All() {
return UnitOfWork.CurrentSession.Query<T>();
}
public virtual T Get(TId id) {
return All().Where(e => e.Id.Equals(id)).SingleOrDefault();
}
public IEnumerable<T> Get(IEnumerable<TId> ids) {
var idList = ids.ToList();
return All().Where(e => idList.Contains(e.Id));
}
public void Delete(T entity) {
UnitOfWork.CurrentSession.Delete(entity);
}
public void Update(T entity) {
UnitOfWork.CurrentSession.Update(entity);
}
}
public interface IRepository<T, in TId> : ICreate<T>, IRead<T, TId>, IDelete<T>, IUpdate<T> where T : Entity<TId> {}
public interface IDelete<in T> {
void Delete(T entity);
}
public interface IRead<out T, in TId> where T : Entity<TId> {
IQueryable<T> All();
T Get(TId id);
IEnumerable<T> Get(IEnumerable<TId> ids);
}
public interface ICreate<in T> {
void Add(T entity);
}
public interface IUpdate<in T> {
void Update(T entity);
}
internal class Person : Entity<string> {
public string Name { get; set; }
public int Age { get; set; }
}
internal class PersonRepository : BaseRepository<Person, string>, IPersonRepository {
}
internal interface IPersonRepository : IRepository<Person, string> {
}
I implemented
CRUD interfaces for my repositories so that a client can choose which operations it wants to interact with. If all a client needs to do is perform reads, then it can consume the
IRead<T> interface and opposed to a full fledged
IRepository<T>. That concrete implementation of
IRead<T> would still be able to inherit from
BaseRepository<T>, but would not be consumed as such. Using
dependency injection, you'd do something like...
Map<IRead<User>>.To<UserRepository>();
Then an
MVC controller or some dependant object would look like...
public class AccountController(IRead<User> userRepository) {...}
This concept is the
I in
SOLID for
Interface Segregation. Give the client only what it needs. Nothing more and nothing less.
I didn't think I'd need something for updates like
IUpdate<T> since most
UoW implementations will implement change tracking. For instance if you retrieve and entity from a
Raven or
NHibernate session and modify it, the changes will automatically be applied upon saving the session. But thent I thought about what happens in
ASP.NET MVC when we handle updates. Say the user goes to our update page and makes some changes to some text fields that represent and entity. The
ASP.NET MVC will automatically construct an instance of our entity or
view model and allow us to persist it. Their is a
TryUpdateModel that
MVC exposes on controllers, but what if you're mapping from
view model to
entity/DTO? There'd be no need to retrieve the entity from the
domain layer since you already have a copy of it in memory. I could be wrong on this. Maybe it's common practice to always find your entity, apply the necessary changes, and persist it. I'm not sure how most do it, but having
IUpdate<T> doesn't hurt right?
Implementing a Request Module for ASP.NET
Now I need a request module that knows how to initialize the session and spawn the
UoW.
public class UnitOfWorkModule : IHttpModule {
public void Init(HttpApplication application) {
application.BeginRequest += ApplicationBeginRequest;
application.EndRequest += ApplicationEndRequest;
}
static void ApplicationBeginRequest(object sender, EventArgs e) {
UnitOfWork.Start();
}
static void ApplicationEndRequest(object sender, EventArgs e) {
UnitOfWork.CurrentSession.SaveChanges();
}
public void Dispose() {
UnitOfWork.CurrentSession.Dispose();
}
}
Let me add that I borrowed the idea of this particular implementation of
UoW from
a blog on nhforge.com. I tweaked it to my liking. It's not perfect, but I'm content with it and it works for me. I'd never go so far as to deem this the ultimate implementation of
UoW.
The cool thing about our implementation is that we can switch from
Raven to
NHibernate with one line of code.
The bad thing is that we can't leverage any framwork specific goodies. For instance, the power behind document databases is that they perform lightning fast reads. This is accomplished via
indexes. In
Raven, we specifiy our indexes upon executing our queries, but there's no way for my
BaseRepository to do that unless it knows it's dealing with
Raven in particular. I'd have to cheat to do that and probably break my encapsulation by assuming certain things about the current
ISession at hand. Something like type casting it to an
IDocumentSession (
Raven specific).
Raven is smart enough to dynamically create indexes for us on the fly if it detects that we didn't specify one client side, and will eventually promote them to permanent indexes if we used them enough over a certain amount of time. Frankly you just need to be aware of what you're gaining and losing. You should analyze if the benefits of a clean and reusable design are worth the extra work it takes to be able to leverage all of your target frameworks features. Sometimes you can get away with declarative xml configuration independent of code, or decorating your classes with a specific attribute and having the runtime pick up on it; But that's a big maybe and a long shot in most cases. Regardless I thought this would be a cool idea and fun to implement.
Implementing Unit Tests For Raven
We're not done yet my friends. It's time for some
unit tests.
NUnit where you be?
[Test]
public void Person_Repository_Can_Save_Person() {
IPersonRepository personRepository = new PersonRepository();
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
personRepository.Add(adubb);
var id = adubb.Id;
var adubbFromRepo = personRepository.Get(id);
Assert.IsNotNull(adubbFromRepo);
Assert.AreEqual(adubb.Id, adubbFromRepo.Id);
Assert.AreEqual(adubb.Name, adubbFromRepo.Name);
Assert.AreEqual(adubb.Age, adubbFromRepo.Age);
personRepository.Delete(adubbFromRepo);
UnitOfWork.CurrentSession.SaveChanges();
}
Whoops!! Looks like
Raven doesn't allow us to call Equals to in the body of our
lamdas. Time to
refactor. We need to
override our base implementation of
Get(TId id); Let's make it
virtual and
override it.
public class RavenBaseRepository<T> : BaseRepository<T, string> where T : Entity<string> {
public override T Get(string id) {
return All().Where(e => e.Id == id).SingleOrDefault();
}
}
internal class PersonRepository : RavenBaseRepository<Person>, IPersonRepository {
}
I'm already noticing that my query is taking a rather long time to execute. This probably means
Raven isn't making
optimized reads. I'd expect things to execute a lot faster.
Anyway, let's run our test again.
That's strange. We didn't find any results. Something must be going wrong with my
Id. The problem is the entity is still
transient. That is to say, it hasn't been persisted yet. We need to submit our changes before performing our read. Let's
refactor our test.
personRepository.Add(adubb);
UnitOfWork.CurrentSession.SaveChanges();
var id = adubb.Id;
We told
Raven to persist the object before retrieving it. Let's try again.
Ok. I'm still getting an error. I probably shouldn't be messing around with my
Id property. That's
Raven's. Let's make one final change.
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
Aaaaand!! Nope. Still didn't work. You may have caught on now but if you haven't the problem is
inheritance.
Raven apparently can't pick up on the fact that I'm inheriting my
Id from my parent class
Entity. So now I have to redefine it in the person class like so.
public class Person : Entity<string> {
public new string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
Alright. Things are working now according to my unit test and
Raven Studio. My
Add test passes. Now it's time to test
delete.
[Test]
public void Person_Repository_Can_Delete_Person() {
IPersonRepository personRepository = new PersonRepository();
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
personRepository.Add(adubb);
UnitOfWork.CurrentSession.SaveChanges();
var id = adubb.Id;
var adubbFromRepo = personRepository.Get(id);
Assert.IsNotNull(adubbFromRepo);
personRepository.Delete(adubbFromRepo);
UnitOfWork.CurrentSession.SaveChanges();
adubbFromRepo = personRepository.Get(id);
Assert.IsNull(adubbFromRepo);
}
This one worked right out of the box. No magic needed.
Get is next.
[Test]
public void Person_Repository_Can_Get_Person() {
IPersonRepository personRepository = new PersonRepository();
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
personRepository.Add(adubb);
UnitOfWork.CurrentSession.SaveChanges();
var id = adubb.Id;
var adubbFromRepo = personRepository.Get(id);
Assert.IsNotNull(adubbFromRepo);
personRepository.Delete(adubb);
}
And lastly
update. This one is pretty easy to test because of
change tracking so I have 2 implementations. The first works just fine.
[Test]
public void Person_Repository_Can_Update_Person_Without_Calling_Update() {
IPersonRepository personRepository = new PersonRepository();
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
personRepository.Add(adubb);
UnitOfWork.CurrentSession.SaveChanges();
var id = adubb.Id;
var adubbFromRepo = personRepository.Get(id);
Assert.IsNotNull(adubbFromRepo);
const string changedName = "Changed Name";
adubbFromRepo.Name = changedName;
UnitOfWork.CurrentSession.SaveChanges();
adubbFromRepo = personRepository.Get(id);
Assert.AreEqual(changedName, adubbFromRepo.Name);
personRepository.Delete(adubb);
}
But we run into problems with the second.
Looks like
Raven is forcing me to fetch my entity from the session before I can update it. It knows the entity is unattached. So I guess I could remove my implementation of
Update.
NHibernate however will properly convert my
Contains call to an
IN Clause; which is what I expect.
[Test]
public void Person_Repository_Can_Update_Person_When_Calling_Update() {
IPersonRepository personRepository = new PersonRepository();
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
personRepository.Add(adubb);
UnitOfWork.CurrentSession.SaveChanges();
const string changedName = "Changed Name";
const int changedAge = 19;
var id = adubb.Id;
// this entity didn't come from the session and thus is not being tracked. we're pretending like we've just
// populated this entity in our controller based on the view and are about to persist it.
var adubbFromRepo = new Person {Id = id, Age = changedAge, Name = changedName };
personRepository.Update(adubbFromRepo);
UnitOfWork.CurrentSession.SaveChanges();
adubbFromRepo = personRepository.Get(id);
Assert.AreEqual(changedName, adubbFromRepo.Name);
Assert.AreEqual(changedAge, adubbFromRepo.Age);
personRepository.Delete(adubb);
}
My last test if for
Get with an overload. Of course it failed because
Raven won't let me call
Contains during my query. I'll figure it out later though. It's 1am right now and I'm beat. Plus my hot pocket is almost done cooking.
public static class ObjectExtensions {
public static IEnumerable<TType> ToSingleEnumerable<TType>(this TType target) {
yield return target;
}
}
[Test]
public void Person_Repository_Can_Find_All_By_Id() {
IPersonRepository personRepository = new PersonRepository();
var adubb = new Person { Age = 22, Name = "Antwan \"A-Dubb\" Wimberly \r\nIt's Okay To Not Hire A Senior Developer!! There Are Good Young Develpers Out There Too!!" };
personRepository.Add(adubb);
UnitOfWork.CurrentSession.SaveChanges();
var id = adubb.Id;
var adubbFromRepo = personRepository.Get(id.ToSingleEnumerable()).FirstOrDefault();
Assert.IsNotNull(adubbFromRepo);
Assert.AreEqual(adubb.Id, adubbFromRepo.Id);
Assert.AreEqual(adubb.Name, adubbFromRepo.Name);
Assert.AreEqual(adubb.Age, adubbFromRepo.Age);
personRepository.Delete(adubbFromRepo);
}
Implementing NHibernate Support
We're going to make a context switch to NHibernate now. We'll start with a concreate implementation of the ISession interface for NHibernate.
internal class NHibernateSession : ISession {
static readonly ISessionFactory SessionFactory;
static NHibernateSession() {
// an expensive operation that should be called only once throughout the lifetime of the application. you'll typically see this in Application_Start of Global.asax.
SessionFactory = Fluently
.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString("Server=.; Database=NHPrac; Integrated Security=true;")
.ShowSql())
.ExposeConfiguration(x => {
// for our CurrentSessionContext. this has to be configured or NHibernate won't be happy
x.SetProperty("current_session_context_class", "thread_static");
// so the Product table can be exported to the database and be created before we make our inserts
var schemaExport = new SchemaExport(x);
schemaExport.Create(false, true);
})
.Mappings(x => x.FluentMappings.AddFromAssembly(Assembly.Load("Intell.Tests")))
.BuildSessionFactory();
}
internal NHibernateSession() {
// sessions are really cheap to initialize/open
var session = SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
static NHibernate.ISession CurrentSession { get { return SessionFactory.GetCurrentSession(); } }
public void Dispose() {
// unbind the factory and dispose the current session that it returns
CurrentSessionContext.Unbind(SessionFactory).Dispose();
}
public IQueryable<TEntity> Query<TEntity>() where TEntity : Entity {
return CurrentSession.Query<TEntity>();
}
public void Add<TEntity>(TEntity entity) where TEntity : Entity {
CurrentSession.Save(entity);
}
public void Update<TEntity>(TEntity entity) where TEntity : Entity {
CurrentSession.Update(entity);
}
public void Delete<TEntity>(TEntity entity) where TEntity : Entity {
CurrentSession.Delete(entity);
}
public void SaveChanges() {
var transaction = CurrentSession.Transaction;
if (transaction != null && transaction.IsActive)
CurrentSession.Transaction.Commit();
}
}
There's nothing special going on here. Just your standard
Fluent NHibernate stuff and
NHibernate basics in general. If my usage of the
CurrentSessionContext class is unfamiliar to you then I'd suggest you get yourself a copy of the
NHibernate 3.0 Cookbook. It's got the latest
NHibernate best practices in it and was the basis of how I managed my
NHibernate session.
Before I go any further I want to note that
NHibernate won't let me call
Equals in my
Get(TId id) method so again I have to make a framework specific repository. Dangit!!
And yes I'm aware that
S#arp Architecture has a base
NHibernate and a base
Entity class (I think...), but I wanted to take a stab at creating my own. It probably looks identical to what's already out there but oh well.
// we'd probably have to make a separate one for id's of type int to support identity columns
public class NHibernateBaseRepository<T> : BaseRepository<T, Guid> where T : Entity<Guid> {
public override T Get(Guid id) {
return All().Where(e => e.Id == id).SingleOrDefault();
}
}
Next we'll build a
ProductRepository,
mapping file, and
Product entity.
public class Product : Entity<Guid> {
public virtual string Name { get; set; }
public virtual int InventoryCount { get; set; }
}
public sealed class ProductMap : ClassMap<Product> {
public ProductMap() {
Id(x => x.Id)
.GeneratedBy
.GuidComb();
Map(x => x.InventoryCount);
Map(x => x.Name);
}
}
internal class ProductRepository : NHibernateBaseRepository<Product>, IProductRepository {}
internal interface IProductRepository : IRepository<Product, Guid> {}
The
Enity<TId> class becomes...
public class Entity<TId> : Entity {
public virtual TId Id { get; set; }
}
Of course everything has to be virtual for proxy support so I had to make a slight modification to my base
Entity<TId> class.
I only wrote 2
unit tests this time around, because I'm more than confident that the functionality works. Earlier I ran into a problem with my
GetAll(IEnumerable<TId> ids) implementation of my
BaseRepository class due to constraints that
Raven enforces. I still have to come up with a clean work around, but we won't worry about that for now. This time I wanted to be sure my
GetAll overload would work so I tested it in addition to
Save. The tests both pass with flying colors.
The
Start method of
UnitOfWork becomes...
public static void Start() {
// CurrentSession = new RavenSession();
CurrentSession = new NHibernateSession();
}
[TestFixture]
public class ProductRepositoryTests {
[TestFixtureSetUp]
public void Init_Unit_Of_Work() {
UnitOfWork.Start();
}
[TestFixtureTearDown]
public void Uninit_Unit_Of_Work() {
UnitOfWork.CurrentSession.Dispose();
}
[Test]
public void Product_Repository_Can_Save() {
var product = new Product { InventoryCount = 12, Name = "A-Dubb's World" };
IProductRepository productRepository = new ProductRepository();
productRepository.Add(product);
var id = product.Id;
Assert.IsFalse(id == Guid.Empty);
product = productRepository.Get(id);
Assert.AreEqual(12, product.InventoryCount);
Assert.AreEqual("A-Dubb's World", product.Name);
productRepository.Delete(product);
UnitOfWork.CurrentSession.SaveChanges();
}
[Test]
public void Product_Repository_Can_Get_All() {
var product = new Product { InventoryCount = 12, Name = "A-Dubb's World" };
IProductRepository productRepository = new ProductRepository();
productRepository.Add(product);
var id = product.Id;
Assert.IsFalse(id == Guid.Empty);
product = productRepository.Get(id.ToSingleEnumerable()).SingleOrDefault();
Assert.IsNotNull(product);
Assert.AreEqual(product.Name, "A-Dubb's World");
Assert.AreEqual(product.InventoryCount, 12);
productRepository.Delete(product);
UnitOfWork.CurrentSession.SaveChanges();
}
}
And yea I should have followed
TDD and written my tests first and I surely could have
refactored my
units tests for reusability's sake; but I'm not gonna bother.
Conclusion
We started out with our common session interface for
NHibernate and
Raven to implement. The we made our
UoW and concrete
Raven based session implementation. That was followed by our strongly typed classes for
local storage and our core
domain layer base classes (
Entity and
BaseRepository). We then subclasses out
BaseRepository to make a
Raven specific implementation that stores
Entities with string based ids as
Raven requires. Since we plan to be able to use
Raven on the web, we made an
HttpModule that can be registered in
Web.config to initialize our session for each request made to our web application. And lastly, we wrapped things up with a few
unit tests, a concrete
ISession implementation for
NHibernate, and discovered some things about
Raven along the way. Specifically it can not pick up on and inherited
Id property and only a specific subset of methods are allowed to be executed within our
query calls/lamda expressions.
Well, that's it folks. As I mentioned before, the switch between
Raven and
NHibernate is a trivial but potentially problematic one,
but you'd at least have your core
domain layer in place for each framework. For one,
Id's in
Raven are string based, which is not the case in
NHibernate where
GUIDs tend to dominate. So switching would probably mean refactoring your
entities switching the
repository you
inherit from; which could certainly be a problem. Secondly, it makes it tougher to use framework specific features such as
indexes in
Raven when executing queries; which is one its most important features. Were it not for the aforementioned constraints, you'd be able to switch from
Raven to
NHibernate with one line of code. That's why I build this post to begin with. I thought I could pull it off, but my
unit tests told me otherwise. Either way, this was really fun to implement and I learned a lot. I hope this post proves to be helpful to a lot of people and can maybe serve as a catalyst for future implementations.
This is quite a bit of code so I should be uploading to
github any day now.
I'll follow up this post by implementing a cool implementation of read only mode in
ASP.NET (web forms). Pretty cool right?
Cheers!!