Rants Tagged with “ADO.NET Data Services”
1 (Total Pages: 1/Total Results: 5)
After some discussion, Mike Flasko (Program Manager, ADO.NET Data Services) has clarified the relationship between the Silverlight 2 Beta 2 Data Services Library and the SP1 of VS2008/.NET 3.5. Specifically he says:
While the Silverlight Beta 2 data services library may work in some scenarios against a .NET Framework 3.5 SP1 RTM server, you should NOT count on the two being fully compatible with each other.
For me, I am going to wait to roll up to SP1 RTM until Silverlight 2 RTM's. Caveat emptor. Follow the above link to read his full discussion.
NOTE: This blog post is an adjunct topic to the IUpdateable series of posts that I have here and here.
As part of my work on the ADO.NET Data Services support for NHibernate.LINQ I decided that we should support IExpandProvider as well.
For the uninitiated, IExpandProvider is another interface that ADO.NET Data Services support to allow the expansion feature of Data Services to work. Essentially, expansion is about eager loading of related entities as part of a Data Service Query. For example, if you had a Data Services query that said:
http://www.silverlightdata.com/simple/HibProducts.svc/Products(1)
This URI would go retrieve the Product, but if you wanted to retrieve the Category (a related entity) as part of the single call, you could annotate the URI with a query string parameter called $expand to indicate the paths to eager load:
http://www.silverlightdata.com/simple/HibProducts.svc/Products(1)?$expand=Category
In fact, the expansion support allows you to specify multiple paths (separated by commas) to eager load:
http://www.silverlightdata.com/simple/HibProducts.svc/Products(1)?$expand=Category,Supplier
The way it works inside the Data Service is that checks to see if the context object specified in the Data Service support IExpandProvider (the built-in Entity Framework provider does this automatically). If it supports the interface, it calls the ApplyExpansions method. At that point, the provider is supposed to take the IQueryable interface and change the query to support the expansions. Remember, the methodology of Data Services is to take the URI syntax and convert the query into an IQueryable instance. At the end of the process it executes this query so the IExpandProvider happens before the database is ever involved. All it needs to do is to expand the expression tree to add the expressions necessary to expand the nodes.
So how did I go about it? My first step was to add expansion support to the LINQ interface. Before I got started, eager loading was the responsibility of the user of the LINQ query. There was no way to make eager loading happen in the expression tree. I modeled the support after the way that Entity Framework supports eager loading and added a new expression (called Expand in the NHibernate.LINQ provider) that instructs the underlying provider to eager load a certain path. Interestingly the ICriteria support in NHibernate already had support for eager loading so it was more plumbing to the NHibernate functionality that any real work. The expand works during the data source of the LINQ query:
var query = from timesheet in session.Linq<Timesheet>().Expand("Entries")
where timesheet.Entries.Count > 0
select timesheet;
By adding the Expand method call, it adds a hint to the query to eager load the Entries on the Timesheet object. Now that expansion is supported in the LINQ provider, I can wire up the IExpandProvider to simply add that expression for every path.
To support it, I simple walk through each of the paths that are passed to the ApplyExpansions method, but its not quite that simple. The method signature is:
IEnumerable ApplyExpansions(IQueryable queryable,
ICollection<ExpandSegmentCollection> expandPaths);
The first property is the query that you are going to modify for the expansions. The second parameter is a list of ExpansionSegmentCollections. Yup, a collection of a collection. I am unclear why this is so, but nevertheless, I simply go through each collection of collections (I've never been passed more than one, but perhaps MS can explain why). The ExpandSegmentCollection is a collection of ExpandSegment objects. This object supports two pieces of information: Name and Filter. Name is the path that is requested in the URI syntax; and Filter which it is unclear the exact use case. In my implementation, I simply threw an error if I found a filter since I couldn't find any code path that caused them so I didn't know how to modify the query to support them. My guess is that its there for future use.
So there you go, its added. This new interface in coordination with the IUpdateable makes any NHibernate object supported in not only LINQ but also ADO.NET Data Services. What do you think?
Now that my ADO.NET Data Services support has been merged into the trunk of NHibernate.LINQ, I do have some caveats about using NHibernate.LINQ with ADO.NET Data Services. ADO.NET Data Services is a beta 1 product so there are some bugs and issues that you will either need to avoid or work around.
The biggest issue is around entity identity. ADO.NET Data Services must know how identity is established for objects in order to support the Data Service. It does this in a two step process:
- First it looks for attributes that describe the 'primary key'.
- Failing that, it looks for properties on the entity called ID, or ending with "ID".
The second approach is where I expect most of NHibernate projects to fall into since you really don't want to pollute your objects with technology specific information (the attributes). This approach works well except that there is a bug in the Beta 1 version of ADO.NET Data Services. If the properties are specified in a base class and the keys are specified ending in "ID" (instead of just being called "ID"), then the search for the identifiers fails and Data Services fails to want to serve these objects. For example:
public class AbstractCategory
{
public virtual int CategoryID { get; set; }
public virtual string CategoryName { get; set; }
public virtual string Description { get; set; }
public virtual byte[] Picture { get; set; }
public virtual IList Products { get; set; }
}
public class Category : AbstractCategory
{
}
}
If this is your scenario, I might suggest waiting for later build of ADO.NET Data Services to be released as this is definitely a bug not expected behavior and I have gotten word from Microsoft that it is fixed in the RTM (which isn't available yet).
The next issue is that for collections, ADO.NET Data Services must understand the types that belong in a collection. In this case our above example will not work either in that having the Products in a Category as a simple IList can't tell ADO.NET Data Services what types of objects to deal with. If we change this to an IList<Products>, it works fine. If we have to change our entities to work with ADO.NET Data Services, this is what our new Category might look like instead:
public class Category
{
public virtual int CategoryID { get; set; }
public virtual string CategoryName { get; set; }
public virtual string Description { get; set; }
public virtual byte[] Picture { get; set; }
public virtual IList<Product> Products { get; set; }
}
With these changes, ADO.NET Data Services work fine.
If you are new to ADO.NET Data Services, this blog entry may help with some debugging issues in using it:
http://wildermuth.com/2008/06/07/Debugging_ADO_NET_Data_Services_with_Fiddler2
Lastly, I want to follow up on a note that Ayende mentioned on his announcement of my examples. In his blog post, he said:
From a technological perspective, I think this is awesome. However, there are architectural issues with exposing your model in such a fashion. Specifically, with regards to availability and scalability on the operations side, and schema versioning and adaptability on the development side.
I think he's right in that there is a schema version issue here that needs to be addressed but that the availability and scalability problems are ones that would be in the underlying data model itself. Since ADO.NET Data Services are just a convenience around WCF's REST Service Model, we can scale out or up depending on our needs (as well as caching).
What I think is important is to understand the reason behind ADO.NET Data Services. It is not a model to replace typical Web Service or Message Bus architectures. Its not all that fast or efficient. Its purpose is to allow the creation of a simple model to allow communication across the firewall. What I mean is that it is meant for the AJAX and RIA developers. Its a way of communicating data to clients that run on the Internet.
Its important to understand that data you expose with ADO.NET Data Services is not magically more secure...in fact, since its meant for client-side consumption of data, you should not allow data to be exposed by ADO.NET Data Services that is sensitive. Remember, that consuming data in the client is not secure in itself. If you wouldn't feel safe consuming data in client-side JavaScript, don't expose it via ADO.NET Data Services.
If you haven't read Part 1 yet, you can read it here.
After spending time creating my own caches of reflection data I found the NHibernate type information to be more complete and faster. Go figure. At this point I am using the SessionFactory's GetClassMetadata and GetCollectionMetadata to return IClassMetadata and ICollectionMetadata interfaces. So far this has given me every piece of runtime information I need and means that I don't need to do any nasty (and potentially fragile) walking through the property interface of the context object. Whew...
So to refactor my GetResource and CreateResource calls. GetResource is easy. GetResource passes in the query and the full type name (though the full type name may be missing in some cases). The query it passes it must resolve to a single result. That means I can simply execute the the query and if it returns more or less than one result, return an error. Once I have the result I can just check it against the full type name (if it was passed in) and return the value.
CreateResource is a bit more complicated. The IClassMetadata allows me to call their Instantiate method to create a new instance of the class but the new instance is not initialized with any data. Most notably, the missing detail is a key. In the case of many entities, this is fine (e.g. if zero ID is a valid "new" entity). But in others the key needs to be specified. (Customer in the Northwind database is an example of this.) The problem is that we have to keep a reference to the new objects so that when ADO.NET Data Services asks us to set properties, get properties and save, we have to have the object in the Session. But we can't add it to the Session with an invalid key. What to do? My solution for now is a local cache of objects that are not ready to be saved (only really happens with newly created objects). When we are ready to save, we'll just add it to the Session then (when its either valid or we'll throw an exception because its invalid).
Now a quick mention of GetProperty and SetProperty. These are both pretty easy as the IClassMetadata includes a similar method to set and get properties. The only wrinkle is that if you try and set a key using the GetProperty/SetProperty, the keys are not in the list of properties. Because of this I just checked to see if the property is part of the key and set the key instead, if its not part of the key we can just set or get the property. Now that we have the basic part of the interface complete its down to the hard parts. But that's for the next part!
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!