How SubmitChanges works in .NET RIA Servies
Now I’m back from my
4 week long vacation. I have spend a lot of time with my new apartment, took
about 2 month to get most of it ready, I still have a lot of things to do, but
all the painting and stuff are done :) I got a question on my e-mail regarding
.NET RIA Services and the Business Application project’s
UserRegistartionService. It was about the AddUser method in the Service and
when and where it’s called. I thought it would be great to write a post about
it.
Something that is
good to have in mind is that the .NET RIA Services is all about code
generation. The Business layer we are creating by following the .NET RIA
Service business layer pattern and guide lines, will be used to generate proxy
classes on the client-side to help us communicate to the server in a easy way.
The proxy class on the client-side will use “Unit of Work” and Track changing
etc. To use those features in a proper way, we have to use a specific “pattern”
when we write our Business logic layer.
Note: This post is based on the July
Preview of the .NET RIA Services, and I also assume you have some basic
knowledge about the .Net RIA Services.
I will start with a
custom DomainSerivce and how it works. Here is an example of a custom
DomainService:
[EnableClientAccess()]
public class MyDomainService : DomainService
{
private List<Customer> _customers = new List<Customer>()
{
new Customer() { ID = 1, Name = "John Doe" },
new Customer() { ID = 2, Name = "Jane Doe" }
};
public IEnumerable<Customer> GetCustomers()
{
return _customers;
}
public void AddCustomer(Customer customer)
{
_customers.Add(customer);
}
public void DeleteCustomer(Customer customer)
{
_customers.Remove(
_customers.Find(c => c.ID == customer.ID));
}
public void UpdateCustomer(Customer customer)
{
var customerToUpdate =
_customers.Find(c => c.ID == customer.ID);
customerToUpdate = customer;
}
}
public class Customer
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
The code has a
MyDomainService class and a Customer entity with two properties, ID and Name.
The MyDomainService has four methods, GetCustomers, AddCustomer, DeleteCustomer
and UpdateCustomer. When we build the code, the .NET RIA Services will generate
our proxy classes (DomainContext) to help use communicate to the DomainService
on the server-side. The generated proxy class will be add to our client
project.
Here is the
generated proxy classes (I have removed some code to make it smaller):
public sealed partial
class Customer : Entity
{
private int _id;
private string _name;
public Customer()
{
...
}
[DataMember()]
[Key()]
public int ID
{
get
{
return this._id;
}
set
{
...
this._id = value;
...
}
}
[DataMember()]
public string Name
{
get
{
return this._name;
}
set
{
...
this._name = value;
...
}
}
public override object
GetIdentity()
{
return this._id;
}
}
public sealed partial
class MyDomainContext : DomainContext
{
...
public EntityList<Customer> Customers
{
get
{
return base.Entities.GetEntityList<Customer>();
}
}
public EntityQuery<Customer> GetCustomersQuery()
{
return base.CreateQuery<Customer>("GetCustomers", null, false, true);
}
...
}
Note: The code I have removed is not
important for this blog post.
If you look at the
generated code, you can see the Customer entity and a class with the name
MyDomainContext. The MyDomainContext is the class that will communicate to our
MyDomainService. As you can see, there is only two public members generated,
Customers and GetCustomerQuery. The GetCustomer, AddCustomer, DeleteCustomer
and UpdateCustomer are not added to the MyDomainContext class. So how can we
call the Add-,Delete- and UpdateCustomer from the generated proxy class?
As I mentioned
before the.NET RIA Services have a track changing and an “Unit of Work”
feature. The MyDomainContext class will have a method called SubmitChanges,
which we can use to submit all changes, like updating entities, removing and
adding etc. We can’t call the Add-, Delete- or UpdateCustomer method from the
client-side directly, we must use the DomainContext “Unit or Work” feature.
The Add, Delete and
Update methods in the DomainService is needed if we want to be able to add,
update or remove an entity. If we don’t add those methods to the DomainService,
we aren’t allowed to Add, Delete or Update Entities on the client-side. So how
can we call the Add, Update and Remove methods from the client-side by using
the DomainContext’s Unit of Work.The following is an example:
public partial class
MainPage : UserControl
{
MyDomainContext _myDomainContext = new MyDomainContext();
public MainPage()
{
var loadOperation =
_myDomainContext.Load<Customer>(_myDomainContext.GetCustomersQuery());
loadOperation.Completed += new EventHandler(loadOperation_Completed);
}
void loadOperation_Completed(object
sender, EventArgs e)
{
var lastCustomer =
_myDomainContext.Customers.Last();
lastCustomer.Name = "Last Doe";
_myDomainContext.Customers.Add(new Customer() { ID = 3, Name = "Jones Doe" });
_myDomainContext.Customers.Remove(_myDomainContext.Customers.First());
_myDomainContext.SubmitChanges();
}
In the constructor
of the MainPage, the Load method of the MyDomainContext class is used. Because
our generated DomainContext proxy inherits from the DomainContext base class,
we have access to some methods, for example the Load and SubmitChanges method.
The Load method will make an async call to the MyDomainSerivice’s GetCustomers
method. When the DomainContext gets the results from the server, the
loadOperation_Completed event will be “executed”. By using the
MyDomainContext’s Customers property (generated by .Net RIA Services) we will
have access to all the Customers that are loaded. The Customers property
returns a EntitList. A DomainContext has an Entity Container, where all
entities are stored. A Entity Container managed a set of EntityLists and have
the responsibility to handle the changing tracking and identity management on
the client.
In the code above
you can see that the Name of the last customer is set to “Last Doe”. When we
update an Entity, an Update operation is added to the DomainContext’s “Unit of
Work”, handled by the Entity Container. No updates will take place until we
tell the Entity Container to process all of the changes. The code above will
also call the Add and Remove method of the Customers property. When doing so,
an Add and Remove operation is added to the list of work that should be
processed when we call the SubmitChanges. When the SubmitChanges is called the
changes are all sent to the service all together.
Note: When we call the Add and Remove method etc, no
calls to the server will take place. It will only take place when we call the
SubmitChanges.
So when the
SubmitChanges is called, the DomainCotnext Unit of Work will be handled. We
have Updated a Entity, added an Entity and also Removed and Entity. So the
SubmitChanges will now do an async. call to the DomainService, and call the
methods that will handle the operations, in this case the MyDomainService’s
AddCustomer, RemoveCustomer and UpdateCustomer method will be executed on the
server-side.
There is a naming
convention which can be used for the CRUD methods, here is a list form the .NET
RIA Services documentation:
Insert
“Insert”, “Add”,
“Create” prefixes
Method signature :
void InsertX(T entity), where T is an entity Type
Update
“Update”, “Change”,
“Modify” prefixes
Method signature :
void UpdateX(T current), where T is an entity Type
Delete
“Delete”, “Remove”
prefixes
Method signature :
void DeleteX(T entity), where T is an entity Type
Query
Method signature
returning a singleton, IEnumerable<T> , IQueryable<T> where T is an
entity Type, and taking zero or more parameters
Resolve
“Resolve” prefix
Method signature :
bool ResolveX(T curr, T original, T store, bool isDelete) where T is an entity
type
We can also use
attributes
(InsertAttribute/UpdateAttribute/DeleteAttribute/QueryAttribute/ResolveAttribute)
if we don’t want to use the “reserved” words:
[Delete]
public void DestoryCustomer(Customer customer)
{
_customers.Remove(
_customers.Find(c => c.ID == customer.ID));
}
Now when you know
why some methods added to a Service isn’t available on the Client-side, we can
go on with the Business Application project template’s UserRegistartionService
If we take a look at
the Business Application project template’s UserRegistartionService. We have
the following methods:
[EnableClientAccess]
public class UserRegistrationService : DomainService
{
public void AddUser(UserInformation user)
{
MembershipCreateStatus
createStatus;
Membership.CreateUser(user.UserName,
user.Password, user.Email, user.Question, user.Answer, true, null, out
createStatus);
if (createStatus != MembershipCreateStatus.Success)
{
throw new
DomainServiceException(ErrorCodeToString(createStatus));
}
}
public IEnumerable<UserInformation> GetUsers()
{
return null;
}
}
We can’t from the
client-side call the AddUser method, is not added to the
UserRegistartionContext class. To add a new user we must add it to an
EntityList, in this case the generated DomainContext will have a EntityList
called UserInformations. This is generated out from the GetUsers query method.
If we don’t have a query method, there is no way we can have access to an
EntityList.
In the Business
Application template’s LoginWindow.xaml.cs file, the generated DomainContext is
created in the constructor:
private UserInformation _userInformation = new UserInformation();
private UserRegistrationContext _registration = new UserRegistrationContext();
public LoginWindow()
{
...
this._registration.UserInformations.Add(this._userInformation);
this.registerForm.CurrentItem = this._userInformation;
}
As you can see, a
new instance of the UserInformation entity (_userInformation) is added to the
UserRegistartionContext’s UserInformations EntityList. At this case an Add
operation is added to the UserRegistartionContext’s “Unit of Work”. The
_userInformation is then bounded to a DataForm, which uses a two-way binding.
So every changed done to the DataForm, will be done to the _userInformation
object. When the Register button of the LoginWindow.xamls is called, the
UserRegistrationService’s SubmitChanges method will be called, which will make
sure the AddUser method of the UserRegistartionService will be executed and the
_userInformation object will be passed as an argument to the AddUser method.
private void RegisterButton_Click(object sender, RoutedEventArgs e)
{
...
_regOp =
_registration.SubmitChanges();
...
}
I hope this post
gave you some understanding about how the DomainContext and DomainSerice handle
the CRUD methods etc.
Published Monday,
August 10, 2009 12:18 PM by Fredrik N
Pasted
from <http://weblogs.asp.net/fredriknormen/archive/2009/08/10/how-submitchanges-works-in-net-ria-servies.aspx>