As per a request, I am providing code for both my in memory testing repository as well as my sql server based NHibernate repository so you can see how I have used Linq-to-NHibernate to create a testable interface and a real implementation that will both work under the same code.
First up is our Persistent base, this isn't really necessary but it saves me some time as I don't have to put an ID field into all our Persistent classes and it allows me to restrict what we can put in our repositories
public abstract class Persistent
{
public int ID { get; set; }
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != this.GetType()) return false;
Persistent comp = (Persistent)obj;
if (comp.ID == 0 && this.ID == 0) return object.ReferenceEquals(this, comp);
return comp.ID == this.ID;
}
}
Now for our Repository interfaces. Notice I don't use the I prefix for interfaces, I am starting to lean away from that practice and going to a more java-like approach of naming things nicely.
public interface Repository
{
RepositorySession OpenSession();
}
public interface RepositorySession : IDisposable
{
IQueryable Query() where T : Persistent;
void Save(Persistent e);
void Delete(Persistent e);
}
I think the interfaces are straightforward enough. We use a session based approach as opposed to a single Repository interface that has all the methods. The idea is that Repositories hold info about connecting to the database and the session itself is similar to a Connection object in ADO.NET. That is why we've inherited from IDisposable
Now for our InMemoryRepository
public class InMemoryRepository : Repository, RepositorySession
{
private List database;
private int nextID = 1;
public InMemoryRepository(params Persistent[] seeds)
{
database = new List();
seeds.Each(s => Save(s));
}
public T Get(int id) where T : Persistent
{
return (T)database.FirstOrDefault(e => e.ID == id && (TypeCheck(e)));
}
public void Save(Persistent e)
{
database.RemoveAll(ent => ent.ID == e.ID);
if (e.ID == 0) e.ID = nextID++;
database.Add(e);
foreach (PropertyInfo property in e.GetType().GetProperties())
{
Type pt = property.PropertyType;
if (pt.IsSubclassOf(typeof(Persistent)))
{
Persistent prop = (Persistent)property.GetValue(e, null);
if (prop != null && !database.Contains(prop))
Save(prop);
}
else if (pt.GetInterfaces().Contains(typeof(IEnumerable)) && pt.IsGenericType && (pt.GetGenericArguments()[0] == typeof(Persistent) || pt.GetGenericArguments()[0].IsSubclassOf(typeof(Persistent))))
{
foreach (Persistent child in (IEnumerable)property.GetValue(e, null))
{
if (child != null && !database.Contains(child))
Save(child);
}
}
}
}
public void Delete() where T : Persistent
{
database.RemoveAll(TypeCheck);
}
public void Delete(Persistent e)
{
database.RemoveAll(ent => ent.ID == e.ID);
}
public IQueryable Query() where T : Persistent
{
return database.Where(TypeCheck).Cast().AsQueryable();
}
private bool TypeCheck(Persistent e)
{
return e.GetType() == typeof(T) || e.GetType().IsSubclassOf(typeof(T));
}
public RepositorySession OpenSession()
{
return this;
}
public void Dispose()
{
}
}
For the most part it is pretty straightforward with a little reflection being used when we save a Persistent object to check if it has any properties that reference Persistents or collections of Persistents and then we chain the Save to them.
Note that this class was developed via TDD so it may not be feature complete for everyone but all our tests in the app we are currently working on pass so it may grow some more over time.
Finally, here is our SqlServerRepository (based on NHibernate)- you will notice that it has much less code than the inmemory one.
public class SqlServerRepository : Repository
{
private string serverName;
private string databaseName;
private ISessionFactory sessionFactory;
public SqlServerRepository(string serverName, string databaseName)
{
this.serverName = serverName;
this.databaseName = databaseName;
}
private void BuildSessionFactory()
{
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
cfg.Properties["connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
cfg.Properties["dialect"] = "NHibernate.Dialect.MsSql2005Dialect";
cfg.Properties["connection.driver_class"] = "NHibernate.Driver.SqlClientDriver";
cfg.Properties["connection.connection_string"] = GetConnectionString();
cfg.Properties["query.substitutions"] = "true 1, false 0";
cfg.AddAssembly(System.Reflection.Assembly.GetExecutingAssembly());
sessionFactory = cfg.BuildSessionFactory();
SchemaManager schemaManager = new SchemaManager(this.serverName, this.databaseName, cfg);
schemaManager.Sync();
}
private string GetConnectionString()
{
return String.Format("Data Source={0}; Initial Catalog={1}; Integrated Security=SSPI;", this.serverName, this.databaseName);
}
#region Repository Members
public RepositorySession OpenSession()
{
if (this.sessionFactory == null)
{
BuildSessionFactory();
}
return new SqlServerRepositorySession(sessionFactory.OpenSession());
}
#endregion
}
public class SqlServerRepositorySession : RepositorySession
{
private ISession session;
public SqlServerRepositorySession(ISession session)
{
this.session = session;
}
#region RepositorySession Members
public IQueryable Query() where T : Persistent
{
return this.session.Linq();
}
public void Save(Persistent e)
{
this.session.SaveOrUpdate(e);
this.session.Flush();
}
public void Delete(Persistent e)
{
this.session.Delete(e);
this.session.Flush();
}
#endregion
#region IDisposable Members
public void Dispose()
{
this.session.Close();
}
#endregion
}
That's about it. We love this approach and it's been awesome for testing. In order to test against a live database, just change your unit tests to utilize an SqlServerRepository and a local DB. This could be done via a config file or by using a base test class for all your tests that hit a Repository and then just changing that class to decide which Repository implementation to use.
One other thing which we do currently is separate our unit tests into fast and slow projects. Tests which go against a real database sit in the slow tests project and the rest go into the fast tests project. 99% of the time we only run our fast tests and then once in a while we'll run the slow to make sure they aren't broken.
As far as using Linq-to-NHibernate in a production environment, I have no qualms about it. It's really only a thin layer on top of NHibernate which is a very stable mapper.