I have been diving pretty deep into ADO.NET Data Services (see an upcoming article about ADO.NET Data Services and Silverlight 2 coming soon). I've been looking at the story around non-Entity Framework models through a Data Service and thought that NHibernate through a Data Service would be a great example.
So I tried to get it to work with the NHibernate LINQ project. The example model that the project uses is a simple Northwind model. I thought I'd just take that model and expose it via ADO.NET Data Services. I crufted up a simple Data Context object for ADO.NET Data Model but it didn't work. ADO.NET Data Services was complaining about the fact that the end-points (e.g. IQueryable<Customer>) was pointing at an Entity. This was a bug...a but in ADO.NET Data Services. I hacked together a fix to get around it (and reporting it). If you're interest, the problem is that if the key of the entity is in a base class and *not* named "ID", it fails to find it. Again, this is a bug not a feature of ADO.NET Data Services.
Now that it was working, now what? I wanted to be able to make changes to the model but the NHibernate context doesn't support the updating interface: IUpdatable. (Though I sure wish they'd rename it IUpdateProvider to match the IExpandProvider interface). If you're not familiar with this requirement, note that only the Entity Framework currently support the IUpdatable interface (and is in fact implemented inside of ADO.NET Data Services not directly in Entity Framework). This means that DataSets and LINQ to SQL do not support it either.
That's where I went to the Rhino Google Group (the discussion point for this project) and offered to implement the IUpdatable for the NHibernate provider. I thought it would be a fun exercise to understand the guts of the ADO.NET Data Service stack.
My first chore was to attempt to find a solution to decouple System.Data.Services.dll from the rest of the LINQ provider. My reasoning for this is that ADO.NET Data Services requires .NET 3.5 SP1 and I didn't want to tie the changes to that version of the Framework. Interestingly this is the same tactic that the ADO.NET Data Services team took. This is why the Entity Framework does not implement the IUpdatable interface themselves, but the ADO.NET Data Service does it for them. For the rest of the world, it expects a IUpdatable implementation directly on the context object that is used as the starting point for the service. After running around a number of ideas and running it by the Rhino people, we've decided to table that problem and get the interface working. That will be a fight we fight later with a number of proposed solutions.
So now that architecture is out of the way, its time to start actually implementing the interface. For the first pass, I am going to implement IUpdatable directly on NHibernateContext and refactor it later for less tight coupling. While I am at it, I also want to add support for IExpandProvider so that we can do expansions through NHibernate.
After adding the interface to the context object, I looked through the interface (its about a dozen calls that need to be implemented) to determine where to start. I am starting with the simple calls (GetResource, CreateResource, DeleteResource). These calls are used by the service to do basic object manipulation.
For GetResource, I am handed a query and possibly a type name. GetResource is supposed to invoke the query to get the one and only one result (implementors should throw an error if the result returns more than one result) and then check the type name against the result. In some cases you won't get the type name, but in most cases you will need to verify that the type of the result is the same as the type name. This is used to make sure that derived types are correctly retrieved from the data provider.
In implementing CreateResource, I noticed that I needed to be able to look up type information. Type information is important as I need information about the containers (e.g. IQueryable endpoints) on the context object. This information is not readily available as its the classes that derive from NHibernate that add these end-points. I could get the type information via NHibernate's type information or using Reflection (neither are particularly cheap). For the first iteration I choose Reflection since I know that better, but I wanted to mitigate the cost with some caching. To that end, I've created some small in-memory caches of the reflection information that hopefully will help with the performance of these lookups. This type information will help act like a factory to create new instances of the object. Unlike other ORM's, NHibernate doesn't use factories but uses simple object creation so we can just use the reflection information to create the instance of the new object and attach it to the NHibernate ISession.
Lastly, the simplest of the three methods to implement is the DeleteResource as it passes in a query (again, should always only return a single result) and mark it deleted in the session. The change shouldn't be persisted (there is a SaveChanges call on the IQueryable interface that facilities the actual persistence to the data store).
Overall a fun couple of days. Now on to the rest of the interface. I'll keep you posted with my experience dealing with them!
|Using Visual Studio Code for ASP.NET Core Projects (new)|
|Building a Web App with ASP.NET Core, MVC6, EF Core and AngularJS|
|Building a Web App with ASP.NET5, MVC6, EF7, and AngularJS (Retired)|
|Best Practices in ASP.NET: Entities, Validation, and View Models|
|Front-End Web Development Quick Start|
|Lessons from Real World .NET Code Reviews|
|Node.js for .NET Developers|
|Application Name||WilderBlog||Environment Name||Production|
|Application Ver||220.127.116.11||Runtime Framework||.NETCoreApp,Version=v1.1|
|App Path||D:\home\site\wwwroot||Runtime Version||.NET Core 4.6.25211.01|
|Operating System||Microsoft Windows 6.2.9200||Runtime Arch||X86|