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

* Download SolutionSchoolTest.zip - 89.16 KB

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

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


>








































































ToRole="OfficeAssignment" />



























































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












[assembly: global::System.Data.Objects.DataClasses.EdmSchemaAttribute()]









namespace


{







}









public partial class

: global::System.Data.Services.Client.DataServiceContext
{
public

(global::System.Uri serviceRoot) : base(serviceRoot)
{
this.OnContextCreated();
}
partial void OnContextCreated();








}




public System.Data.Services.Client.DataServiceQuery<

>

{
get
{
if (this._

== null)
{
this._

= base.CreateQuery<

>("[

]");
}
return this._

;
}
}
private global::System.Data.Services.Client.DataServiceQuery<

> _

;



public void AddTo

(

@

)
{
base.AddObject("

", @

);
}







select="../../schema:Association[@Name=$AssociationName]/schema:End[@Role=$FirstRole]/@Type">
select="../../schema:Association[@Name=$AssociationName]/schema:End[@Role=$FirstRole]/@Multiplicity">
select="../../schema:Association[@Name=$AssociationName]/schema:End[@Role=$SecondRole]/@Type">
select="../../schema:Association[@Name=$AssociationName]/schema:End[@Role=$SecondRole]/@Multiplicity">
[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("

", "

", "

", global::System.Data.Metadata.Edm.RelationshipMultiplicity.


Many


One


ZeroOrOne


, typeof(

), "

", global::System.Data.Metadata.Edm.RelationshipMultiplicity.


Many


One


ZeroOrOne


, typeof(

))]




#region class


[global::System.Serializable()]
[global::System.Data.Services.Common.DataServiceKeyAttribute(

"

"

)]
public partial class

: global::System.ComponentModel.INotifyPropertyChanged
{


#region Fields






#endregion //Fields

#region Properties






#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



[global::System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
private


global::System.Nullable<global::System.

>


global::System.



_

;


[global::System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
private
../../@Namespace">










global::System.Collections.ObjectModel.Collection<

>


_



= new global::System.Collections.ObjectModel.Collection<

>();


;





public


global::System.Nullable<global::System.

>


global::System.





{
get
{
return this._

;
}
set
{
if (this._

!= value)
{
this._

= value;
this.OnPropertyChanged("

");
}
}
}


[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
public

../../@Namespace">










global::System.Collections.ObjectModel.Collection<

>




{
get
{
return this._

;
}
set
{
if (this._

!= value)
{
this._

= value;
this.OnPropertyChanged("

");
}
}
}


public sealed class ClassNames {

public const string

= "

";

}


namespace ANPE.Annuaire.ReglesMetier.

s {
public sealed class

Attributes {

///<summary>

</summary>
public const string

= "

";

public const int

_MinLength =

;


public const int

_MaxLength =

;


}
}


namespace ANPE.Annuaire.ReglesMetier.

s {
[Serializable]
[DirectoryClass(ClassNames.

)]
public partial class

: System.ICloneable {


private










[]












_

;




[DirectoryAttribute(
../../@LogicalName" />
Attributes.

)]
public










[]















{
get { return _

; }
set { _

= value; }
}

public Object Clone()
{
return MemberwiseClone ();
}
}
}


public class QueryNames {

public const string

= "

";

}



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 =
new global::System.Collections.ObjectModel.Collection();
[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 =
new global::System.Collections.ObjectModel.Collection();
#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
{
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
{
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.->Read More...

0 Comments:

Đăng nhận xét