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
In the solution directory, I added a Tools directory where you can find the msxsl.exe tools:
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:
<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 clicking on the WebDataService.svc file on the solution explorer and selecting "Open in Browser". You will see something like this:
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:
You now see a complete description of the school model:
="1.0"="utf-8"="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:
="1.0"="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="../../@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="../../@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="../../@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 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:
#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.
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");
}
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