Blog

Abstracting an ORM to Use Multiple Backends

Abstracting an ORM to Use Multiple Backends

Object-relational mappings (ORMs) took the world by storm in the mid-aughts. Obviously there are many advantages to ORMs given their success, but there are some drawbacks. I'll explain one particular hurdle that I ran into as well as the steps I took to overcome it.

A client had an existing database we needed to interact with using an ORM; this necessitated a database-first approach. Furthermore, this database's schema was mirrored in other databases with slight modifications. DMC had to build an application that could talk to all of these databases - which were 99% similar - using a single interface, so that new, slightly different schemas could be dropped in without a problem.

The problem with a database-first ORM, here Microsoft Entity Framework 5, was that the use of multiple database objects infected the business logic. The schemas were similar enough that a single interface could be abstracted out. The only question after devising this interface was how to implement it using the Entity Framework generated files.

Consider a table called Entity, with a field called Property. This schema exists on different database servers with different query strings, and Property is slightly different between the two. For simplicity's sake lets say it's a number in one database and a numeric string in another database.

Interacting with Property with the Entity Framework would require two database models and their associated classes, with one Property class being a float and another Property class being a string. Our interface presents this as a float.

public interface IEntity
{
  float Property { get; set; }
}

To interact with both, we create explicit implementations of the interface for the Entity table on a partial class mapped to the Entity class generated by our database-first approach. One implementation of this interface is a pass-through to the first Property member of the generated partial class. The other converts the float to a string before passing the converted value on to the second generated Property member.

Now that we have abstracted the Entity table, let's abstract the database itself. In Entity Framework, tables are represented as DbSets. DbSet implements IQueryable, which is sufficient for querying the database using Linq. The interface follows.

public interface IDatabase : IDisposable
{
  IQueryable<IEntity> Entity { get; }

  IEntity CreateEntity();
  IEntity Add(IEntity entity);

  int SaveChanges();
}

Note that additional Create and Add methods are required since IQueryable does not support these operations.

Implementing this interface is as simple as implementing IEntity. Explicitly implement IDatabase on a partial class of Database and pass the required properties from the Entity Framework-generated class to the interface implementation.

That's it. Note that when implementing an ICollection foreign key property on a table, you will need to cast it as IEnumerable in the interface. This cast is handled implicitly with .NET 4.5.

There's one more trick, though. The Entity Framework is not smart enough to take namespace into account when mapping code objects to database objects. So if you are implementing IEntity multiple times, each implementation will have to be done within its own assembly and namespace. A good solution architecture is to have an interface project referenced by each implementation project and then finally a factory project that references the interface project and all implementation projects. The factory methods can give you a different implementation of IDatabase based upon some parameter.

Learn more about DMC's custom hardware and software development services.

Comments

There are currently no comments, be the first to post one.

Post a comment

Name (required)

Email (required)

CAPTCHA image
Enter the code shown above:

Related Blog Posts