Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XSLT

Generate Your Own Proxy for ADO.NET Data Services on Client Side

4.75/5 (3 votes)
19 Sep 2008CPOL4 min read 1   176  
The proxy generated by the ADO.NET Data

Introduction

First of all, please accept my apologies for my poor English. :)

This article talks about creating entities proxy classes for your own need.

Problem

The proxy class generated from a ADO.NET Data Service is simple and only helps for standard CRUD scenarios. But as soon as you want to make something complex or special, you will find some restrictions. If you need some features that do not exist inside this proxy, you can write them by adding a new partial class to your project or use reflection. You can do this easily to add some features such as a static method or a simple utility method. But if you have to make a feature shared between all your entities or something that depends on your entities semantics, you won't be able to use this method.

You need to use an automatic generation of a proxy class that contains the features you need. That's the thing we will make here: create a new set of partial classes to complete existing partial class of the proxy with our needs or create the whole set of proxy classes.

Our Answer

In order to generate our own class, we will have to read metadatas from the web data service.

The generation will use the msxsl.exe tool from Microsoft: it builds a result from an XML and an XSLT file. The "result" will be our proxy classes, the "XML" will be the $metadata result of the web data service and the "XSLT" the job we have to make here.

Create the Database

This is a very simple step. Microsoft offers some T-Sql scripts to generate a sample database. You can find it here. Install the script to create your own database.

The Solution

We are not here to learn how to create those projects. So just download the zip given with this article to get the entire solution. You will find in it:

  • A web project with an ADO.NET data service
  • An entities project with an edmx
  • A client project

gd01.png

In the solution directory, I added a Tools directory where you can find the msxsl.exe tools:

gd02.png

Before launching the service, we have to make a link between the edmx and the database.

Inside the web.config file, change the end of the connection string to make a connection to the school database:

XML
<add name="SchoolEntities" 
connectionString="metadata=res://*/SchoolModel.csdl|res://*/
			SchoolModel.ssdl|res://*/SchoolModel.msl;
provider=System.Data.SqlClient;provider 
	connection string=&quot;Data Source=localhost\sqlexpress;
Initial Catalog=School;Integrated Security=True;MultipleActiveResultSets=True&quot;"
providerName="System.Data.EntityClient" />

Start the web data service by right clicking on the WebDataService.svc file on the solution explorer and selecting "Open in Browser". You will see something like this:

gd03.png

Our service is ready.

Generation Needs metadatas (XML Part)

We need an XML that contains all information about the entities of the school model and their relation. Just add the keyword $metadata to the end of the web data service URL like this:

gd04.png

You now see a complete description of the school model:

XML
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<edmx:Edmx>
<edmx:DataServices>
<Schema>
<EntityType Name="Course">
<Key>
<PropertyRef Name="CourseID" />
</Key>
<Property Name="CourseID" Type="Edm.Int32" Nullable="false" />
<Property Name="Title" Type="Edm.String" Nullable="false" 
	MaxLength="100" Unicode="true" FixedLength="false" />
<Property Name="Credits" Type="Edm.Int32" Nullable="false" />
<NavigationProperty Name="Department" 
	Relationship="SchoolModel.FK_Course_Department" 
	FromRole="Course" ToRole="Department" />
<NavigationProperty Name="CourseGrade" 
	Relationship="SchoolModel.FK_CourseGrade_Course" 
	FromRole="Course" ToRole="CourseGrade" />
<NavigationProperty Name="OnlineCourse" 
	Relationship="SchoolModel.FK_OnlineCourse_Course" 
	FromRole="Course" ToRole="OnlineCourse" />
<NavigationProperty Name="OnsiteCourse" 
	Relationship="SchoolModel.FK_OnsiteCourse_Course" 
	FromRole="Course" ToRole="OnsiteCourse" />
<NavigationProperty Name="Person" 
	Relationship="SchoolModel.CourseInstructor" 
	FromRole="Course" ToRole="Person" />
</EntityType>
<EntityType Name="CourseGrade">
<Key>
<PropertyRef Name="EnrollmentID" />
</Key>
<Property Name="EnrollmentID" Type="Edm.Int32" Nullable="false" />
<Property Name="Grade" Type="Edm.Decimal" 
`Nullable="true" Precision="3" Scale="2" />
<NavigationProperty Name="Course" 
	Relationship="SchoolModel.FK_CourseGrade_Course" 
	FromRole="CourseGrade" ToRole="Course" />
<NavigationProperty Name="Person" 
	Relationship="SchoolModel.FK_CourseGrade_Student" 
	FromRole="CourseGrade" ToRole="Person" />
</EntityType>
<EntityType Name="Department">
<Key>
<PropertyRef Name="DepartmentID" />
</Key>
<Property Name="DepartmentID" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" Nullable="false" 
	MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="Budget" Type="Edm.Decimal" 
	Nullable="false" Precision="19" Scale="4" />
<Property Name="StartDate" Type="Edm.DateTime" Nullable="false" />
<Property Name="Administrator" Type="Edm.Int32" Nullable="true" />
<NavigationProperty Name="Course" 
	Relationship="SchoolModel.FK_Course_Department" 
	FromRole="Department" ToRole="Course" />
</EntityType>
<EntityType Name="OfficeAssignment">
<Key>
<PropertyRef Name="InstructorID" />
</Key>
<Property Name="InstructorID" Type="Edm.Int32" Nullable="false" />
<Property Name="Location" Type="Edm.String" Nullable="false" 
	MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="Timestamp" Type="Edm.Binary" Nullable="false" 
	MaxLength="8" FixedLength="true" />
<NavigationProperty Name="Person" 
	Relationship="SchoolModel.FK_OfficeAssignment_Person" 
	FromRole="OfficeAssignment" ToRole="Person" />
</EntityType>
<EntityType Name="OnlineCourse">
<Key>
<PropertyRef Name="CourseID" />
</Key>
<Property Name="CourseID" Type="Edm.Int32" Nullable="false" />
<Property Name="URL" Type="Edm.String" Nullable="false" 
	MaxLength="100" Unicode="true" FixedLength="false" />
<NavigationProperty Name="Course" 
	Relationship="SchoolModel.FK_OnlineCourse_Course" 
	FromRole="OnlineCourse" ToRole="Course" />
</EntityType>
<EntityType Name="OnsiteCourse">
<Key>
<PropertyRef Name="CourseID" />
</Key>
<Property Name="CourseID" Type="Edm.Int32" Nullable="false" />
<Property Name="Location" Type="Edm.String" Nullable="false" 
	MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="Days" Type="Edm.String" Nullable="false" 
	MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="Time" Type="Edm.DateTime" Nullable="false" />
<NavigationProperty Name="Course" 
	Relationship="SchoolModel.FK_OnsiteCourse_Course" 
	FromRole="OnsiteCourse" ToRole="Course" />
</EntityType>
<EntityType Name="Person">
<Key>
<PropertyRef Name="PersonID" />
</Key>
<Property Name="PersonID" Type="Edm.Int32" Nullable="false" />
<Property Name="LastName" Type="Edm.String" Nullable="false" 
	MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="FirstName" Type="Edm.String" Nullable="false" 
	MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="HireDate" Type="Edm.DateTime" Nullable="true" />
<Property Name="EnrollmentDate" Type="Edm.DateTime" Nullable="true" />
<NavigationProperty Name="CourseGrade" 
	Relationship="SchoolModel.FK_CourseGrade_Student" 
	FromRole="Person" ToRole="CourseGrade" />
<NavigationProperty Name="OfficeAssignment" 
	Relationship="SchoolModel.FK_OfficeAssignment_Person" FromRole="Person"
 ToRole="OfficeAssignment" />
<NavigationProperty Name="Course" 
    Relationship="SchoolModel.CourseInstructor" FromRole="Person" ToRole="Course" />
</EntityType>
<Association Name="FK_Course_Department">
<End Role="Department" Type="SchoolModel.Department" Multiplicity="1" />
<End Role="Course" Type="SchoolModel.Course" Multiplicity="*" />
</Association>
<Association Name="FK_CourseGrade_Course">
<End Role="Course" Type="SchoolModel.Course" Multiplicity="1" />
<End Role="CourseGrade" Type="SchoolModel.CourseGrade" Multiplicity="*" />
</Association>
<Association Name="FK_OnlineCourse_Course">
<End Role="Course" Type="SchoolModel.Course" Multiplicity="1" />
<End Role="OnlineCourse" Type="SchoolModel.OnlineCourse" Multiplicity="0..1" />
<ReferentialConstraint>
<Principal Role="Course">
<PropertyRef Name="CourseID" />
</Principal>
<Dependent Role="OnlineCourse">
<PropertyRef Name="CourseID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="FK_OnsiteCourse_Course">
<End Role="Course" Type="SchoolModel.Course" Multiplicity="1" />
<End Role="OnsiteCourse" Type="SchoolModel.OnsiteCourse" Multiplicity="0..1" />
<ReferentialConstraint>
<Principal Role="Course">
<PropertyRef Name="CourseID" />
</Principal>
<Dependent Role="OnsiteCourse">
<PropertyRef Name="CourseID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="FK_CourseGrade_Student">
<End Role="Person" Type="SchoolModel.Person" Multiplicity="1" />
<End Role="CourseGrade" Type="SchoolModel.CourseGrade" Multiplicity="*" />
</Association>
<Association Name="FK_OfficeAssignment_Person">
<End Role="Person" Type="SchoolModel.Person" Multiplicity="1" />
<End Role="OfficeAssignment" Type="SchoolModel.OfficeAssignment" Multiplicity="0..1" />
<ReferentialConstraint>
<Principal Role="Person">
<PropertyRef Name="PersonID" />
</Principal>
<Dependent Role="OfficeAssignment">
<PropertyRef Name="InstructorID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="CourseInstructor">
<End Role="Course" Type="SchoolModel.Course" Multiplicity="*" />
<End Role="Person" Type="SchoolModel.Person" Multiplicity="*" />
</Association>
</Schema>

</edmx:DataServices>
</edmx:Edmx>

The proxy class will be generated from this XML.

The XSLT File

The XSLT file will generate the entities classes proxy. Here is the content of the XSLT file:

XML
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet>
<xsl:output method="text"/>
<!-- ROOT OF DOCUMENT -->
<xsl:template match="/edmx:Edmx">
<xsl:apply-templates select="edmx:DataServices" />
<xsl:apply-templates select="edmx:Queries">
</xsl:apply-templates>
</xsl:template>
<!--SERVICES-->
<xsl:template match="edmx:DataServices">
<xsl:text>[assembly: 
	global::System.Data.Objects.DataClasses.EdmSchemaAttribute()]&#10;</xsl:text>
<xsl:apply-templates select="schema:Schema">
<xsl:sort select="@Namespace"/>
</xsl:apply-templates>
</xsl:template>
<!-- NAMESPACE -->
<xsl:template match="schema:Schema">
<xsl:apply-templates select="schema:EntityContainer" mode="AssemblyMetadatas"/>
<xsl:text>
&#10;&#10;</xsl:text>
<xsl:text>namespace </xsl:text>
<xsl:value-of select="@Namespace"/>
<xsl:text>
{</xsl:text>
<xsl:apply-templates select="schema:EntityContainer" mode="EntitiesContext">
<xsl:with-param name="NamespaceName" select="@Namespace"></xsl:with-param>
</xsl:apply-templates>
<xsl:apply-templates select="schema:EntityType">
<xsl:sort select="@Name"/>
</xsl:apply-templates>
<xsl:text>&#10;}&#10;</xsl:text>
</xsl:template>
<!--CONTEXT-->
<xsl:template match="schema:EntityContainer" mode="AssemblyMetadatas">
<xsl:apply-templates select="schema:AssociationSet">
<xsl:sort select="@Name"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="schema:EntityContainer" mode="EntitiesContext">
<xsl:param name="NamespaceName" select="NamespaceName"></xsl:param>
<xsl:text>&#10;&#9;public partial class </xsl:text>
<xsl:value-of select="@Name"></xsl:value-of>
<xsl:text> : global::System.Data.Services.Client.DataServiceContext&#10;</xsl:text>
<xsl:text>&#9;{&#10;</xsl:text>
<xsl:text>&#10;&#9;&#9;public </xsl:text>
<xsl:value-of select="@Name"></xsl:value-of>
<xsl:text>(global::System.Uri serviceRoot) : base(serviceRoot)&#10;</xsl:text>
<xsl:text>&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;this.OnContextCreated();&#10;</xsl:text>
<xsl:text>&#9;&#9;}&#10;</xsl:text>
<xsl:text>&#9;&#9;partial void OnContextCreated();&#10;</xsl:text>
<xsl:apply-templates select="schema:EntitySet" mode="Properties">
<xsl:sort select="@Name"/>
<xsl:with-param name="NamespaceName" select="$NamespaceName"></xsl:with-param>
</xsl:apply-templates>
<xsl:apply-templates select="schema:EntitySet" mode="Add">
<xsl:sort select="@Name"/>
<xsl:with-param name="NamespaceName" select="$NamespaceName"></xsl:with-param>
</xsl:apply-templates>
<xsl:text>&#10;&#9;}&#10;&#10;</xsl:text>
</xsl:template>
<!--ENTITIES QUERIES-->
<xsl:template match="schema:EntitySet" mode="Properties">
<xsl:param name="NamespaceName" select="NamespaceName"></xsl:param>
<xsl:text>&#10;&#9;&#9;
	public System.Data.Services.Client.DataServiceQuery&lt;</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>&gt; </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>&#10;&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;get&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;&#9;if (this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> == null)&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;&#9;&#9;this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> = base.CreateQuery&lt;</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>&gt;("[</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>]");&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;&#9;}&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;&#9;return this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;}&#10;</xsl:text>
<xsl:text>&#9;&#9;}&#10;</xsl:text>
<xsl:text>&#9;&#9;private global::System.Data.Services.Client.DataServiceQuery&lt;
</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>&gt; _</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;&#10;</xsl:text>
</xsl:template>
<xsl:template match="schema:EntitySet" mode="Add">
<xsl:param name="NamespaceName" select="NamespaceName"></xsl:param>
<xsl:text>&#10;&#9;&#9;public void AddTo</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>(</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text> @</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>)&#10;</xsl:text>
<xsl:text>&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;base.AddObject("</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text> ", @</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>);&#10;</xsl:text>
<xsl:text>&#9;&#9;}&#10;</xsl:text>
</xsl:template>
<!--ASSEMBLY METADATAS-->
<xsl:template match="schema:AssociationSet">
<xsl:param name="NamespaceName" select="NamespaceName"></xsl:param>
<xsl:variable name="AssociationName" select="@Name"></xsl:variable>
<xsl:variable name="FirstRole" select="schema:End[1]/@Role"></xsl:variable>
<xsl:variable name="SecondRole" select="schema:End[2]/@Role"></xsl:variable>
<xsl:variable name="AssociationFirstType" 
select="../../schema:Association[@Name=$AssociationName]/
	schema:End[@Role=$FirstRole]/@Type"></xsl:variable>
<xsl:variable name="AssociationFirstMultiplicity" 
select="../../schema:Association[@Name=$AssociationName]/
	schema:End[@Role=$FirstRole]/@Multiplicity"></xsl:variable>
<xsl:variable name="AssociationSecondType"
 select="../../schema:Association[@Name=$AssociationName]/
	schema:End[@Role=$SecondRole]/@Type"></xsl:variable>
<xsl:variable name="AssociationSecondMultiplicity" 
select="../../schema:Association[@Name=$AssociationName]/
	schema:End[@Role=$SecondRole]/@Multiplicity"></xsl:variable>
<xsl:text>[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute
	("</xsl:text>
<xsl:value-of select="$NamespaceName"/>
<xsl:text>", "</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>", "</xsl:text>
<xsl:value-of select="$FirstRole"/>
<xsl:text>", global::System.Data.Metadata.Edm.RelationshipMultiplicity.</xsl:text>
<xsl:choose>
<xsl:when test="$AssociationFirstMultiplicity='*'">
<xsl:text>Many</xsl:text>
</xsl:when>
<xsl:when test="$AssociationFirstMultiplicity='1'">
<xsl:text>One</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>ZeroOrOne</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>, typeof(</xsl:text>
<xsl:value-of select="$AssociationFirstType"/>
<xsl:text>), "</xsl:text>
<xsl:value-of select="$SecondRole"/>
<xsl:text>", global::System.Data.Metadata.Edm.RelationshipMultiplicity.</xsl:text>
<xsl:choose>
<xsl:when test="$AssociationSecondMultiplicity='*'">
<xsl:text>Many</xsl:text>
</xsl:when>
<xsl:when test="$AssociationSecondMultiplicity='1'">
<xsl:text>One</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>ZeroOrOne</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>, typeof(</xsl:text>
<xsl:value-of select="$AssociationSecondType"/>
<xsl:text>))]&#10;</xsl:text>
</xsl:template>
<!--CLASSES-->
<xsl:template match="schema:EntityType">
<xsl:text>&#10;&#10;</xsl:text>
<xsl:text>&#9;#region class </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:text>&#9;[global::System.Serializable()]</xsl:text>
<xsl:text>&#9;[global::System.Data.Services.Common.DataServiceKeyAttribute(</xsl:text>
<xsl:for-each select="schema:Key/schema:PropertyRef">
<xsl:text>"</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>"</xsl:text>
</xsl:for-each>
<xsl:text>)]&#10;</xsl:text>
<xsl:text>&#9;public partial class </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> : global::System.ComponentModel.INotifyPropertyChanged
&#9;{&#10;</xsl:text>
<!-- FIELDS -->
<xsl:text>&#10;&#9;&#9;#region Fields&#10;&#10;</xsl:text>
<xsl:apply-templates select="schema:Property" mode="FieldsDeclaration">
<xsl:sort select="@Name"/>
</xsl:apply-templates>
<xsl:apply-templates select="schema:NavigationProperty" mode="FieldsDeclaration">
<xsl:sort select="@Name"/>
</xsl:apply-templates>
<xsl:text>&#10;&#9;&#9;#endregion //Fields&#10;</xsl:text>
<!-- PROPERTIES -->
<xsl:text>&#10;&#10;&#9;&#9;#region Properties&#10;&#10;</xsl:text>
<xsl:apply-templates select="schema:Property" mode="PropertiesDeclaration">
<xsl:sort select="@Name"/>
</xsl:apply-templates>
<xsl:apply-templates select="schema:NavigationProperty" mode="PropertiesDeclaration">
<xsl:sort select="@Name"/>
</xsl:apply-templates>
<xsl:text>&#10;&#9;&#9;#endregion //Property&#10;</xsl:text>
<xsl:text>&#10;&#10;&#9;&#9;#region INotifyPropertyChanged Membres
&#10;&#9;&#9;public event 
	System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
&#9;&#9;protected virtual void OnPropertyChanged(string property)
&#9;&#9;{
&#9;&#9;&#9;if (this.PropertyChanged != null)
&#9;&#9;&#9;{
&#9;&#9;&#9;&#9;this.PropertyChanged
	(this, new System.ComponentModel.PropertyChangedEventArgs(property));
&#9;&#9;&#9;}
&#9;&#9;}
&#9;&#9;#endregion&#10;</xsl:text>
<xsl:text>&#10;&#9;}&#10;</xsl:text>
<xsl:text>&#10;&#10;&#9;#endregion //class&#10;</xsl:text>
</xsl:template>
<!--FIELDS-->
<xsl:template match="schema:Property" mode="FieldsDeclaration">
<xsl:text>&#9;&#9;[global::System.Diagnostics.DebuggerBrowsable
	(System.Diagnostics.DebuggerBrowsableState.Never)]&#10;</xsl:text>
<xsl:text>&#9;&#9;private</xsl:text>
<xsl:choose>
<xsl:when test="(@Nullable='true') and @Type != 'Edm.String'">
<xsl:text> global::System.Nullable&lt;global::System.</xsl:text>
<xsl:value-of select="substring-after(@Type, '.')"/>
<xsl:text>&gt;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text> global::System.</xsl:text>
<xsl:value-of select="substring-after(@Type, '.')"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text> _</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;&#10;</xsl:text>
</xsl:template>
<xsl:template match="schema:NavigationProperty" mode="FieldsDeclaration">
<xsl:text>&#9;&#9;
[global::System.Diagnostics.DebuggerBrowsable
(System.Diagnostics.DebuggerBrowsableState.Never)]&#10;</xsl:text>
<xsl:text>&#9;&#9;private </xsl:text>
<xsl:variable name="NamespaceName" select="../../@Namespace"></xsl:variable>
<xsl:variable name="RelationShipName" select="@Relationship"></xsl:variable>
<xsl:variable name="ValueName" select="@ToRole"></xsl:variable>
<xsl:choose>
<xsl:when test="$Multiplicity = '0..1'">
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
</xsl:when>
<xsl:when test="$Multiplicity = '1'">
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>global::System.Collections.ObjectModel.Collection&lt;</xsl:text>
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
<xsl:text>&gt;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> _</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:choose>
<xsl:when test="$Multiplicity = '*'">
<xsl:text> = new global::System.Collections.ObjectModel.Collection&lt;</xsl:text>
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
<xsl:text>&gt;();&#10;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>;&#10;</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--PROPERTY-->
<xsl:template match="schema:Property" mode="PropertiesDeclaration">
<xsl:text>&#9;&#9;public </xsl:text>
<xsl:choose>
<xsl:when test="(@Nullable='true') and @Type != 'Edm.String'">
<xsl:text> global::System.Nullable&lt;global::System.</xsl:text>
<xsl:value-of select="substring-after(@Type, '.')"/>
<xsl:text>&gt;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text> global::System.</xsl:text>
<xsl:value-of select="substring-after(@Type, '.')"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>&#10;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;get</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;return this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;}</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;set</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;if (this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> != value)</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;&#9;this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> = value;</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;&#9;this.OnPropertyChanged("</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>");</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;}</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;}</xsl:text>
<xsl:text>&#10;&#9;&#9;}&#10;&#10;</xsl:text>
</xsl:template>
<xsl:template match="schema:NavigationProperty" mode="PropertiesDeclaration">
<xsl:text>&#9;&#9;[global::System.Xml.Serialization.XmlIgnoreAttribute()]
&#9;&#9;[global::System.Xml.Serialization.SoapIgnoreAttribute()]
&#9;&#9;public </xsl:text>
<xsl:variable name="NamespaceName" select="../../@Namespace"></xsl:variable>
<xsl:variable name="RelationShipName" select="@Relationship"></xsl:variable>
<xsl:variable name="ValueName" select="@ToRole"></xsl:variable>
<xsl:choose>
<xsl:when test="$Multiplicity = '0..1'">
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
</xsl:when>
<xsl:when test="$Multiplicity = '1'">
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>global::System.Collections.ObjectModel.Collection&lt;</xsl:text>
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
<xsl:text>&gt;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>&#10;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;get</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;return this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;}</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;set</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;if (this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> != value)</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;{</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;&#9;this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> = value;</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;&#9;this.OnPropertyChanged("</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>");</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;&#9;}</xsl:text>
<xsl:text>&#10;&#9;&#9;&#9;}</xsl:text>
<xsl:text>&#10;&#9;&#9;}&#10;&#10;</xsl:text>
</xsl:template>
<xsl:template match="edmx:Classes">
<xsl:text>&#10;&#9;public sealed class ClassNames {&#10;&#10;</xsl:text>
<xsl:for-each select="edmx:Class">
<xsl:text>&#9;&#9;public const string </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text> = "</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>";&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#9;}&#10;</xsl:text>
</xsl:template>
<xsl:template match="edmx:Class" mode="AttributesDeclaration">
<xsl:text>namespace ANPE.Annuaire.ReglesMetier.</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>s {&#10;</xsl:text>
<xsl:text>&#10;&#9;public sealed class </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>Attributes {&#10;&#10;</xsl:text>
<xsl:for-each select="edmx:Attributes/edmx:add">
<xsl:text>&#9;&#9;///&lt;summary&gt; </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>&lt;/summary&gt;&#10;</xsl:text>
<xsl:text>&#9;&#9;public const string </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text> = "</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>";&#10;</xsl:text>
<xsl:if test="@MinLength">
<xsl:text>&#9;&#9;public const int </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>_MinLength = </xsl:text>
<xsl:value-of select="@MinLength" />
<xsl:text>;&#10;</xsl:text>
</xsl:if>
<xsl:if test="@MaxLength">
<xsl:text>&#9;&#9;public const int </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>_MaxLength = </xsl:text>
<xsl:value-of select="@MaxLength" />
<xsl:text>;&#10;</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>&#9;}&#10;</xsl:text>
<xsl:text>}&#10;</xsl:text>
</xsl:template>
<xsl:template match="edmx:Class" mode="ClassesDeclaration">
<xsl:text>namespace ANPE.Annuaire.ReglesMetier.</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>s {&#10;</xsl:text>
<xsl:text>&#10;&#9;[Serializable]</xsl:text>
<xsl:text>&#10;&#9;[DirectoryClass(ClassNames.</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>)]</xsl:text>
<xsl:text>&#10;&#9;public partial class </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text> : System.ICloneable {&#10;&#10;</xsl:text>
<!-- fields declaration -->
<xsl:for-each select="edmx:Attributes/edmx:add">
<xsl:text>&#9;&#9;private&#9;</xsl:text>
<xsl:choose>
<xsl:when test="@IsSingleValued='false'">
<xsl:choose>
<xsl:when test="contains(@DataType,',')">
<xsl:value-of select="substring-before(@DataType,',')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@DataType" />
</xsl:otherwise>
</xsl:choose>
<xsl:text>[]</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="contains(@DataType,',')">
<xsl:value-of select="substring-before(@DataType,',')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@DataType" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
<xsl:text>&#9;_</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>;&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#10;</xsl:text>
<!-- properties declaration -->
<xsl:for-each select="edmx:Attributes/edmx:add">
<xsl:text>&#9;&#9;[DirectoryAttribute(</xsl:text>
<xsl:value-of select="../../@LogicalName" />
<xsl:text>Attributes.</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>)]&#10;</xsl:text>
<xsl:text>&#9;&#9;public&#9;</xsl:text>
<xsl:choose>
<xsl:when test="@IsSingleValued='false'">
<xsl:choose>
<xsl:when test="contains(@DataType,',')">
<xsl:value-of select="substring-before(@DataType,',')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@DataType" />
</xsl:otherwise>
</xsl:choose>
<xsl:text>[]</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="contains(@DataType,',')">
<xsl:value-of select="substring-before(@DataType,',')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@DataType" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
<xsl:text>&#9;</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>&#10;</xsl:text>
<xsl:text>&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;get { return _</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>; }&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;set { _</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>= value; }&#10;</xsl:text>
<xsl:text>&#9;&#9;}&#10;&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#9;&#9;public Object Clone()&#10;</xsl:text>
<xsl:text>&#9;&#9;{&#10;</xsl:text>
<xsl:text>&#9;&#9;&#9;return MemberwiseClone ();&#10;</xsl:text>
<xsl:text>&#9;&#9;}&#10;</xsl:text>
<xsl:text>&#9;}&#10;</xsl:text>
<xsl:text>}&#10;</xsl:text>
</xsl:template>
<xsl:template match="edmx:Queries">
<xsl:text>&#10;&#9;public class QueryNames {&#10;&#10;</xsl:text>
<xsl:for-each select="edmx:Query">
<xsl:text>&#9;&#9;public const string </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text> = "</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>";&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#9;}&#10;</xsl:text>
</xsl:template>
</xsl:stylesheet>

Even if you know absolutely nothing about XSLT language, you can read this file.You can find here all templates that match the nodes of the $metadata result of the web data service. You can change it very easily.

Inside the properties of the client project, select the generation event. Inside the pre build event, you can see the following command:

$(SolutionDir)Tools\msxsl.exe 
http://localhost:3932/BackOfficeServices/WebDataService.svc/$metadata 
$(ProjectDir)EntitiesGeneratorXSLTFile.xslt -o $(ProjectDir)EntitiesCustom.cs

It call the msxsl.exe tool in order to generate a file named entitiescustom.cs from the transformation of our service with a specified XSLT file. Each time you build the solution, the EntitiesCustom.cs file will be generated but the msxsl.exe tool with the XML extracted from the $metadata response of the web data service and with the XSLT seen before. You can begin this command with the "REM" keyword in order to comment the line.

The Result (Proxy Classes)

The result is similar to the proxy class generated by Microsoft. I added some debugger attributes to clean the class aspect and some #region. Here is one of the entity classes generated:

C#
#region class Course

[global::System.Serializable()]
[global::System.Data.Services.Common.DataServiceKeyAttribute("CourseID")]
public partial class Course : global::System.ComponentModel.INotifyPropertyChanged
{
#region Fields
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private global::System.Int32 _CourseID;
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private global::System.Int32 _Credits;
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private global::System.String _Title;
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private global::System.Collections.ObjectModel.Collection<CourseGrade> _CourseGrade = 
new global::System.Collections.ObjectModel.Collection<CourseGrade>();
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private Department _Department;
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private OnlineCourse _OnlineCourse;
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private OnsiteCourse _OnsiteCourse;
[global::System.Diagnostics.DebuggerBrowsable
	System.Diagnostics.DebuggerBrowsableState.Never)]
private global::System.Collections.ObjectModel.Collection<Person> _Person = 
new global::System.Collections.ObjectModel.Collection<Person>();
#endregion //Fields
#region Properties
public global::System.Int32 CourseID
{
get
{
return this._CourseID;
}
set
{
if (this._CourseID != value)
{
this._CourseID = value;
this.OnPropertyChanged("CourseID");
}
}
}
public global::System.Int32 Credits
{
get
{
return this._Credits;
}
set
{
if (this._Credits != value)
{
this._Credits = value;
this.OnPropertyChanged("Credits");
}
}
}
public global::System.String Title
{
get
{
return this._Title;
}
set
{
if (this._Title != value)
{
this._Title = value;
this.OnPropertyChanged("Title");
}
}
}
[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public global::System.Collections.ObjectModel.Collection<CourseGrade> CourseGrade
{
get
{
return this._CourseGrade;
}
set
{
if (this._CourseGrade != value)
{
this._CourseGrade = value;
this.OnPropertyChanged("CourseGrade");
}
}
}
[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public Department Department
{
get
{
return this._Department;
}
set
{
if (this._Department != value)
{
this._Department = value;
this.OnPropertyChanged("Department");
}
}
}
[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public OnlineCourse OnlineCourse
{
get
{
return this._OnlineCourse;
}
set
{
if (this._OnlineCourse != value)
{
this._OnlineCourse = value;
this.OnPropertyChanged("OnlineCourse");
}
}
}
[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public OnsiteCourse OnsiteCourse
{
get
{
return this._OnsiteCourse;
}
set
{
if (this._OnsiteCourse != value)
{
this._OnsiteCourse = value;
this.OnPropertyChanged("OnsiteCourse");
}
}
}
[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public global::System.Collections.ObjectModel.Collection<Person> Person
{
get
{
return this._Person;
}
set
{
if (this._Person != value)
{
this._Person = value;
this.OnPropertyChanged("Person");
}
}
}
#endregion //Property
#region INotifyPropertyChanged Membres
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, 
	new System.ComponentModel.PropertyChangedEventArgs(property));
}
}
#endregion
}
#endregion //class

Now we can test our proxy.

The Test

We will try to retrieve a Person and change its name.

Here is the code added to the Program.cs file of the client side project.

C#
var query = from u in entities.Person
where u.LastName == "Abercrombie"
select u;

foreach (Person person in query)
{
Console.Out.WriteLine("A Person finded :");
Console.Out.WriteLine(string.Format("{0} {1}", person.FirstName, person.LastName));

Console.Out.WriteLine("Why not changing its name ?");
Console.Out.WriteLine("Please specifies a new first name :");
string firstName = Console.In.ReadLine();

person.FirstName = firstName;
entities.UpdateObject(person);
entities.SaveChanges();
Console.Out.WriteLine("New name saved");
}

gd06.png

Our proxy class works well !

Conclusion

With this method, you can easily add features to your entities classes and enjoy the ADO.NET data services. Just change the XSLT and add your own features.

I did not test the XSLT in a lot of scenarios. If there are some problems, just ask me.

History

  • 19th September, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)