Recommended

Skip Navigation LinksHome / Articles / View Article

ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates

+ Add to SilverlightShow Favorites
7 comments   /   posted by Alexey Zakharov on Feb 26, 2009
(74 votes)

1. Introduction

This is the first article of my series about deep dive into  ADO.NET Data Services. In this article I'm going to show you how to implement your own ADO.NET Data Services proxy with T4 templates.

Source code and database backup

2. Content

2.1 Problem

ADO.NET Data  Services is a very powerful toy, but as many other cool Microsoft technologies it needs some workarounds to become usable in the real world applications.

Most of the problems are connected with the auto generated proxy which is created after the addition of a service reference. Here are the most popular:

1. Data Contract objects do not support INotifyPropertyChanged which is needed for two way data binding.There are partial methods for detecting changes to specific properties which makes implementing the INotifyPropertyChanged interface possible, but not quick.

2. Add some common interface for all generated entites. (e.g. IIdentifyable)

3. Add custom attributes to properties of generated entities.

4. And many many others, which is caused by absence of control over the code generation process.

Uncontrolled code generation is a problem of all Microsoft ORM products, that is why community have created similar projects for LINQ to SQL (Damien Guard) and Entity Framework (Danny Simmons, ADO.NET team).

2.2 Solution

Solution to this problem is using of T4 templates for generation of ADO.NET data service proxy. In this case you will have absolute control over code generation process, so such task as implementing INotifyPropertyChanged interface will become trivial.

Mostly I was inspired by Damien Guard project T4 template for generating LINQ to SQL Data Context. It was my first experience with T4 templates, that's why template helpers code may be a bit dirty.

2.3 How it works

To generate ADO.NET data service entities and service context class we need some metadata, which describes them. You can find metadata file (services.edmx) in ado.net data service reference. But this is not a good place to look for it =) The best way to get metadata file is to add the keyword $metadata to the end of the data service url (http://MySite.com/MyDataService.svc/$metadata).

The language used to describe ADO.NET Data Services is the conceptual schema definition language (CSDL) defined by the ADO.NET Entity Data Model.

With the help of Linq to XML I will parse this metadata and wrap it into single object which will be used inside T4 template.

2.4  Implementation

First of all copy MetadataHelper.tt file to your project. It includes all metadata parsing logic and provides you single object with data needed for proxy generation. I'm not going to describe its implementation in the article, but its source is open so you are free to explore and modify it.

Next step is to add your T4 template, which will generate entities and data service context. There is no visual studio item template for T4 template, that is why you should add text file and change its extension to .tt. After changing of extension Visual Studio will automatically recognize it as T4 template and will add code behind .cs file where generated entities and data service context will appear.

After you have successfully added T4 template item you should perform little setup:

1. Include MetadataHelper.tt

2. Initialize options anonymous object with metadataUri and namespace of generated proxy. This is the only piece of information that will be normally changed while reusing of ado.net data services proxy t4 template.

3. Create instance of Data class by providing metadata Uri to its constructor. Created instance of Data class that has all information which we need to generate entities and data service context object.

   1: <#@ template language="C#v3.5" hostspecific="True" debug="True"  #>
   2: <#@ include file="MetadataHelper.tt" #>
   3: <# 
   4: var options = new {
   5:     MetadataUri = "http://localhost:51002/DataService.svc/$metadata",
   6:     Namespace = "DataAccess"
   7: };
   8:  
   9: var data = new Data(options.MetadataUri); 
  10: #> 

Now we are ready to  create the entities template. Let us assume that we need to implement INotifyPropertyChanged for each property.

Information about all entities provided by Data Service is held in Data class Entities property. So first of all we create cycle through all entities.

   1: <# 
   2: foreach (var entity in data.Entities)
   3: {
   4: #>
   5:     Entity template is here!
   6: <#
   7: }
   8: #>

Each entity has name and collections of properties (info about properties, which has standard simple type such as string, int, double, DateTime) and navigation properties (info about properties which represent relationships between entities).

Entity template:

   1: public class <#= entity.Name #> : INotifyPropertyChanged
   2: {
   3: <# 
   4: foreach (var property in entity.Properties)
   5: {
   6: #>
   7:     Property template is here!
   8: <#
   9: }
  10: foreach (var property in entity.NavigationProperties)
  11: {
  12: #>
  13:     Navigation property template is here!
  14: <#
  15: }   
  16: #>
  17:     public event PropertyChangedEventHandler PropertyChanged;
  18:  
  19:     private void OnPropertyChanged(string propertyName)
  20:     {
  21:         PropertyChangedEventHandler handler = PropertyChanged;
  22:         if (handler != null)
  23:         {
  24:             handler(this, new PropertyChangedEventArgs(propertyName));
  25:         }
  26:     }
  27: }

Each entity property has name, type and flag that shows if property is nullable.

Property template:

   1: private <#= property.Type #><#= property.IsNullable ? "?" : ""  #> <#= property.Name.ToLower() #>;
   2: public <#= property.Type #><#= property.IsNullable ? "?" : ""  #> <#= property.Name #>
   3: {
   4:     get { return <#= property.Name.ToLower() #>; }
   5:     set
   6:     {
   7:         if (<#= property.Name.ToLower() #> != value)
   8:         {
   9:             <#= property.Name.ToLower() #> = value;
  10:             OnPropertyChanged("<#= property.Name #>");
  11:         }
  12:     }            
  13: }

Each entity navigation property has name, type and flag that shows if property is collection.

Navigation property template:

   1: <#
   2: if (property.IsCollection) {  
   3: #>
   4: public Collection<<#= property.Type #>> <#= property.Name #> {get;set;}
   5: <#  
   6: } else { 
   7: #>
   8: public <#= property.Type #> <#= property.Name #> {get;set;}
   9: <# 
  10: }
  11: #>

Whole entity generation template will look like this.

   1: namespace <#= options.Namespace #>
   2: { 
   3: <# 
   4: foreach (var entity in data.Entities)
   5: {
   6: #>
   7:     public class <#= entity.Name #> : INotifyPropertyChanged
   8:     {
   9: <# 
  10:     foreach (var property in entity.Properties)
  11:     {
  12: #>
  13:         private <#= property.Type #><#= property.IsNullable ? "?" : ""  #> <#= property.Name.ToLower() #>;
  14:         public <#= property.Type #><#= property.IsNullable ? "?" : ""  #> <#= property.Name #>
  15:         {
  16:             get { return <#= property.Name.ToLower() #>; }
  17:             set
  18:             {
  19:                 if (<#= property.Name.ToLower() #> != value)
  20:                 {
  21:                     <#= property.Name.ToLower() #> = value;
  22:                     OnPropertyChanged("<#= property.Name #>");
  23:                 }
  24:             }            
  25:         }
  26: <#
  27: }
  28:     foreach (var property in entity.NavigationProperties)
  29:     {
  30:         if (property.IsCollection) {  
  31: #>
  32:         public Collection<<#= property.Type #>> <#= property.Name #> {get;set;}
  33: <#  
  34:         } else { 
  35: #>
  36:         public <#= property.Type #> <#= property.Name #> {get;set;}
  37: <# 
  38:         } 
  39:     }
  40: #>
  41:         public event PropertyChangedEventHandler PropertyChanged;
  42:  
  43:         private void OnPropertyChanged(string propertyName)
  44:         {
  45:             PropertyChangedEventHandler handler = PropertyChanged;
  46:             if (handler != null)
  47:             {
  48:                 handler(this, new PropertyChangedEventArgs(propertyName));
  49:             }
  50:         }
  51:     }
  52: <#
  53: }
  54: #>

And here is an example of generated entities:

   1: public class Client : INotifyPropertyChanged
   2: {
   3:     private int id;
   4:     public int ID
   5:     {
   6:         get { return id; }
   7:         set
   8:         {
   9:             if (id != value)
  10:             {
  11:                 id = value;
  12:                 OnPropertyChanged("ID");
  13:             }
  14:         }            
  15:     }
  16:     private string name;
  17:     public string Name
  18:     {
  19:         get { return name; }
  20:         set
  21:         {
  22:             if (name != value)
  23:             {
  24:                 name = value;
  25:                 OnPropertyChanged("Name");
  26:             }
  27:         }            
  28:     }
  29:     private string address;
  30:     public string Address
  31:     {
  32:         get { return address; }
  33:         set
  34:         {
  35:             if (address != value)
  36:             {
  37:                 address = value;
  38:                 OnPropertyChanged("Address");
  39:             }
  40:         }            
  41:     }
  42:     public Collection<Order> Orders {get;set;}
  43:     public event PropertyChangedEventHandler PropertyChanged;
  44:  
  45:     private void OnPropertyChanged(string propertyName)
  46:     {
  47:         PropertyChangedEventHandler handler = PropertyChanged;
  48:         if (handler != null)
  49:         {
  50:             handler(this, new PropertyChangedEventArgs(propertyName));
  51:         }
  52:     }
  53: }
  54: public class Order : INotifyPropertyChanged
  55: {
  56:     private int id;
  57:     public int ID
  58:     {
  59:         get { return id; }
  60:         set
  61:         {
  62:             if (id != value)
  63:             {
  64:                 id = value;
  65:                 OnPropertyChanged("ID");
  66:             }
  67:         }            
  68:     }
  69:     private int amount;
  70:     public int Amount
  71:     {
  72:         get { return amount; }
  73:         set
  74:         {
  75:             if (amount != value)
  76:             {
  77:                 amount = value;
  78:                 OnPropertyChanged("Amount");
  79:             }
  80:         }            
  81:     }
  82:     public Client Client {get;set;}
  83:     public Product Product {get;set;}
  84:     public event PropertyChangedEventHandler PropertyChanged;
  85:  
  86:     private void OnPropertyChanged(string propertyName)
  87:     {
  88:         PropertyChangedEventHandler handler = PropertyChanged;
  89:         if (handler != null)
  90:         {
  91:             handler(this, new PropertyChangedEventArgs(propertyName));
  92:         }
  93:     }
  94: }
  95: public class Product : INotifyPropertyChanged
  96: {
  97:     private int id;
  98:     public int ID
  99:     {
 100:         get { return id; }
 101:         set
 102:         {
 103:             if (id != value)
 104:             {
 105:                 id = value;
 106:                 OnPropertyChanged("ID");
 107:             }
 108:         }            
 109:     }
 110:     private string name;
 111:     public string Name
 112:     {
 113:         get { return name; }
 114:         set
 115:         {
 116:             if (name != value)
 117:             {
 118:                 name = value;
 119:                 OnPropertyChanged("Name");
 120:             }
 121:         }            
 122:     }
 123:     private int price;
 124:     public int Price
 125:     {
 126:         get { return price; }
 127:         set
 128:         {
 129:             if (price != value)
 130:             {
 131:                 price = value;
 132:                 OnPropertyChanged("Price");
 133:             }
 134:         }            
 135:     }
 136:     private double? weight;
 137:     public double? Weight
 138:     {
 139:         get { return weight; }
 140:         set
 141:         {
 142:             if (weight != value)
 143:             {
 144:                 weight = value;
 145:                 OnPropertyChanged("Weight");
 146:             }
 147:         }            
 148:     }
 149:     public Collection<Order> Orders {get;set;}
 150:     public event PropertyChangedEventHandler PropertyChanged;
 151:  
 152:     private void OnPropertyChanged(string propertyName)
 153:     {
 154:         PropertyChangedEventHandler handler = PropertyChanged;
 155:         if (handler != null)
 156:         {
 157:             handler(this, new PropertyChangedEventArgs(propertyName));
 158:         }
 159:     }
 160: }

Now we should generate data service context. To create your own data service context you should inherit from DataServiceContext class and implement to methods which resolve difference between client and server entity namespaces. To generate these methods Data class provides list of server namespaces (SeverNamspaces property).

Also we need to generate a number of properties for each entity set of data service. To generate these properties Data class provide dictionary, which Key is name of entity set and Value is entity type (EntitySets property).

DataContext template:

   1:  
   2:     public class AstoriaServiceContext : DataServiceContext
   3:     {
   4: <# 
   5: foreach (var es in data.EntitySets)
   6: {
   7: #>        
   8:         public DataServiceQuery<<#= es.Value #>> <#= es.Key #>
   9:         {
  10:             get
  11:             {
  12:                 return CreateQuery<<#= es.Value #>>("<#= es.Key #>");
  13:             }
  14:         }
  15: <#
  16: }
  17: #>
  18:         public AstoriaServiceContext(Uri serviceRoot) : base(serviceRoot)
  19:         {
  20:             ResolveName = ResolveNameFromType;
  21:             ResolveType = ResolveTypeFromName;
  22:         }
  23:  
  24:         protected Type ResolveTypeFromName(string typeName)
  25:         {
  26: <# 
  27: foreach (var ns in data.ServerNamespaces)
  28: {
  29: #>        
  30:             if (typeName.StartsWith("<#= ns #>", StringComparison.Ordinal))
  31:             {
  32:                 return this.GetType().Assembly.GetType(string.Concat("<#= options.Namespace #>", typeName.Substring(<#= ns.Length #>)), true);
  33:             }
  34: <#
  35: }
  36: #>                       
  37:             return null;
  38:         }
  39:  
  40:         protected string ResolveNameFromType(Type clientType)
  41:         {
  42: <# 
  43: foreach (var ns in data.ServerNamespaces)
  44: {
  45: #>            
  46:             if (clientType.Namespace.Equals("<#= options.Namespace #>", StringComparison.Ordinal))
  47:             {
  48:                 return string.Format("{0}.{1}","<#= ns #>", clientType.Name);
  49:             }
  50: <#
  51: }
  52: #>            
  53:             return null;
  54:         }
  55:     }

 

3. Summary

I think T4 template technique described in this article has a great potential, because it give us absolute control over code generation process. Unfortunately, T4 template has rather poor integration with visual studio that's why debugging of it may be annoying.

If you find this T4 ado.net data services proxy useful, please write a comment or send me email with your suggestions and thoughts.

4. FAQ

What are T4 templates?

T4 templates is Visual Studio built-in code generator. However, it's not deep-deep built in, because there's no item templates in File | New Item and there's no intellisense or syntax highlighting. But guys from Clarius Consulting created a product T4 Editor (T4 Editor Community Edition), which supports coloring and intellisense (C# code coloring and intellisense supported only in commercial version).

5. Links

1. Data Service Metadata (ADO.NET Data Services Framework)

2. EDM Specifications - specifications you need to know to perform ado.net data services metadata parsing.

3. http://www.olegsych.com - great resource about T4 templates.

4. T4 template for generating LINQ to SQL Data Context (by Damien Guard)

5. T4 template for generating Entity Framework classes (by Danny Simmons, ADO.NET team)

PS

Current version of MetadataHelper.tt is not well tested, so use it on your own risk.

Share


Comments

Comments RSS RSS
  • RE: ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates  

    posted by krokodilov on Feb 26, 2009 09:55

    Useful article! It's exactly what I need!

  • RE: ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates  

    posted by Kalle on 16:56

    Heh, *hugs and kisses*...

    Finishing a presentation ADO.NET DS with T4 customed LINQ to SQL model for fine-grained control over user-specific data; was just looking if someone had made the proxy creation T4 and here I stumbled.

    Excellent timing ;-) I was testing out serve ADS => ADS to be able to filter the data from the proxy and serve it on. Will spend some time with this later on to see if the user-specific/role specific  (field) data filtering is achievable while passing queries through ADS...

     

  • RE: ADO.NET DS Custom Classes with change tracking and conflict resolution  

    posted by Tim on Apr 29, 2009 07:18
    Hi there,

    in case you look also for change tracking and conflict resolution i recommend the following how-do-i video:

    http://t4-editor.tangible-engineering.com/How-Do-I-With-T4-Editor-Text-Templates.htm 

    Regards

     

    Tim


  • RE: ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates  

    posted by crystal on Sep 03, 2009 01:54

    Hi

     

    it failed at private static readonly Dictionary<string, string> edmTypes = new Dictionary<string, string>

    Error 1 Compiling transformation: A new expression requires () or [] after type c:\Documents and Settings\cxue\projects\APODataService\TestSilverlightApplication\Service References\MetadataHelper.tt 26 65 TestSilverlightApplication

    Can you give any help?

     

    Thanks,

     

    Crystal

     

     

    {

  • RE: ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates  

    posted by crystal on Sep 03, 2009 02:13

    Hi

     

    it failed at private static readonly Dictionary<string, string> edmTypes = new Dictionary<string, string>

    Error 1 Compiling transformation: A new expression requires () or [] after type c:\Documents and Settings\cxue\projects\APODataService\TestSilverlightApplication\Service References\MetadataHelper.tt 26 65 TestSilverlightApplication

    Can you give any help?

     

    Thanks,

     

    Crystal

     

     

    {

  • RE: ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates  

    posted by Aquilax on Sep 22, 2009 10:42
    @Crystal

     From the Metadata.tt file delete the generated cs files or in the file properties windows under advanced section clear the CustomTool property (it should contains the name "TextTemplatingFileGenerator").

  • RE: ADO.NET Data Services Advanced Topics - Custom proxy based on T4 templates  

    posted by Rob on Nov 01, 2009 04:50
    Btw there's a typo in the MetaDataHelper which causes an exception if you have a Boolean type in the metadata.  Line 28 should be Edm.Boolean and not Edm.Booleand

Add Comment

 
 

   
  
  
   
Please add 2 and 7 and type the answer here:

Help us make SilverlightShow even better and win a free t-shirt. Whether you'd like to suggest a change in the structure, content organization, section layout or any other aspect of SilverlightShow appearance - we'd love to hear from you! Need a material (article, tutorial, or other) on a specific topic? Let us know and SilverlightShow content authors will work to have that prepared for you. (hide this)