Generate your own proxy for ADO.Net Data Services on client side

Introduction

First of all, please accept my appologizes for my poor english :)

This article speak 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 scenarii. 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 reflexion. You can make 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 way.

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 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 build a result from an xml and a 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 Data base

Very simple step. Microsoft offer some T-Sql scripts to generate a sample database. Go here to find it. Install the script to create to own base.

The Solution

We are not here to learn how to create those projects. So just download the zip gived 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 data base.

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

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

providerName="System.Data.EntityClient" />

Start the web data service by right click 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 a Xml file that contains all informations 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 see now a complete description of the school model :

Collapse
xml version="1.0" encoding="utf-8" standalone="yes"?>
<edmx:Edmx href="%22%22%22%22http://schemas.microsoft.com/ado/2007/06/edmx%22%3E%3Cedmx:DataServices%3E%3CSchema%3E%3CEntityType%22%22%22%22">>
<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 :

Collapse
xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet>
<xsl:output method="text"/>

<xsl:template match="/edmx:Edmx">
<xsl:apply-templates select="edmx:DataServices" />
<xsl:apply-templates select="edmx:Queries">
</xsl:apply-templates>
</xsl:template>

<xsl:template match="edmx:DataServices">
<xsl:text>[assembly: global::System.Data.Objects.DataClasses.EdmSchemaAttribute()] </xsl:text>
<xsl:apply-templates select="schema:Schema">
<xsl:sort select="@Namespace"/>
</xsl:apply-templates>
</xsl:template>

<xsl:template match="schema:Schema">
<xsl:apply-templates select="schema:EntityContainer" mode="AssemblyMetadatas"/>
<xsl:text>
</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> } </xsl:text>
</xsl:template>

<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> public partial class </xsl:text>
<xsl:value-of select="@Name"></xsl:value-of>
<xsl:text> : global::System.Data.Services.Client.DataServiceContext </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> public </xsl:text>
<xsl:value-of select="@Name"></xsl:value-of>
<xsl:text>(global::System.Uri serviceRoot) : base(serviceRoot) </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> this.OnContextCreated(); </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text> partial void OnContextCreated(); </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> } </xsl:text>
</xsl:template>

<xsl:template match="schema:EntitySet" mode="Properties">
<xsl:param name="NamespaceName" select="NamespaceName"></xsl:param>
<xsl:text> public System.Data.Services.Client.DataServiceQuery<</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>> </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> { </xsl:text>
<xsl:text> get </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> if (this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> == null) </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> = base.CreateQuery<</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>>("[</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>]"); </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text> return this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>; </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text> private global::System.Data.Services.Client.DataServiceQuery<</xsl:text>
<xsl:value-of select="substring-after(@EntityType, concat($NamespaceName, '.'))"/>
<xsl:text>> _</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>; </xsl:text>
</xsl:template>
<xsl:template match="schema:EntitySet" mode="Add">
<xsl:param name="NamespaceName" select="NamespaceName"></xsl:param>
<xsl:text> 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>) </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> 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>); </xsl:text>
<xsl:text> } </xsl:text>
</xsl:template>

<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>))] </xsl:text>
</xsl:template>

<xsl:template match="schema:EntityType">
<xsl:text> </xsl:text>
<xsl:text> #region class </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> </xsl:text>
<xsl:text> [global::System.Serializable()]</xsl:text>
<xsl:text> [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>)] </xsl:text>
<xsl:text> public partial class </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> : global::System.ComponentModel.INotifyPropertyChanged
{ </xsl:text>

<xsl:text> #region Fields </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> #endregion //Fields </xsl:text>

<xsl:text> #region Properties </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> #endregion //Property </xsl:text>
<xsl:text> #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 </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text> #endregion //class </xsl:text>
</xsl:template>

<xsl:template match="schema:Property" mode="FieldsDeclaration">
<xsl:text> [global::System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] </xsl:text>
<xsl:text> private</xsl:text>
<xsl:choose>
<xsl:when test="(@Nullable='true') and @Type != 'Edm.String'">
<xsl:text> global::System.Nullable<global::System.</xsl:text>
<xsl:value-of select="substring-after(@Type, '.')"/>
<xsl:text>></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>; </xsl:text>
</xsl:template>
<xsl:template match="schema:NavigationProperty" mode="FieldsDeclaration">
<xsl:text> [global::System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] </xsl:text>
<xsl:text> private </xsl:text>
<xsl:variable name="NamespaceName" select="%22%22%22%22%22%22@Namespace%22%3E%3C/xsl:variable%22%22%22%22%22%22%22">../../@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<</xsl:text>
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
<xsl:text>></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<</xsl:text>
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
<xsl:text>>(); </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>; </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<xsl:template match="schema:Property" mode="PropertiesDeclaration">
<xsl:text> public </xsl:text>
<xsl:choose>
<xsl:when test="(@Nullable='true') and @Type != 'Edm.String'">
<xsl:text> global::System.Nullable<global::System.</xsl:text>
<xsl:value-of select="substring-after(@Type, '.')"/>
<xsl:text>></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> {</xsl:text>
<xsl:text> get</xsl:text>
<xsl:text> {</xsl:text>
<xsl:text> return this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> set</xsl:text>
<xsl:text> {</xsl:text>
<xsl:text> if (this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> != value)</xsl:text>
<xsl:text> {</xsl:text>
<xsl:text> this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> = value;</xsl:text>
<xsl:text> this.OnPropertyChanged("</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>");</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> } </xsl:text>
</xsl:template>
<xsl:template match="schema:NavigationProperty" mode="PropertiesDeclaration">
<xsl:text> [global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public </xsl:text>
<xsl:variable name="NamespaceName" select="%22%22%22%22%22%22@Namespace%22%3E%3C/xsl:variable%22%22%22%22%22%22%22">../../@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<</xsl:text>
<xsl:value-of select="substring-after($Type, concat($NamespaceName, '.'))"/>
<xsl:text>></xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> {</xsl:text>
<xsl:text> get</xsl:text>
<xsl:text> {</xsl:text>
<xsl:text> return this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>;</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> set</xsl:text>
<xsl:text> {</xsl:text>
<xsl:text> if (this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> != value)</xsl:text>
<xsl:text> {</xsl:text>
<xsl:text> this._</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text> = value;</xsl:text>
<xsl:text> this.OnPropertyChanged("</xsl:text>
<xsl:value-of select="@Name"/>
<xsl:text>");</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> }</xsl:text>
<xsl:text> } </xsl:text>
</xsl:template>
<xsl:template match="edmx:Classes">
<xsl:text> public sealed class ClassNames { </xsl:text>
<xsl:for-each select="edmx:Class">
<xsl:text> public const string </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text> = "</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>"; </xsl:text>
</xsl:for-each>
<xsl:text> } </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 { </xsl:text>
<xsl:text> public sealed class </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>Attributes { </xsl:text>
<xsl:for-each select="edmx:Attributes/edmx:add">
<xsl:text> ///<summary> </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text></summary> </xsl:text>
<xsl:text> public const string </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text> = "</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>"; </xsl:text>
<xsl:if test="@MinLength">
<xsl:text> public const int </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>_MinLength = </xsl:text>
<xsl:value-of select="@MinLength" />
<xsl:text>; </xsl:text>
</xsl:if>
<xsl:if test="@MaxLength">
<xsl:text> public const int </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>_MaxLength = </xsl:text>
<xsl:value-of select="@MaxLength" />
<xsl:text>; </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text> } </xsl:text>
<xsl:text>} </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 { </xsl:text>
<xsl:text> [Serializable]</xsl:text>
<xsl:text> [DirectoryClass(ClassNames.</xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text>)]</xsl:text>
<xsl:text> public partial class </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text> : System.ICloneable { </xsl:text>

<xsl:for-each select="edmx:Attributes/edmx:add">
<xsl:text> private </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> _</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>; </xsl:text>
</xsl:for-each>
<xsl:text> </xsl:text>

<xsl:for-each select="edmx:Attributes/edmx:add">
<xsl:text> [DirectoryAttribute(</xsl:text>
<xsl:value-of select="%22%22%22%22%22%22@LogicalName%22%22%22%22%22%22%22">../../@LogicalName" />
<xsl:text>Attributes.</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>)] </xsl:text>
<xsl:text> public </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> </xsl:text>
<xsl:value-of select="@LogicalName" />
<xsl:text> </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> get { return _</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>; } </xsl:text>
<xsl:text> set { _</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>= value; } </xsl:text>
<xsl:text> } </xsl:text>
</xsl:for-each>
<xsl:text> public Object Clone() </xsl:text>
<xsl:text> { </xsl:text>
<xsl:text> return MemberwiseClone (); </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text> } </xsl:text>
<xsl:text>} </xsl:text>
</xsl:template>
<xsl:template match="edmx:Queries">
<xsl:text> public class QueryNames { </xsl:text>
<xsl:for-each select="edmx:Query">
<xsl:text> public const string </xsl:text>
<xsl:value-of select="@Name" />
<xsl:text> = "</xsl:text>
<xsl:value-of select="@Name" />
<xsl:text>"; </xsl:text>
</xsl:for-each>
<xsl:text> } </xsl:text>
</xsl:template>
</xsl:stylesheet>

Even if you know absolutly 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 easlily.

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 bu 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 if 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 class generated :

Collapse
#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.

Collapse
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 way of doing 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 scenarii. If there are some problem just ask me.

License

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

->Read More...

0 Comments:

Đăng nhận xét