It used to be that in any thin client application that you build, you would spent most time worrying about how to bring your data to the client and then how to properly submit your changes back. These days, as the data access frameworks and the ORM solutions matured, you can actually afford to think about your application, your business logic etc... instead of constantly tinkering with the DA plumbing.
Such challenges still exist, to a certain degree, for young platforms like Silverlight. But you'd be happy to know that XPO is making it to Silverlight. In fact, you can already find some bits in your v8.2 install :).
I am going to show you how to use them... [If you are new to XPO I suggest reading more about it here http://www.devexpress.com/Products/NET/ORM/ and also check out Gary's Blog for some XPO related goodies...]
Warning: These are pre-release preview bits for (DXperience 8.2.4) and are subject to change.
Developing an HTTP Data Layer
To accomplish the flow illustrated above, we will need an IDataStore that will work over HTTP.
public interface IDataStore {
AutoCreateOption AutoCreateOption { get; }
ModificationResult ModifyData(params ModificationStatement[] dmlStatements);
SelectedData SelectData(params SelectStatement[] selects);
UpdateSchemaResult UpdateSchema(bool dontCreateIfFirstTableNotExist, params DBTable[] tables);
}
On the server side, we will expose it as a WCF Web Service.
[XmlSerializerFormat]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract(Namespace = "")]
public class Gateway {
static IDataStore s_dataStore;
static Gateway() {
s_dataStore = XpoDefault.GetConnectionProvider(
MSSqlConnectionProvider.GetConnectionString("<COMPUTER_NAME>", "<DATABASE_NAME>"),
AutoCreateOption.DatabaseAndSchema);
}
[OperationContract]
public AutoCreateOption GetAutoCreateOption() {
return s_dataStore.AutoCreateOption;
}
[OperationContract]
public SelectedData SelectData(SelectStatement[] selects) {
if (selects != null && selects.Length > 0 && selects[0].TableName == "XPObjectType") {
return new SelectedData(new SelectStatementResult());
}
return s_dataStore.SelectData(selects);
}
[OperationContract]
public UpdateSchemaResult UpdateSchema(bool dontCreateIfFirstTableNotExist,
DBTable[] tables) {
return s_dataStore.UpdateSchema(dontCreateIfFirstTableNotExist, tables);
}
[OperationContract]
public ModificationResult ModifyData(ModificationStatement[] statements) {
return s_dataStore.ModifyData(statements);
}
}
And on the client side we will create a wrapper for it.
The contract:
[XmlSerializerFormat]
[ServiceContractAttribute(Namespace = "")]
public interface IGateway {
[OperationContractAttribute(AsyncPattern = true,
Action = "urn:Gateway/GetAutoCreateOption", ReplyAction = "urn:Gateway/GetAutoCreateOptionResponse")]
IAsyncResult BeginGetAutoCreateOption(AsyncCallback callback, object asyncState);
AutoCreateOption EndGetAutoCreateOption(IAsyncResult result);
[OperationContractAttribute(AsyncPattern = true,
Action = "urn:Gateway/SelectData", ReplyAction = "urn:Gateway/SelectDataResponse")]
IAsyncResult BeginSelectData(SelectStatement[] selects, AsyncCallback callback, object asyncState);
SelectedData EndSelectData(IAsyncResult result);
[OperationContract(AsyncPattern = true,
Action = "urn:Gateway/UpdateSchema", ReplyAction = "urn:Gateway/UpdateSchemaResponse")]
IAsyncResult BeginUpdateSchema(bool dontCreateIfFirstTableNotExist, DBTable[] tables, AsyncCallback callback, object asyncState);
UpdateSchemaResult EndUpdateSchema(IAsyncResult result);
[OperationContract(AsyncPattern = true,
Action = "urn:Gateway/ModifyData", ReplyAction = "urn:Gateway/ModifyDataResponse")]
IAsyncResult BeginModifyData(ModificationStatement[] statements, AsyncCallback callback, object asyncState);
ModificationResult EndModifyData(IAsyncResult result);
}
And the transport channel:
public class Gateway : ClientBase<IGateway>, IGateway, IDataStore {
#region IDataStore Members
...
public SelectedData SelectData(params SelectStatement[] selects) {
if (this._dispatcher.CheckAccess()) {
throw new InvalidOperationException();
}
var _ar = BeginSelectData(selects, null, null);
if (!_ar.IsCompleted) {
_ar.AsyncWaitHandle.WaitOne();
}
return EndSelectData(_ar);
}
...
#endregion
#region IGateway Memebers
...
public IAsyncResult BeginSelectData(SelectStatement[] selects, AsyncCallback callback, object asyncState) {
return base.Channel.BeginSelectData(selects, callback, asyncState);
}
public SelectedData EndSelectData(IAsyncResult result) {
return base.Channel.EndSelectData(result);
}
...
#endregion
#region ClientChannel
private class ClientChannel : ChannelBase<IGateway>, IGateway {
...
public IAsyncResult BeginSelectData(SelectStatement[] selects, AsyncCallback callback, object asyncState) {
object[] _args = new object[1];
_args[0] = selects;
IAsyncResult _result = base.BeginInvoke("SelectData", _args, callback, asyncState);
return _result;
}
public SelectedData EndSelectData(IAsyncResult result) {
object[] _args = new object[0];
SelectedData _result = (SelectedData)base.EndInvoke("SelectData", _args, result);
return _result;
}
...
}
#endregion
}
Full implementation of the Gateway proxy can be downloaded here: Gateway.cs
Preparing your Silverlight Application
In your Silverlight project add references to the following assemblies:
- DevExpress.Data.v8.2.SL
- DevExpress.Xpo.v8.2.SL
- System.Xml.Serialization
- System.ServiceModel
Add the Gateway.cs and include a config. file for the gateway web service:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_Gateway" maxBufferSize="65536"
maxReceivedMessageSize="65536">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:50691/Gateway.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_Gateway" contract="DevExpress.Xpo.Xtras.IGateway"
name="BasicHttpBinding_Gateway" />
</client>
</system.serviceModel>
</configuration>
Working with Data
Now that all the prop. work is out of the way, we can start executing some queries. Suppose you are building an auto showroom application and you want to retrieve all the BMWs from the table inventory.
[OptimisticLocking(false)]
public class Inventory : PersistentBase {
Guid _id;
[Key(AutoGenerate = true)]
public Guid ID {
get { return _id; }
set { SetPropertyValue("ID", ref _id, value); }
}
string _make;
public string Make {
get { return _make; }
set { SetPropertyValue("Make", ref _make, value); }
}
string _model;
public string Model {
get { return _model; }
set { SetPropertyValue("Model", ref _model, value); }
}
public Inventory(Session session)
: base(session) {
}
}
UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(_gateway));
XPQuery<Inventory> inventory = new XPQuery<Inventory>(unitOfWork);
var query = from t in inventory
where t.Make == "BMW"
select t;
var list = query.ToList<Inventory>();
// Do stuff with the list... bind it to a grid etc...
Where _gateway is an instance of our DevExpress.Xpo.Xtras.Gateway : IDataStore initialized somewhere during startup or page load.
Modifying or creating a new record is also done naturally via the Inventory class.
using (UnitOfWork u = new UnitOfWork(new SimpleDataLayer(this._gateway))) {
var o = new Inventory(u);
o.Make = "Audi";
u.Save(o);
u.CommitChanges();
}
One very important thing to remember here is that all operations that require a web service call need to be performed asynchronously. This is enforced by the Silverlight runtime [and it's good thing]. So always invoke your calls from a worker thread, for example:
ThreadPool.QueueUserWorkItem(delegate(object state) {
UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(_gateway));
XPQuery<Inventory> inventory = new XPQuery<Inventory>(unitOfWork);
var query = from t in inventory
where t.Make == "BMW"
select t;
var list = query.ToList<Inventory>();
Dispatcher.BeginInvoke(() => {
agDataGrid.DataSource = list;
});
});
Download Sample Project
Cheers,
Azret