skip to main | skip to sidebar
Showing posts with label ADO.NET Data Services. Show all posts
Showing posts with label ADO.NET Data Services. Show all posts

Friday, August 01, 2008

ADO.NET Data Services: Creating a custom Data Context #3: Updating

This follows part 1 where I create a custom data context and part 2 where I create a client application to talk to it.

For your custom data context to allow for updates you have to implement IUpdatable.

[画像:ado_net_im_iupdatable]

This interface has a number of cryptic methods and it wasn't at all clear at first how to write an implementation for it. I'm sure there must be some documentation somewhere but I couldn't find it. I resorted to writing trace writes in the empty methods and firing inserts and updates at my web service. You can then use Sysinternals' DebugView to watch what happens.

First of all lets try an insert:

public void AddANewTeacher()
{
 Console.WriteLine("\r\nAddANewTeacher");
 var frankyChicken = new Teacher
 {
 ID = 3,
 Name = "Franky Chicken"
 };
 service.AddObject("Teachers", frankyChicken);
 var response = service.SaveChanges();
}

We get this result:

[画像:ado_net_im_debug_insert]

So you can see that first all the existing Teachers are returned, then a new Teacher instance is created, it's properties set, SaveChanges is called and then ResolveResource. For my simple in-memory implementation I just added the new Teacher to my static list of teachers:

public object CreateResource(string containerName, string fullTypeName)
{
 Trace.WriteLine(string.Format("CreateResource('{0}', '{1}')", containerName, fullTypeName));
 var type = Type.GetType(fullTypeName);
 var resource = Activator.CreateInstance(type);
 switch (containerName)
 {
 case "Teachers":
 Root.Teachers.Add((Teacher)resource);
 break;
 case "Courses":
 Root.Courses.Add((Course)resource);
 break;
 default:
 throw new ApplicationException("Unknown containerName");
 }
 return resource;
}
public void SetValue(object targetResource, string propertyName, object propertyValue)
{
 Trace.WriteLine(string.Format("SetValue('{0}', '{1}', '{2})", targetResource, propertyName, propertyValue));
 Type type = targetResource.GetType();
 var property = type.GetProperty(propertyName);
 property.SetValue(targetResource, propertyValue, null);
}

Next let's try an update:

public void UpdateATeacher()
{
 Console.WriteLine("\r\nUpdateATeacher");
 var fredJones = service.Teachers.Where(t => t.ID == 2).Single();
 fredJones.Name = "Fred B Jones";
 service.UpdateObject(fredJones);
 service.SaveChanges();
}

We get this result:

[画像:ado_net_im_debug_update]

This time the Teacher to be updated is returned by GetResource then SetValue is called to update the Name property. Finally SaveChanges and ResolveResources are called again.

The GetResource implementation is straight from Shawn Wildermuth's LINQ to SQL implementation.

public object GetResource(IQueryable query, string fullTypeName)
{
 Trace.WriteLine(string.Format("GetResource('query', '{0}')", fullTypeName));
 // Get the first result
 var results = (IEnumerable)query;
 object returnValue = null;
 foreach (object result in results)
 {
 if (returnValue != null) break;
 returnValue = result;
 }
 // Check the Typename if needed
 if (fullTypeName != null)
 {
 if (fullTypeName != returnValue.GetType().FullName)
 {
 throw new DataServiceException("Incorrect Type Returned");
 }
 }
 // Return the resource
 return returnValue;
}

Now all I have to do is work on creating relationships between entities, possibly more on this next week.

Code is here:

http://static.mikehadlow.com/Mike.DataServices.InMemory.zip

Posted by Mike Hadlow at 5:20 pm 2 comments

ADO.NET Data Services: Creating a custom Data Context #2: The Client

In part 1 I showed how to create a simple in-memory custom data context for ADO.NET Data Services. Creating a managed client is also very simple. First we need to provide a similar domain model to our server. In this case the classes are identical except that now Teacher has a List<Course> rather than a simple array (Course[]) as it's Courses property:

using System.Collections.Generic;
namespace Mike.DataServices.Client.Model
{
 public class Teacher
 {
 public int ID { get; set; }
 public string Name { get; set; }
 public List<Course> Courses { get; set; }
 }
}

Next I wrote a class to extend DataServiceContext with properties for Teachers and Courses that are both DataServiceQuery<T>. Both DataServiceContext and DataServiceQuery<T> live in the System.Data.Services.Client assembly. You don't have to create this class, but it makes the subsequent use of the DataServiceContext simpler. You can also use use the 'Add Service Reference' menu item, but I don't like the very verbose code that this generates.

using System;
using System.Data.Services.Client;
using Mike.DataServices.Client.Model;
namespace Mike.DataServices.Client
{
 public class SchoolServiceProxy : DataServiceContext
 {
 private const string url = "http://localhost:4246/SchoolService.svc";
 public SchoolServiceProxy() : base(new Uri(url))
 {
 }
 public DataServiceQuery<Teacher> Teachers
 {
 get
 {
 return CreateQuery<Teacher>("Teachers");
 }
 }
 public DataServiceQuery<Course> Courses
 {
 get
 {
 return CreateQuery<Course>("Courses");
 }
 }
 }
}

Here's a simple console program that outputs teacher John Smith and his courses and then the complete list of courses. The nice thing is that DataServiceQuery<T> implements IQueryable<T> so we can write LINQ queries against our RESTfull service.

using System;
using System.Linq;
using System.Data.Services.Client;
using Mike.DataServices.Client.Model;
namespace Mike.DataServices.Client
{
 class Program
 {
 readonly SchoolServiceProxy service = new SchoolServiceProxy();
 static void Main(string[] args)
 {
 var program = new Program();
 program.GetJohnSmith();
 program.GetAllCourses();
 }
 public void GetJohnSmith()
 {
 Console.WriteLine("\r\nGetJohnSmith");
 var teachers = service.Teachers.Where(c => c.Name == "John Smith");
 foreach (var teacher in teachers)
 {
 Console.WriteLine("Teacher: {0}", teacher.Name);
 // N+1 issue here
 service.LoadProperty(teacher, "Courses");
 foreach (var course in teacher.Courses)
 {
 Console.WriteLine("\tCourse: {0}", course.Name);
 }
 }
 }
 public void GetAllCourses()
 {
 Console.WriteLine("\r\nGetAllCourses");
 var courses = service.Courses;
 foreach (var course in courses)
 {
 Console.WriteLine("Course: {0}", course.Name);
 }
 }
 }
}

We get this ouput:

[画像:ado_net_im_console]

Code is here:

http://static.mikehadlow.com/Mike.DataServices.InMemory.zip

Posted by Mike Hadlow at 2:50 pm 7 comments

ADO.NET Data Services: Creating a custom Data Context #1

Yesterday I wrote a quick overview of ADO.NET Data Services. We saw how it exposes a RESTfull API on top of any IQueryable<T> data source. The IQueryable<T> interface is of course at the core of any LINQ enabled data service. It's very easy to write your own custom Data Context if you already have a data source that supports IQueryable<T>. It's worth remembering that anything that provides IEnumerable<T> can be converted to IQueryable<T> by the AsQueryable() extension method, which means we can simply export an in-memory object graph in a RESTfull fashion with ADO.NET Data Services. That's what I'm going to show how to do today.

I got these techniques from an excellent MSDN Magazine article by Elisa Flasko and Mike Flasko, Expose And Consume Data in A Web Services World.

The first thing we need to do is provide a Domain Model to export. Here is an extremely simple example, two classes: Teacher and Course. Note that each entity must have an ID property that the Data Service can recognize as its primary key.

[画像:ado_net_im_model]

For a read-only service (I'll show insert, update and delete in part 2) you simply need a data context that exports the entities of the domain model as IQueryable<T> properties:

using System.Linq;
using Mike.DataServices.Model;
namespace Mike.DataServices
{
 public class SchoolDataContext
 {
 private static Teacher[] teachers;
 private static Course[] courses;
 public SchoolDataContext()
 {
 var johnSmith = new Teacher
 {
 ID = 1,
 Name = "John Smith"
 };
 var fredJones = new Teacher
 {
 ID = 2,
 Name = "Fred Jones"
 };
 var programming101 = new Course
 {
 ID = 1,
 Name = "programming 101",
 Teacher = johnSmith
 };
 var howToMakeAnything = new Course
 {
 ID = 2,
 Name = "How to make anything",
 Teacher = johnSmith
 };
 johnSmith.Courses = new[] {programming101, howToMakeAnything};
 var yourInnerFish = new Course
 {
 ID = 3,
 Name = "Your inner fish",
 Teacher = fredJones
 };
 fredJones.Courses = new[] {yourInnerFish};
 teachers = new[] {johnSmith, fredJones};
 courses = new[] {programming101, howToMakeAnything, yourInnerFish};
 }
 public IQueryable<Teacher> Teachers
 {
 get { return teachers.AsQueryable(); }
 }
 public IQueryable<Course> Courses
 {
 get { return courses.AsQueryable(); }
 }
 }
}

Note that we're building our object graph in the constructor in this demo. In a realistic implementation you'd probably have your application create its model somewhere else.

Now we simply have to set the type parameter of the DataService to our data context (DataService<SchoolDataContext>):

using System.Data.Services;
namespace Mike.DataServices
{
 [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
 public class SchoolService : DataService<SchoolDataContext>
 {
 public static void InitializeService(IDataServiceConfiguration config)
 {
 config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
 }
 }
}

And we can query our model via our RESTfull API:

[画像:ado_net_im_ie_root]

And here's all the courses that the first teacher teaches:

[画像:ado_net_im_ie_teachers_courses]

Code is here:

http://static.mikehadlow.com/Mike.DataServices.InMemory.zip

Posted by Mike Hadlow at 2:29 pm 0 comments

Thursday, July 31, 2008

Playing with ADO.NET Data Services

I've recently been asked to architect and build my first major Silverlight based application. The requirement is for an on-line accommodation booking service for a large multi-site educational institution. As a fully paid up member of the ORM mafia I really wanted to have the same persistence ignorance on the client that I enjoy on the server. The only way I could see to achieve this was building some kind of generic web service front end on top of my repository model along with a client side proxy that behaved in much the same way. It looked like a lot of work.

Enter ADO.NET Data Services. The first thing I'll say about it, is that the name is really crap. I can't see how this has anything remotely to do with ADO.NET. Fundamentally it's a combination of a REST style service over anything that provides an IQueryable implementation, plus client side proxies that expose an IQueryable API. Here's a diagram:

[画像:ado_net_architecture]

The Data Context can be any class that has a set of properties that return IQueryable<T> and that implements System.Data.Services.IUpdatable and System.Data.Services.IExpandProvider. The IQueryable<T> properties give the entities that are exposed by the REST API, IUpdatable supports (as you'd expect) posting updates back to the Data Context and IExpandProvider supports eagerly loading your object graph. The REST API has lazy load semantics by default, with all entity references being expressed as REST URLs.

It's very nicely thought out and easy to use. You simply create a new DataService<T> where T is your Data Context and the service auto-magically exposes your data as a RESTfull web service. I've been playing with the Northwind example. You just create a new Web Application in Visual Studio, add a "LINQ to SQL classes" item to your project and drag all the Northwind tables onto the LINQ-to-SQL designer. Next add an "ADO.NET Data Service" to the project then it's just a question of entering the name of the NorthwindDataContext class as the type parameter of the DataService:

[画像:ado_net_DataService]

Note I've also added a ServiceBehaviour to show exception details in faults. Now when I hit F5 I get this error:

[画像:ado_net_ds_request_error]

The way that ADO.NET Data Services works an entity's primary key is pretty simplistic. It just looks for properties that end with ID and gets confused by CustomerCustomerDemos. Shawn Wildermuth explains about this issue here. So I'll just remove everything from the designer except for tables with an obvious Id, and now I get this:

[画像:ado_net_toplevel]

It's easy to explore the model by playing with the URL...

Currently only the much loved Entity Framework and LINQ to SQL provide a Data Context that you can use with this. David Hayden shows how to set up a LINQ to SQL project here. However, Shawn Wildermuth has been doing some great work exposing LINQ to NHibernate as a Data Service friendly Data Context. You can read about it here, here, here and here. His NHibernateContext is now part of the LINQ to NHibernate project. My project is using NHibernate as it's ORM, so I'll posting more about this soon.

You can download my sample using LINQ to SQL and Entity Framework here:

http://static.mikehadlow.com/Mike.DataServices.zip

Subscribe to: Posts (Atom)
 

AltStyle によって変換されたページ (->オリジナル) /