(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .
        Login with Facebook

WCF Data Services Advanced Topics: SubSonic Data Context

(3 votes)
Alexey Zakharov
>
Alexey Zakharov
Joined Sep 25, 2008
Articles:   8
Comments:   5
More Articles
2 comments   /   posted on Jul 24, 2009
Tags:   wcf-data-services , subsonic , alexey-zakharov
Categories:   Data Access , General

This article is compatible with the latest version of Silverlight.


1. Introduction

In my previous article about WCF Data Services I showed you how to get a full control under the proxy generation process using T4 templates. But to tell you the truth, I don’t like code generation in all of its forms. Today I am going to show you how to use WCF Data Services without code generation at all.

Download source code

2. Content

2.1. Solution overview

To achieve this I will use SubSonic ORM which supports pure POCO objects. Instead of using code generation, I will share Subsonic POCO objects across NET and Silverlight runtimes using Microsoft Project Linker. To expose subsonic to Silverlight I will create custom WCF Data Services context. On the client side (Silverlight) I will also need to create WCF Data Services context class, but it has nothing in common with datacontext on the sever side.

2.2. Subsonic overview

Subsonic is a lightweight ORM, which allows transparent mapping of POCO object to Database tables. By transparent mapping I mean that there is no need to create any kind of mapping configuration, because database is altered on the fly if any changes in POCO classes have happened.

To use it you just need to create SimpleRepository class with supplied connection string inside its constructor and to start querying your POCO objects using SimpleRepository generic methods.

 var repo=newSimpleRepository(SimpleRepositoryOptions.RunMigrations);
  
 //seeif a record exists
 bool exists=repo.Exists<Post>(x=>x.Title=="My Title");
  
 //useIQueryable
 var qry=from p in repo.All<Post>()
         where p.Title=="MyTitle"
         select p;
   
 //geta post
 varpost=repo.Single<Post>(x=>x.Title=="My Title");
 varpost=repo.Single<Post>(key);
  
 //alot of posts
 varposts=repo.Find<Post>(x=>x.Title.StartsWith("M"));
  
 //aPagedList of posts - using 10 per page
 varposts=repo.GetPaged<Post>(0,10);
 //sort by title
 varposts=repo.GetPaged<Post>("Title",0,10);
  
 //adda post
 var newKey=repo.Add(post);
  
 //adda lot of posts - using a transaction
 IEnumerable<Post>posts=GetABunchOfNewPosts();
 repo.Add(posts);
  
 //update a post
 repo.Update(post);
  
 //update a bunch of posts in a transaction
 IEnumerable<Post>posts=GetABunchOfNewPosts();
 repo.Update(posts);
  
 //delete a post
 repo.Delete<Post>(key);
   
 //delete a lot of posts
 repo. DeleteMany<Post>(x=>x.Title.StartsWith("M"));
  
 //delete posts using a transaction
 IEnumerable<Post>posts=GetABunchOfNewPosts();
 repo.DeleteMany(posts);

Due to some technical reasons I will not use this class to create WCF Data Services context and will use lower level API, which will allow the creation of batch queries.

The simple repository pattern is not the only way by which you can use the Subsonic. If you want to know more about Subsonic please refer to its documentation.

2.3 Creating Subsonic WCF Data Services context

2.3.1.  Expose queryable collection

As you may already know, WCF Data Service exposes its object through IQueryable generic properties defined in data context class. So the first step is to create generic method which will return IQueryable<T> for specified POCO class type.

You can make it with SimpleRepository All<T> method, but as I said before we won’t use it now. Instead of this we will use Query<T> object, which needs Subsonic IDataProvider class. Its using is also straightforward, but in this case we need to manually implement migrate functionality, which will regenerate database tables if POCO classes have been changed. The implementation of subsonic database migration function is out of the scope of this article, so its implementation was simply borrowed from the SubSonic SimpleRepository source code.

 public IQueryable<T> GetAll<T>() where T : class, new()
 {
     if (this.options.Contains(SimpleRepositoryOptions.RunMigrations))
     {
         this.Migrate<T>();
     }
  
     var qry = new Query<T>(this.dataProvider);
     return qry;
 }

Now you can easily share our entity sets with this method:

 public IQueryable<Product> ProductSet
 {
     get
     {
         return this.GetAll<Product>();
     }
 }

That is all we need to do on server side and now it's time to use the created service with Silverlight.

Surely we can generate proxy by adding a reference to our data service. But I won’t do it =)

The first step is to create a NET class library linked to a Silverlight library with the same name using project linker. There is a very nice example here.

Now POCO classes which you will create in NET assembly will be synchronized with the Silverlight assembly.

To query this classes you should use CreateQuery<T> method of DataServiceContext. But it also requires entity set name. To solve this issue I make an assumption that all shared entity sets will be named according to this rule: POCO Type + “Set”

 public DataServiceQuery<T>CreateQuery<T>() where T :class, new()
 {
     return this.CreateQuery<T>(string.Concat(typeof(T).Name, "Set"));
 }

And here is a query example, which takes the first two products ordered by name:

 DataServiceQuery<Product>query = (DataServiceQuery<Product>)dataContext.CreateQuery<Product>().OrderBy(p=> p.Name).Take(2);
 query.BeginExecute(ar =>
 {
     List<Product> result =query.EndExecute(ar).ToList();
 },
 null);

2.3.2 Implementing IUpdateble interface to enable delete, add and update operations.

Let's start with Delete operation. To enable delete you should track delete request in DeleteResource method and then perform delete during SaveChanges method of IUpdateble.

To track any actions performed with object, I have created internal dictionary, which key is object and which value is change action.

 private Dictionary<object, ChangeType> changes = new Dictionary<object, ChangeType>();
  
 public enum ChangeType
 {
     Added,
     Deleted,
     Updated,
     NotChanged
 }

Adding delete request to change list.

 public void DeleteResource(object targetResource)
 {
     this.changes[targetResource] = ChangeType.Deleted;
 }

There is no update resource method, so to track object update you catch the first invocation of the SetValue method.

 public void SetValue(object targetResource, string propertyName, object propertyValue)
 {
     PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
     if (pi == null)
     {
         throw new Exception("Can't find property");
     }
  
     pi.SetValue(targetResource, propertyValue, null);
   
     if (this.changes[targetResource] == ChangeType.NotChanged)
     {
          this.changes[targetResource] = ChangeType.Updated;
     }
 }

For enabling add operation we should implement CreateResource method.

 public object CreateResource(string containerName, string fullTypeName)
 {
     Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
  
     Type type = null;
  
     foreach (Assembly assembly in assemblies)
     {
         type = assembly.GetType(fullTypeName);
          if (type != null)
          {
              break;
          }
     }
   
      object resource = Activator.CreateInstance(type);
   
      this.changes.Add(resource, ChangeType.Added);
   
      return resource;
  }

All performed changes should be executed at once during the save changes method, that is why I’ve started using low level API of Subsonic instead of SimpleRepository.

For each operation I will create Subsonic ISqlQuery with the help of the following Subsonic Database generic extension methods: ToInsertQuery<T>, ToUpdateQuery<T>, ToDeletQuery<T>. You cannot use them directly, because we received all changed objects as System.Object. That is why I have created a private method, which invokes them using reflection:

 private ISqlQuery GetDeleteQuery(object item)
 {
     return this.GetQuery("ToDeleteQuery", item);
 }
  
 private ISqlQuery GetInsertQuery(object item)
 {
     return this.GetQuery("ToInsertQuery", item);
 }
   
 private ISqlQuery GetUpdateQuery(object item)
 {
     return this.GetQuery("ToUpdateQuery", item);
 }
  
 private ISqlQuery GetQuery(string methodName, object item)
 {
     if (this.options.Contains(SimpleRepositoryOptions.RunMigrations))
     {
        this.Migrate(item.GetType());
     }
  
     MethodInfo methodInfo = typeof(Database).GetMethod(methodName);
     var sqlQuery = (ISqlQuery)methodInfo.MakeGenericMethod(item.GetType()).Invoke(null, new[] { item, this.dataProvider });
  
     return sqlQuery;
 }

All created queries will be placed inside Subsonic BatchQuery transaction and will be executed at once:

 public void SaveChanges()
 {
     var batchQuery = new BatchQuery(this.dataProvider);
  
     foreach (var change in this.changes)
     {
         ISqlQuery query = null;
         switch (change.Value)
         {
              case ChangeType.Deleted:
                  query = this.GetDeleteQuery(change.Key);
                  break;
              case ChangeType.Added:
                  query = this.GetInsertQuery(change.Key);
                  break;
              case ChangeType.Updated:
                  query = this.GetUpdateQuery(change.Key);
                  break;
          }
   
          batchQuery.QueueForTransaction(query);
      }
   
      batchQuery.ExecuteTransaction();
   
      this.changes.Clear();
  }

On the Silverlight side we should also create custom AddObject method which will automatically place entities in the <POCO type + “Set”> entity set:

 public void AddObject(object entity)
 {
     this.AddObject(string.Concat(entity.GetType().Name, "Set"), entity);
 }

And here are some examples of CRUD operations:

Insert new product with name “Banana”.

 dataContext.AddObject(new Product()
     {
         CategoryId = 1,
         Name = "Banana"
     });
  
 dataContext.BeginSaveChanges(SaveChangesOptions.Batch,
     ar =>
     {
          dataContext.EndSaveChanges(ar);
     },
     null);

Delete all products with name “Apple”.

 DataServiceQuery<Product> query = (DataServiceQuery<Product>)dataContext.CreateQuery<Product>().Where(p => p.Name == "Apple");
 query.BeginExecute(ar =>
 {
     IEnumerable<Product> result = query.EndExecute(ar);
     foreach (var product in result)
     {
         dataContext.DeleteObject(product);
     }
     dataContext.BeginSaveChanges(SaveChangesOptions.Batch, ar2 =>
      {
          dataContext.EndSaveChanges(ar2);
      },
      null);
  },
  null);

Change the category of every Product with name “Apple” to 2.

 DataServiceQuery<Product> query = (DataServiceQuery<Product>)dataContext.CreateQuery<Product>().Where(p => p.Name == "Apple");
 query.BeginExecute(ar =>
 {
     IEnumerable<Product> result = query.EndExecute(ar);
     foreach (var product in result)
     {
         product.CategoryId = 2;
         dataContext.UpdateObject(product);
     }
   
     dataContext.BeginSaveChanges(SaveChangesOptions.Batch, ar2 =>
     {
         dataContext.EndSaveChanges(ar2);
     },
     null);
 },
 null);

Subsonic ORM doesn’t support relations. That is why you cannot use Expand operation to load referenced entities and don’t need to implement RemoveReferenceFromCollection, AddReferenceToCollection, SetReference methods of IUpdatable.

And one more thing - the current version of SubSonic have a bug in the already mentioned ToUpdateQuery<T> method. That is why you should use my Subsonic.Core.dll instead of the official 3.0.0.3 release. But don’t worry, Rob Conery (the creator of SubSonic) will include it in the next release.

3. Summary

I hope you have enjoyed the article. I think that subsonic ORM will be ideal for small silverlight projects, which don't require complex entity relations. The same advantages have the Entity Framework V2 POCO entities, but it is not released yet.

I’m also thinking of another way of exposing Subsonic to Silverlight. In that approach I want to transform linq expression into the SQL query on the Silverlight side and then execute it on the server. It should significantly reduce server charges related to expression tree –> sql query transformations. But it requires the creation of lightweight version of Subsonic.Core, not referenced to .NET database classes.

If you have any questions or you have found a bug, please write a comment or e-mail me through my blog contact form.

4. Links

Here are some useful links, which are a good start to learn Subsonic, Microsoft Project Linker.

1. Subsonic documentation

2. MSDN: Project Linker: Synchronization Tool

3. Early prototype of subsonic data service context


Subscribe

Comments

  • MisterFantastic

    RE: ADO.NET Data Services Advanced Topics: SubSonic Data Context


    posted by MisterFantastic on Jul 27, 2009 15:02

    Hi There,

    Your areticle is very nice. But we don know how much subsonic supports for heavier database.

    Can you please give some tips on that?

    Thanks,

    Thani

  • -_-

    RE: ADO.NET Data Services Advanced Topics: SubSonic Data Context


    posted by john marston on Nov 12, 2010 21:11
    thanks for the explanation about how data services are needed fro subsonic data context. I am starting up a couple of programs that was following this brakes topic. I would really appreciated if you could help me out I just need to figure out that problem thanks.

Add Comment

Login to comment:
  *      *       
Login with Facebook