Showing posts with label RIA Services. Show all posts
Showing posts with label RIA Services. Show all posts

Saturday, August 21, 2010

Silverlight RIA Data Validation Best Practices

Silverlight Data and Controls Series

Introduction

I have been building a pretty heavy forms Silverlight application recently and I wanted to share some simple approaches on how you can handle data validation. There are really tons of options and you can potentially get a lot for free. I am using Silverlight 4 with RIA Services to convert a large scale tradition rich client application to be web based. If we did not have a technology like Silverlight it would be pretty much impossible to build this application without creating significant amounts of Javascript and AJAX – both of which I just plain avoid in all my solutions because of the long term development and maintenance cost of them. Silverlight is what we have wanted since ASP.net can out.

Back to Silverlight validation; there are a couple options you have:

  1. Declarative - Validation Attribute on Property
  2. Custom Attribute Validation (Class or Property)
  3. RIA Service Validation
  4. Silverlight Client Validation (and Asynchronous Validation)

Note that I had to get an understanding of much of this because I had to build a custom RIA Service to hook into a large, legacy non-Microsoft bend database what was not going to change anytime soon. I could not benefit from much of the free stuff you get with Entity. In my case, I create custom DTOs that get filled in the service layer and then passed through a RIA Service to the Silverlight Application.

In this blog I will cover the pros and cons of each and provide simple examples to get you started.

1 - Declarative Validation - Validation Attribute on Property

This is probably the most simple and easiest validation you have in your arsenal. Much of the validations you have are located within System.ComponentModel.DataAnnotations. There are several however the ones that I effectively have used the most are:

  • Key – Enforces uniqueness.
  • Range – Sets a range of values that numeric type can be in.
  • RegularExpression – Regular expression applied to a property.
  • Required – Indicates if a value must be present.
  • StringLength – Indicates the length that a string type property can be.
  • Editable – Indicates if the property can be edited.

Here is a simple example. As you can see in this example I have some of the most basic validations I need.

public class Employee
{
[Key]
public int ID { get; set; }

[Required]
public DateTime DOB { get; set; }

[Required]
[RegularExpression(@"^(?!000)([0-6]\d{2}7([0-6]\d7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$")]
public string SSN { get; set; }

[Required]
[StringLength(20)]
public string FirstName { get; set; }

[Required]
[StringLength(30)]
public string LastName { get; set; }
}

Pros:

  • For Free - These validations pretty much kick in for free once you bind your object instance to a control in Silverlight. If any of these rules are violated when a value is set, the user will be displayed a message and the field will be highlighted in Red.
  • No RIA Round Trips - If any of the validations have been violated, the user will not be able to submit changes back to the RIA Services. This is good because there is no round trip involved to RIA for the validations to kick in. The validations are actually projected into the Generated_Code of the DTO class type in the Silverlight Application.

Cons:

  • Basic Only - You only get very basic validations.

2 - Custom Attribute Validation (Property or Class Level)

Custom validations can be created and the applied to the class in the similar manner as the declarative manner described above. You will quickly need to do something like this so that you can perform custom validations across multiple fields or against the whole class.

To implement you need to create a class and name the class file XXX.Shared.cs to the location where you have your DTOs defined. Then add some validation logic and reference as CustomValidation attribute on the DTO.

You will notice after you do a build, a copy of the XXX.Shared.cs file will be copied to the Generated_Code folder in the Silverlight Application. What this basically means is that the validation will be run on the Silverlight Application side and not require a round trip to RIA.

Here is an example where I have:

  • I have a validation called IsDOBValid which will validate the date.
  • I have a validation called IsPostEmployeementDateValid which will check to see PostEmployeementDate is correct.
  • I also have an IsActiveValid which performs a similar validation.

I then have IsEmployeeValid which is a class level validation. I have no implementation because I could not come up with a good example but I wanted to show you that it can be used.

using System;
using System.ComponentModel.DataAnnotations;

namespace SilverlightApplication2.Web
{
public class EmployeeValidation
{
public static ValidationResult IsEmployeeValid(Employee employee, ValidationContext context)
{
//Place in Employee Scope validations
return ValidationResult.Success;
}

public static ValidationResult IsDOBValid(DateTime dob, ValidationContext context) {

if (dob == DateTime.MinValue)
{
return new ValidationResult("The Employee DOB is set.",
new string[] { "DOB" });
}

if (dob >= DateTime.Now)
{
return new ValidationResult("The DOB cannot be greater than today.",
new string[] { "DOB" });
}

return ValidationResult.Success;
}

public static ValidationResult IsPostEmployeementDateValid(DateTime postEmployeementDate, ValidationContext context)
{

if (context.ObjectInstance != null)
{
Employee employee = context.ObjectInstance as Employee;
return ValidatePostEmployeementDate(postEmployeementDate, employee.Active);
}

return ValidationResult.Success;
}

public static ValidationResult IsActiveValid(bool active, ValidationContext context)
{

if (context.ObjectInstance != null)
{
Employee employee = context.ObjectInstance as Employee;
return ValidatePostEmployeementDate(employee.PostEmployeementDate, active);
}

return ValidationResult.Success;
}

private static ValidationResult ValidatePostEmployeementDate(DateTime postEmployeementDate, bool active)
{
if (active == false && (postEmployeementDate == null postEmployeementDate == DateTime.MinValue))
{
return new ValidationResult("The Post Employeement Date must be set if the Employee is inactive.",
new string[] { "PostEmployeementDate", "Active" });
}

return ValidationResult.Success;
}

}
}

One observation is I use the value that is passed in to do the validation and not the value in the Employee object when the validation is used on a property. For instance on IsActiveValid I use active and not employee.Active. This is because the value passed in is the value that changed and employee.Active has the old value. This is actually helpful if you need to check to see how the values have changed.

The following modifications to the DTO I created above:

  • First I put the class level validation at the Employee class level.
  • Second I put the three property level validations.
[CustomValidation(typeof(EmployeeValidation), "IsEmployeeValid")]
public class Employee
{
[Key]
public int ID { get; set; }

[Required]
[CustomValidation(typeof(EmployeeValidation), "IsDOBValid")]
public DateTime DOB { get; set; }

[Required]
[RegularExpression(@"^(?!000)([0-6]\d{2}7([0-6]\d7[012]))([ -]?)(?!00)\d\d\3(?!0000)\d{4}$")]
public string SSN { get; set; }

[Required]
[StringLength(20)]
public string FirstName { get; set; }

[Required]
[StringLength(30)]
public string LastName { get; set; }

[Required]
[CustomValidation(typeof(EmployeeValidation), "IsActiveValid")]
public bool Active { get; set; }

[Required]
public DateTime HireDate { get; set; }

[CustomValidation(typeof(EmployeeValidation), "IsPostEmployeementDateValid")]
public DateTime PostEmployeementDate { get; set; }
}

There are several things you should know about these before jumping in and using them across the board. I recommend using these validations when you have simple/medium level validations you need to do.

Pros:

  • Medium Complex Validations - These are great because they do allow you to put in medium complexity level sort of validations for your objects which was missing from the first example I presented.
  • User Notification - Validations will be fired immediately when a value is set into a property level custom validation.

Cons:

  • Class Level User Notification - Class level validations are executed on the client, however they are only evaluated when the context.SubmitChanges() method is called..
  • DataGrid Limitation - If you are using a DataGird, none validations using this pattern will be fired immediately when a user sets a value. This is because the EndEdit event of the DataGrid row needs to be called first and then these validations will kick in. This will become a problem for you if the user needs to set a value in a grid, press tab and need to see the error immediately.
  • Reference Other Library - Library dependencies are major limitation of this solution. You will be tempted to do a complex validation logic in the XXX.Shared.cs class file (i.e. call out to a database and check something). An example would be to reference a service layer library and call a method on it. Then based on the result send a validation error. This will probably compile in your DTO Library because it may have a reference to the service layer library. However when the XXX.Shared.cs is copied to the Silverlight Application into the Generated_Code folder, the Silverlight Application will not compile. This I because the Silverlight Application does not have a direct reference to you the service layer library DLL. Nor can you fix that because you service layer library DLL is written in straight up .NET code library which a Silverlight Application cannot reference.
  • No Asynchronous Validations – This pattern cannot perform asynchronous validations either. This is again because XXX.Shared.cs is defined in the DTO project library and not in your Silverlight Application. So you cannot use any of the RIA services to assist you with you validations.

3 - RIA Service Validations

If you read my blog on RIA Service Method Lifecycle, there is a method called ValidateChangeSet which you can override and perform validations on just the RIA side. This is called before ExecuteChangeSet which is where much of your database transaction logic will reside.

The code below is not meant to be a good code sample, just so you the basic mechanics for working with overriding the ValidateChangeSet method.

protected override bool ValidateChangeSet()
{
bool isValid = base.ValidateChangeSet();

if (IsSomeValidation() == false) {
isValid = false;
ValidationResult validationResult = new ValidationResult("Blah Blah.", new string[] { "PostEmployeementDate" });
throw new ValidationException(validationResult, null, null);
}

return isValid;
}

Pros:

  • Fail Safe Errors – If you have some validations that need to always occur and you want to make sure 100% they are evaluated before any commits occur, this is the place to put them.

Cons:

  • User Will Not Immediately See – The user will not immediately see these errors and have to wait for a RIA round trip.

4 - Silverlight Client Validation (and Asynchronous Validation)

I have found through my Silverlight development that I have ultimately settled on doing validations this way more often than not. If you recall, the Employee DTO definition was projected by RIA to the Generated_Code folder.

If you go look in the generated class, you will see that all of the Employee DTO properties look similar to the following:

/// <summary>
/// Gets or sets the 'DOB' value.
/// </summary>
[CustomValidation(typeof(EmployeeValidation), "IsDOBValid")]
[DataMember()]
[Required()]
public DateTime DOB
{
get
{
return this._dob;
}
set
{
if ((this._dob != value))
{
this.OnDOBChanging(value);
this.RaiseDataMemberChanging("DOB");
this.ValidateProperty("DOB", value);
this._dob = value;
this.RaiseDataMemberChanged("DOB");
this.OnDOBChanged();
}
}
}

Here you see I pulled out the DOB property of the Employee class that is in the Generated_Code folder. There are some interesting things here you should be aware of:

  • OnDOBChanging it a method that can be overridden to perform a task before the values is actually set.
  • RaiseDataMemeberChange is a notification that the DOB value is about to be changed.
  • ValidateProperty is a method that will execute all of the declarative validations (which I discussed earlier). If it fails, it will not continue.
  • Next the value is set.
  • RaiseDataMemberChanged is a notification that the value has changed. This will actually part of the INotifyPropertyChanged interface implementation and will notify the UI binding with the updated value.
  • OnDOBChanged is a method you can override after the value has been set.

In this pattern we will simply extend the generated code by creating a partial class so that the code we create does not get lost every time the code the solution is built:

  • First create a matching partial class in the Silverlight Application for the DTO.
  • Next you override the OnXXXChanging or OnXXXChanged methods.

Here is a simple example:

using System;
using System.ServiceModel.DomainServices.Client;
using System.ComponentModel.DataAnnotations;

namespace SilverlightApplication2.Web
{
partial class Employee
{
ValidationResult someValidationResult;

partial void OnPostEmployeementDateChanged()
{
//Call RIA Method
SimpleContext _simpleContext = new SimpleContext();
_simpleContext.SomeValidation(this.PostEmployeementDate, SomeValidationComplete, null);
}

private void SomeValidationComplete(InvokeOperation<bool> io)
{
if (io.HasError)
{
io.MarkErrorAsHandled();
//do something
}
else
{
if (io.Value)
{
//Remove the error.
if (someValidationResult != null) {
this.ValidationErrors.Remove(someValidationResult);
someValidationResult = null;
}
}
else {
//There was an error
someValidationResult = new ValidationResult("Error Message.", new string[] { "PostEmployeementDate" });
this.ValidationErrors.Add(someValidationResult);
}
}
}
}
}

In the code you will see that:

  • I called a simple RIA Method that returns a bool.
  • I override the OnPostEmployeementDateChanged method and you will note that I did not have to make it is override. All I did was mark it as partial. Now this will be called after a value has been successfully set into the PostEmployeementDate property.
  • In the call back, I check the results and will add a ValidationResult if there is an error.

Pros:

  • Asynchronous Validation – This is a huge plus versus the other validation patterns. This is needed when you got to check an input value against some complex routine which may be database driven.
  • Extending Your DTOs for the UI – This is a great solution because what you can do is define DTOs in the RIA layer with no functionality. Then in the Silverlight layer you can extend your DTOs to have much more functionality and business logic that is only associated to the Silverlight application.

Cons:

  • Validation Not Evaluated on RIA Side – Using the approaches above the validations are fired on both the Silverlight Application side, as well as the RIA side. Obviously the benefit is that your DTOs will always have the rules applied.

Saturday, August 14, 2010

Project Silverlight RIA Methods to Access All Data in Context

Silverlight Data and Controls Series

Background

I have a little trick I have been doing lately with my custom RIA Services to be able to access all the objects that get returned the Context on the Silverlight Application Side. The Context object, which is generated for each RIA Domain Service you have, is a bunch of hash table like collection of objects when you look underneath the hood.

The code generation I just mentioned is commonly referred to as “code projection”. What happens is when you compile your Silverlight application that is using RIA Services you will see a hidden folder called Generated_Code. In there you will find several classes have been generated. First is the context class which is generated for each RIA Domain Service you have. This class will have all the RIA methods you have defined. Second you will see tons of classes in there for each object type that is returned out of the RIA Services. In my case, I have tons of custom DTOs being returned from the RIA methods, so there will be a corresponding type returned.

Here is an example of a RIA method you may create:

[EnableClientAccess()]
public class SimpleService : DomainService
{
public List<ClassA> GetClassAs()
{
return DataService.GetClassAs();
}
}

You will notice that is an extremely simple method and it is returning a collection of DTOs from some data tier. In this example, the ClassA DTO has two encapsulated DTO lists within it (i.e. a one-to-many relationship of data). Here are the definitions to the three DTOs and their relationships to each other:

public class ClassA
{
[System.ComponentModel.DataAnnotations.Key]
public int ID { get; set; }
public string ValueA { get; set; }
private List<ClassB> _classBs = new List<ClassB>();

[Include]
[Association("ClassA_ClassB", "ID", "ClassAID")]
public List<ClassB> ClassBs {
get {
return _classBs;
}
}
}

public class ClassB
{
[System.ComponentModel.DataAnnotations.Key]
public int ID { get; set; }
public int ClassAID { get; set; }
public string ValueB { get; set; }
private List<ClassC> _classCs = new List<ClassC>();

[Include]
[Association("ClassB_ClassC", "ID", "ClassBID")]
public List<ClassC> ClassCs
{
get
{
return _classCs;
}
}
}

public class ClassC
{
[System.ComponentModel.DataAnnotations.Key]
public int ID { get; set; }
public int ClassBID { get; set; }
public string ValueC { get; set; }
}

Then in your Silverlight application, you will call the RIA Service to get your data like the following. As you can see I can access all ClassAs in a LINQ query but I cannot access all of the ClassCs in this manner. In the code below, you will see there is a compile error.

private void GetData() {
_simpleContext = new SimpleContext();
_simpleContext.Load(_simpleContext.GetClassAsQuery(), GetClassAsLoaded, null);
}

private void GetClassAsLoaded(LoadOperation<ClassA> lo)
{
if (lo.HasError)
{
lo.MarkErrorAsHandled();
//do something
}
else
{
//Compiles
var foos = from A in _simpleContext.ClassAs
where A.ValueA == "Foo"
select A;

//Will NOT COMPILE
var bars = from C in _simpleContext.ClassCs
where C.ValueC == "Bars"
select C;
}
}

This was pretty annoying because if I put a breakpoint on context and then do a quick watch I was easily able to view the data. It was even more annoying because RIA enforces that each object instance be unique within the context (SimpleContext in my example). So why can I not just be able to access the list of ClassCs directly to perform an operation. The only solution you would think is to loop over ClassA, then ClassB, and then ClassC.

Solution

The solution I came up with is rather simple. On the RIA service add the following method.

[EnableClientAccess()]
public class SimpleService : DomainService
{
public List<ClassA> GetClassAs()
{
return DataService.GetClassAs();
}

public List<ClassB> GetClassBs()
{
return new List<ClassB>();
}

public List<ClassC> GetClassCs()
{
return new List<ClassC>();
}
}

What this will do is project properties on the context class definition so I can directly access the collection of encapsulated objects. Notice the two new methods have no implementation, nor is any needed either. Now the code above will compile. This example is not really the compelling however when you have a very large custom RIA Service layer you are writing for a big Silverlight application, you will find this very useful.

Saturday, July 31, 2010

DomainOperationException Overriding Custom Exceptions

Issue

We ran into a rather silly issue with a recent Silverlight deployment. In our RIA services is it not uncommon for use to throw custom exceptions from the RIA side and then have our Silverlight application display them to the user.

For instance we would throw a custom exception if there was an issue with the login and we would expect the following to be "User and password is invalid" to be displayed. However we were getting "Load operation failed for query 'Login'. Exception of type 'System.ServiceModelServices.Client.DomainOperationException' was thrown."

What we noticed is anytime we through a custom exception it was being returned as DomainOperationException instead of the custom exception type. This drove us nuts because it was working in each of our development environments but not on this integration machine we were using for testing. We did some searches on DomainOperationException but really could not find much that helped. We did find this blog (http://weblogs.asp.net/fredriknormen/archive/2009/12/08/wcf-ria-services-exception-handling.aspx) however it was poorly written.

Solution

Well the solution was really simple. All we had to do was make the following modification to our web.config where the RIA Services were hosted.

<customErrors mode="Off"/>

Sunday, June 13, 2010

Silverlight RIA Services Load and Performance Testing

Introduction

I was able to do some initial unit testing of my RIA Service methods using Silverlight unit test projects. Now I needed to do some performance/load testing of the asynchronous RIA Services methods. After doing some research I quickly found out that I could not use the Enqueue* methods that come with the Silverlight Unit Testing frame; my solution using multithreading.

The goal of the performance/load testing is to ensure that the new RIA Services do not become a bottleneck. The system we are writing needs to be able to support lots of transactions and some transactions take a long time to calculate in the database end. Even though the user experience is acceptable because RIA easily supports asynchronous transactions, we have to be concerned about the amount of resources being consumed on the IIS machine which is hosting the RIA Services.

Some colleagues had suggested to me to write unit tests outside of the Silverlight and call the RIA Service methods using WCF directly. However I believe that is not a good solution because:

  • I cannot use the generated code that call the WCF RIA methods and I would have to subsequently write a lot of new code to call those services. Plus if there are inefficiencies in the generated code, they would not be discovered.
  • I cannot use my ViewModels which we had spent a lot time developing so they would be testable.

The Solution

I was able to come up with a simple solution that allows me to use Silverlight Unit Test project to call my RIA Services. The solution is to:

  • Initiate threads from the test method to call the RIA Service methods.
  • Write logs out to a log file that captures the duration of each RIA Service call.

Another nice thing about this solution is I have the ability to install the Unit Test Silverlight page onto the eventual server and run the unit tests from a client machine.

I basically had to relearn System.Threading and I was able to get things to work. Below I will show you the code on how to do some basic load testing of your RIA Services. I still like run the load/performance tests off a development environment. This is because production machines are more powerful and will hide performance errors. It is better to discover them on a machine that less resources.

The Code

I highly recommend reading the last section this blog so you understand the design, issues and challenges so you understand why I did what I did. I know many just look for code to copy – I know I do <g>

This is the test class. In it you can see that all I do is create a log file that will be used by all the threads to write to. I then initiate the search threads by calling a class called SearchTestScenario.

[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;
bool blnSearch1Complete = false;
bool blnSearch2Complete = false;
bool blnSearch3Complete = false;

[TestMethod]
public void TestMethod()
{
string fileName = CreateLogFile();

SearchTestScenario searchTest1 = new SearchTestScenario(fileName);
Thread thread1 = new Thread(new ThreadStart(searchTest1.Search1));
thread1.Start();

SearchTestScenario searchTest2 = new SearchTestScenario(fileName);
Thread thread2 = new Thread(new ThreadStart(searchTest2.Search2));
thread2.Start();

SearchTestScenario searchTest3 = new SearchTestScenario(fileName);
Thread thread3 = new Thread(new ThreadStart(searchTest3.Search3));
thread3.Start();
}

private string CreateLogFile()
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
string strLogFileName = System.DateTime.Now.ToString().Replace("/", "_").Replace(":", "_").Replace(" ", "_") + "_Search.txt";
IsolatedStorageFileStream rootFile = store.CreateFile(strLogFileName);
rootFile.Close();

return strLogFileName;
}
}

}

Here is SearchTestScenario. In this class I write methods that will execute searches and do some logs. I have not added anything into the call backs but that would be a good place to do some Asserts.

public class SearchTestScenario
{

private MyBusinessContext context;
private System.DateTime duration;
private string fileName;

public SearchTestScenario(string fileName)
{
context = new MyBusinessContext();
this.fileName = fileName;
}

public void Search1()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search1Complete, null);

Log("Search1 Started - " + duration.ToString());
}

public void Search2()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search2Complete, null);

Log("Search2 started - " + duration.ToString());
}

public void Search3()
{
duration = System.DateTime.Now;
context.Load(context.SearchQuery("foo", "bar", "blah"), Search3Complete, null);

Log("Search3 Search - " + duration.ToString());
}

private void Search1Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search1Complete Duration - " + span.Seconds.ToString());
}

private void Search2Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search2Complete Duration - " + span.Seconds.ToString());
}

private void Search3Complete(LoadOperation<SearchResult> oLoadOperation)
{
TimeSpan span = System.DateTime.Now.Subtract(duration);
Log("Search3Complete Duration - " + span.Seconds.ToString());
}

static readonly object lockObject = new object();
private void Log(string message)
{
lock (lockObject)
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream rootFile = store.OpenFile(fileName, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(rootFile))
{
sw.WriteLine(message);
sw.Close();
}
rootFile.Close();
}
}
}
}

}

Design, Issues and Challenges

I have to admit I ran into a lot of problems trying to get this simple solution working. I had to relearn threading but there were tons of issues I had to address. Here is a short list of them.

  • I elected to not use a single instance of RIA Service context and pass that from the main thread into the running threads. This is because you get locking issues when access that object instance across threads. Even though I could lock it, I figured there was no value to it either.
  • For someone who is experienced with threading you will probably ask why did I not join all the running search threads back together when the searches were completed. Let me tell you I tried and burned lots of hours. I tried a solution where I used WaitHandle.WaitAll with ManualResetEvent for each running search thread. The problem was when a RIA Service method was called the entire unit test would freeze. I found this which was very similar to what I was experiencing. The net effect is the unit test will complete and if any assertions or errors occur, they will be treated as exceptions and not be picked up by the unit testing framework.
  • I do not create a single search method on SearchTestScenario because I may want to test for different thresholds for each search query.
  • I have to write the results to isolated storage because that was the easiest thing to get access to very quickly.

Relearn Threading Resources

Saturday, June 12, 2010

Silverlight Asynchronous Unit Test MVVM and RIA Services

Introduction

I am building up a Silverlight application and we needed to incorporate unit testing in our project. Specifically we needed to unit test our asynchronous RIA Services method calls. I needed to find some examples of this and did not find any really good clean examples. There is really nothing that just gives you a simple example of how to get started.

I was able to get things working and I decided to put together three very basic examples that will give you a place to start from. The first two just give you an understanding of how things work and the third will test a RIA method that resides in a ViewModel.

It is possible to call asynchronous RIA methods inside of a Silverlight unit test without doing what I am about to show you. However doing it this way you can:

  • Really quickly build up unit tests that use your ViewModels.
  • Allow you to capture the full duration of the unit.
  • Avoid issues with asserts failing in asynchronous callbacks which will result in all the unit tests stopping.

Setting Up Asynchronous Unit Test

The steps for setting up an asynchronous unit test are pretty simple:

  • Add a unit test to your Silverlight unit test project.
  • Modify the unit test to inherit from Microsoft.Silverlight.Testing.SilverlightTest.
  • Decorate your test method with Microsoft.Silverlight.Testing.Asynchronous.

Building Up Asynchronous Unit Test

When you inherit from SilverlightTest you will have access to some methods that will help you build up your asynchronous unit test.

  • EnqueueTestComplete() – This method should be used when the test is complete. You can place in your call backs and this will signify the test is over.
  • EnqueueCallback() – This method adds a Callback method into the test queue.
  • EnqueueConditional() – This basically tells the test queue to wait until the condition is true.
  • EnqueueDelay() – Add a delay before continuing to evaluate work items.
  • EnqueueWorkItem – This adds a task to the task queue.

Example 1 – Simple RIA Callback

Here is my first simple example. In this sample we will:

  • Log into Silverlight application.
  • Then call my RIA method to perform a search.
  • Then in the callback of the search, do a check to see if results were returned.
  • All I need to do is add EnqueueTestComplete() into my callback.
[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;

[Asynchronous]
[TestMethod]
public void TestMethod1()
{
LoginParameters userCreds = new LoginParameters("UserName", "Password");
WebContext.Current.Authentication.Login(userCreds, LoginOperation_Completed, null);
}

private void LoginOperation_Completed(LoginOperation loginOperation) {

context = new MyBusinessContext();

context.Load<SearchResult>(context.SearchQuery("foo", "bar", "blah"), SearchCompleted, null);
}

private void SearchCompleted(LoadOperation<SearchResult> loadOperation) {
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
Assert.Fail(loadOperation.Error.Message);
}
else {
Assert.IsTrue(context.SearchResults.Count > 0);
}

this.EnqueueTestComplete();
}

}

Example 2 – Multiple RIA Callbacks

Here on my second example, let’s add search two callbacks.

You will notice in this one I use EnqueueConditional() to evaluate that each search callback work item is completed. Then I call EnqueueComplete() after both of the EnqueueConditional() methods have been called.

[TestClass]
public class Tests : SilverlightTest
{
MyBusinessContext context;
bool blnSearch1Complete = false;
bool blnSearch2Complete = false;

[Asynchronous]
[TestMethod]
public void TestMethod1()
{
LoginParameters userCreds = new LoginParameters("UserName", "Password");
WebContext.Current.Authentication.Login(userCreds, LoginOperation_Completed, null);
}

private void LoginOperation_Completed(LoginOperation loginOperation) {

context = new MyBusinessContext();

context.Load<SearchResult>(context.SearchQuery("foo", "bar", "blah"), Search1Completed, null);
context.Load<SearchResult>(context.SearchQuery("blah", "yop", "flop"), Search2Completed, null);

this.EnqueueConditional(() => blnSearch1Complete);
this.EnqueueConditional(() => blnSearch2Complete);
this.EnqueueTestComplete();
}

private void Search1Completed(LoadOperation<SearchResult> loadOperation)
{
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
Assert.Fail(loadOperation.Error.Message);
}
else {
Assert.IsTrue(context.SearchResults.Count > 0);
}

blnSearch1Complete = true;
}

private void Search2Completed(LoadOperation<SearchResult> loadOperation)
{
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
Assert.Fail(loadOperation.Error.Message);
}
else
{
Assert.IsTrue(context.SearchResults.Count > 0);
}

blnSearch2Complete = true;
}

}

Example 3 – Testing ViewModel Asynchronous Methods

So now you may be wondering how to test ViewModels with your RIA method calls. Here is a simple ViewModel that performs a search which could be bound to a Silverlight control.

  • You see the search callback I part of the ViewModel.
  • There are IsSearching and IsSearchComplete properties that indicate the status of the search.
  • There is a property that returns a collection of search results.

public class MyViewModel
{
MyBusinessContext _context;
bool _IsSearchComplete = false;

public MyViewModel() {
_context = new MyBusinessContext();
}

public void Search(string Criteria1, string Criteria2, string Criteria3)
{
_IsSearchComplete = false;
_context.SearchResults.Clear();
_context.Load<SearchResult>(_context.SearchQuery(Criteria1, Criteria2, Criteria3), SearchCompleted, null);
}

private void SearchCompleted(LoadOperation<SearchResult> loadOperation)
{
if (loadOperation.HasError)
{
loadOperation.MarkErrorAsHandled();
}

_IsSearchComplete = true;
}

public EntitySet<SearchResult> GeSearchResults {
get {
return _context.SearchResults;
}
}

public bool IsSearching {
get {
return _context.IsLoading;
}
}

public bool IsSearchComplete {
get {
return _IsSearchComplete;
}
}
}


The following is a Silverlight Unit Test that will test this asynchronous method call:

  • First we create an instance of the ViewModel.
  • I then use the EnqueueCallback() method to call the Search method which has an asynchronous callback within the ViewModel. This basically puts the method onto the queue.
  • Then I call EnqueueConditional() where I set the ViewModel.IsSearchComplete to be evaluated to check to see of the asynchronous search operation has been completed. This basically blocks the queue from continuing until this is true.
  • Next I call EnqueueCallback() again and set an Assert to check to see if search results were returned. This assert will be evaluated after the EnqueueConditional() is complete.
  • Finally EnqueueTestComplete() is called to tell the queue that testing is over.

[TestClass]
public class Tests : SilverlightTest
{

[Asynchronous]
[TestMethod]
public void TestMethod1()
{
LoginParameters userCreds = new LoginParameters("UserName", "Password");
WebContext.Current.Authentication.Login(userCreds, LoginOperation_Completed, null);
}

private void LoginOperation_Completed(LoginOperation loginOperation) {

MyViewModel myViewModel = new MyViewModel();
EnqueueCallback(() => myViewModel.Search("foo", "bar", "blah"));
EnqueueConditional(() => myViewModel.IsSearchComplete);
EnqueueCallback(() => Assert.IsTrue(myViewModel.GeSearchResults.Count > 0));
EnqueueTestComplete();

}

}

Conclusions

Once I got the hang of this, I really saw how simple it is to put together good unit tests that exercise my Silverlight ViewModels and RIA Service calls.

References

Thursday, June 10, 2010

Silverlight Unit Test Project Templates Compile Error

Introduction

Silverlight Unit Test project templates work perfect fine Silverlight Application project templates for Silverlight 4 and Visual Studio 2010. They will compile right off the bat. However Silverlight Business Application project templates will not immediately compile with a Silverlight Unit Test project template.

If you were to:

  1. Create a Silverlight Business Application.
  2. Then add Silverlight Unit Test. Enable WCF RIA Services and select the Business Application project as the Host.
  3. Then compile without making any changes.

What will result is compile errors and you will get different compile errors based on using C# or VB.net

C# Template

The following are the errors you would see in C#. To resolve these errors all you need to do is add a reference to the Silverlight Business Application to the Silverlight Unit Test project.

The annoying thing about this bug is that when you add the Silverlight Unit Test project the expectation is that this reference would be created since you selected the Business Application as the hosting project. You do not have to do this with Silverlight Applications.

Error 1 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 299 101 SilverlightTest1

Error 2 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 334 92 SilverlightTest1

Error 3 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 367 138 SilverlightTest1

Error 4 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 398 103 SilverlightTest1

Error 5 The type or namespace name 'Resources' does not exist in the namespace 'BusinessApplication1.Web' (are you missing an assembly reference?) C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.cs 437 95 SilverlightTest1

VB.net Template

You will get a couple more errors when doing VB.net. To resolve first add a reference to the Business Application project as we did for the C# solution. That will resolve errors 6, 8, and 9 however 1 to 5 will still remain. This is pretty confusing because doing that resolved the same issues for VB.net.

Next you need to either the following:

  1. Go to the Silverlight Unit Test Project properties. Change the Root Namespace to BusinessApplication1 in this case.
  2. Or go the BusinessAppllication1.Web project (the RIA Services project), go to the Models folder, and modify RegistrationData.vb. You will need to remove all reference to RegistrationDataResources in that class. What is happening is the RegistrationDataResources.resx is not projecting to the Silverlight Unit Test project and subsequently creating reference errors.

Error 1 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 305 79 SilverlightTest1

Error 2 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 336 70 SilverlightTest1

Error 3 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 365 117 SilverlightTest1

Error 4 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 392 81 SilverlightTest1

Error 5 Type 'BusinessApplication1.RegistrationDataResources' is not defined. C:\BusinessApplication1\SilverlightTest1\Generated_Code\BusinessApplication1.Web.g.vb 427 73 SilverlightTest1

Error 6 'FriendlyName' is not a member of 'SilverlightTest1.Web.User'. C:\BusinessApplication1\SilverlightTest1\Generated_Code\Models\Shared\User.shared.vb 10 45 SilverlightTest1

Error 7 'FriendlyName' is not a member of 'SilverlightTest1.Web.User'. C:\BusinessApplication1\SilverlightTest1\Generated_Code\Models\Shared\User.shared.vb 11 28 SilverlightTest1

Error 8 'Name' is not a member of 'SilverlightTest1.Web.User'. C:\BusinessApplication1\SilverlightTest1\Generated_Code\Models\Shared\User.shared.vb 13 28 SilverlightTest1

Wednesday, May 5, 2010

Encountered Silverlight RIA Services Errors

I ran into a couple errors while working with Silverlight and RIA Services today and figure I provide the resolutions to them real quick if you run up against them.

Entity Referenced Across Multiple Domain Services

I got the following error when I created a DTO/Entity object and then used the same DTO/Entity in two different Domain Services. I was a little surprised to see that using the same DTO/Entity across Domain Services would cause an error when the proxy to the RIA service is being built in the Silverlight project.

The entity type 'XXX' is exposed by multiple DomainService types. Entity types cannot be shared across DomainServices.

The resolution is to move you code and put all of the methods that use the DTO/Entity into the same Domain Service.

Method Parameters Not Serializable

The error message is very obvious in that it is stating that primitive types like int, string, date, etc. can only be used. However there are some subtleties that you should understand.

Parameter 'XXX' of domain operation entry 'XXX' must be one of the predefined serializable types.

First you will get this error when you pass a complex type into a Domain Service method. However you will not get this error when you pass a complex type to an insert, update, delete entity method. I found that a little confusing.

Second I was actually coding this up in VB.net for the client. You will get this error if any of the parameters for a method are ByRef and not ByVal. You will get this whether you are passing in a primitive type or not.

Domain Service Methods Not Set Up Properly

Here is another error you may get if you do not set up your Domain Service methods incorrectly when the proxy is built in the Silverlight project.

Parameter 'XXX' of domain method 'XXX' must be an entity type exposed by the DomainService, either directly via a query operation, or indirectly via an included association.

This error is basically saying that I have an Insert, Update or Delete method but I have no methods that return an object of that type or the object type is not referenced through an association. Basically it is saying that an Insert, Update and Delete method cannot exist for an object type if it is never loaded – which makes sense.

You can also potentially get this error if you do not have a Key attribute defined on your DTO/Entity.

One scenario is that you may hypothetically only ever need an insert of a DTO/Entity so you will not want to have a get method. What you should do is just add an empty get method that returns a list of that object type. If you want, throw an exception when that method is called. What this will do is suppress the error and allow you to just perform inserts.

Saturday, April 24, 2010

Deep Dive into Custom RIA Service and Transactions

Silverlight Data and Controls Series

Introduction

In one of my previous blogs, I gave a quick introduction on MVVM, RIA Services and building a Custom Domain Service. Then in another blog I gave an introduction to the life-cycle of domain service methods. In this blog I am going to build upon them and have a deeper discussion on how to build up a custom RIA Domain Service with Data Transactions Objects (DTOs) that need to can connect to an existing data service layer which may not be something like Entity.

I had not intended for this blog to be as long as it is but it turned into a deeper dive than I expected. I found out some rather interesting things in the process. What I will discuss in depth is how to create associations between DTOs and how the ChangeSet is utilized with RIA Services.

DTOs

Here is the data model I created for this working example. In this example I am going to have an EmployeeDTO and a ProjectDTO. Every employee can have zero to many projects. As well a project can have many employees, so we will want to put an association table between the two called EmployeeProjectDTO. A typical many-to-many relationship.

Here are my three DTOs needed. As you can see the only relationships I have are the foreign key references however we can do better.

using System;
using System.ComponentModel.DataAnnotations;

namespace MVVM.Web.Model
{
public class EmployeeDTO
{
[Key]
public int ID { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime HireDate { get; set; }

public int Title { get; set; }
}
}

using System;
using System.ComponentModel.DataAnnotations;

namespace MVVM.Web.Model
{
public class ProjectDTO
{
[Key]
public int ID { get; set; }

public string Name { get; set; }

public DateTime StartDate { get; set; }

public int RegionalDirectorID { get; set; }
}
}

using System.ComponentModel.DataAnnotations;

namespace MVVM.Web.Model
{
public class EmployeeProjectDTO
{
[Key]
public int ID { get; set; }

public int EmployeeID { get; set; }

public int ProjectID { get; set; }

public bool IsProjectManager { get; set; }

public bool IsProgramDirector { get; set; }
}
}

Associating DTOs

Now that we have three DTOs, it is time to add some associations. What I am going to do is:

  • Add a list of EmployeeProjectDTO objects to EmployeeDTO to contain the list of projects an employee.
  • Add a list of EmployeeProjectDTO objects to ProjectDTO to contain a list of all the employees working on a project.
  • Add an EmployeeDTO property to ProjectDTO for the regional director.

Here are my DTOs again. Please take note of the new attributes that I have added to my properties that expose lists or complex types. Setting these up will ensure that objects will be returned and that when the data is submitted, insert, updates and deletes in the ChangeSet will be called in the correct order.

Please also take note that the foreign keys on all of the tables still remain. For example look at EmployeeDTO’s association to EmployeeProjectDTO. Notice that references “EmployeeID” and not “Employee.ID”. If you tried to use “Employee.ID” you will get an error saying “invalid: OtherKey property named 'Employee.ID' cannot be found on entity type”.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Server;

namespace MVVM.Web.Model
{
public class EmployeeDTO
{
private List<EmployeeProjectDTO> _employeeProjects = new List<EmployeeProjectDTO>();

[Key]
public int ID { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime HireDate { get; set; }

public int Title { get; set; }

[Include]
[Composition]
[Association("EmployeeDTO_EmployeeProjectDTO", "ID", "EmployeeID")]
public List<EmployeeProjectDTO> EmployeeProjects {
get {
return _employeeProjects;
}
}
}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Server;

namespace MVVM.Web.Model
{
public class ProjectDTO
{
private List<EmployeeProjectDTO> _employeeProjects = new List<EmployeeProjectDTO>();
private EmployeeDTO _projectRegionalDirector;

[Key]
public int ID { get; set; }

public string Name { get; set; }

public DateTime StartDate { get; set; }

public int RegionalDirectorID { get; set; }

[Include]
[Association("ProjectDTO_RegionalDirectorEmployeeDTO", "RegionalDirectorID", "ID", IsForeignKey = true)]
public EmployeeDTO RegionalDirector {
get {
return _projectRegionalDirector;
}
set {
_projectRegionalDirector = value;
RegionalDirectorID = _projectRegionalDirector.ID;
}
}

[Include]
[Composition]
[Association("ProjectDTO_EmployeeProjectDTO", "ID", "ProjectID")]
public List<EmployeeProjectDTO> EmployeeProjects
{
get
{
return _employeeProjects;
}
}
}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel.DomainServices.Server;

namespace MVVM.Web.Model
{
public class EmployeeProjectDTO
{
private EmployeeDTO _employee;
private ProjectDTO _project;

[Key]
public int ID { get; set; }

public int EmployeeID { get; set; }

public int ProjectID { get; set; }

public bool IsProjectManager { get; set; }

public bool IsProgramDirector { get; set; }

[Include]
[Association("EmployeeProjectDTO_EmployeeDTO", "EmployeeID", "ID", IsForeignKey = true)]
public EmployeeDTO Employee
{
get {
return _employee;
}
set {
_employee = value;
if (_employee != null)
{
EmployeeID = _employee.ID;
}
else
{
EmployeeID = 0;
}
}
}

[Include]
[Association("EmployeeProjectDTO_ProjectDTO", "ProjectID", "ID", IsForeignKey = true)]
public ProjectDTO Project {
get
{
return _project;
}
set
{
_project = value;
if (_project != null)
{
ProjectID = _project.ID;
}
else
{
ProjectID = 0;
}
}
}
}
}

Getting Data

The following is a RIA Service with a method that will get all of the employees and fill it up with data. Please read through the data I set up because you will have to understand it for the following sections.

namespace MVVM.Web.Service
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Security.Principal;

using Model;

[EnableClientAccess()]
[RequiresAuthentication()]
public class EmployeeDomainService : DomainService
{

public List<EmployeeDTO> GetEmployees()
{
List<EmployeeDTO> employees = new List<EmployeeDTO>();

//Create the employees
EmployeeDTO developer = new EmployeeDTO();
developer.ID = 1;
developer.FirstName = "Jason";
developer.LastName = "Apergis";
developer.Title = 1;
developer.HireDate = new DateTime(1990, 4, 15);
employees.Add(developer);

EmployeeDTO projectManager = new EmployeeDTO();
projectManager.ID = 2;
projectManager.FirstName = "Ethan";
projectManager.LastName = "Apergis";
projectManager.Title = 2;
projectManager.HireDate = new DateTime(2000, 6, 3);
employees.Add(projectManager);

EmployeeDTO programDirector = new EmployeeDTO();
programDirector.ID = 3;
programDirector.FirstName = "Caroline";
programDirector.LastName = "Apergis";
programDirector.Title = 3;
programDirector.HireDate = new DateTime(2005, 12, 20);
employees.Add(programDirector);

EmployeeDTO regionalDirector = new EmployeeDTO();
regionalDirector.ID = 4;
regionalDirector.FirstName = "Catherine";
regionalDirector.LastName = "Apergis";
regionalDirector.Title = 3;
regionalDirector.HireDate = new DateTime(2005, 12, 20);
employees.Add(regionalDirector);

//Create a project
ProjectDTO project = new ProjectDTO();
project.ID = 1;
project.Name = "Project ABC";
project.StartDate = new DateTime(2006, 6, 17);
project.RegionalDirector = regionalDirector;

//Create the employee to project associations
EmployeeProjectDTO employeeProject1 = new EmployeeProjectDTO();
employeeProject1.ID = 1;
employeeProject1.Project = project;
employeeProject1.Employee = developer;

EmployeeProjectDTO employeeProject2 = new EmployeeProjectDTO();
employeeProject2.ID = 2;
employeeProject2.Project = project;
employeeProject2.Employee = projectManager;
employeeProject2.IsProjectManager = true;

EmployeeProjectDTO employeeProject3 = new EmployeeProjectDTO();
employeeProject3.ID = 3;
employeeProject3.Project = project;
employeeProject3.Employee = programDirector;
employeeProject3.IsProgramDirector = true;

//Add the associations to the employee objects
developer.EmployeeProjects.Add(employeeProject1);
projectManager.EmployeeProjects.Add(employeeProject2);
programDirector.EmployeeProjects.Add(employeeProject3);

//Add the associations to the project object
project.EmployeeProjects.Add(employeeProject1);
project.EmployeeProjects.Add(employeeProject2);
project.EmployeeProjects.Add(employeeProject3);

return employees;
}

}
}

Persisting Data

To persist the data, I need to have the following methods on my custom domain service I just created. I have no code in here right now to actually wire up to the custom enterprise services I will be utilizing.

private static int count = 10;

public void InsertEmployeeDTO(EmployeeDTO employee) {
employee.ID = count++;
System.Diagnostics.Debug.WriteLine("Insert EmployeeDTO");
}

public void UpdateEmployeeDTO(EmployeeDTO employee)
{
System.Diagnostics.Debug.WriteLine("Update EmployeeDTO");
}

public void DeleteEmployeeDTO(EmployeeDTO employee)
{
System.Diagnostics.Debug.WriteLine("Delete EmployeeDTO");
}

public void InsertProjectDTO(ProjectDTO project)
{
project.ID = count++;
System.Diagnostics.Debug.WriteLine("Insert ProjectDTO");
}

public void UpdateProjectDTO(ProjectDTO project)
{
System.Diagnostics.Debug.WriteLine("Update ProjectDTO");
}

public void DeleteProjectDTO(ProjectDTO project)
{
System.Diagnostics.Debug.WriteLine("Delete ProjectDTO");
}

public void InsertEmployeeProjectDTO(EmployeeProjectDTO employeeProject)
{
employeeProject.ID = count++;
System.Diagnostics.Debug.WriteLine("Insert EmployeeProjectDTO");
}

public void UpdateEmployeeProjectDTO(EmployeeProjectDTO employeeProject)
{
System.Diagnostics.Debug.WriteLine("Update EmployeeProjectDTO");
}

public void DeleteEmployeeProjectDTO(EmployeeProjectDTO employeeProject)
{
System.Diagnostics.Debug.WriteLine("Delete EmployeeProjectDTO");
}

Scenario 1

Now I am ready to start using my RIA Services. I am not going to bind these objects to a Silverlight UI as I want to focus on the transactions. I am now going to give a couple scenarios and discuss which methods will be called in the RIA Services.

In the following code, demonstrates how I would promote the developer to a project manager of a specific project.

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Update the developers EmployeeProjectDTO to IsProjectManager
EmployeeDTO developer = _context.EmployeeDTOs.
Where<EmployeeDTO>(e => e.ID == 1).FirstOrDefault();
EmployeeProjectDTO developerEmployeeProject = developer.EmployeeProjects.
Where<EmployeeProjectDTO>(ep => ep.ID == 1).FirstOrDefault();
developerEmployeeProject.IsProjectManager = true;

//Remove the previous Project Manager's association to the project
EmployeeDTO projectManager = _context.EmployeeDTOs.
Where<EmployeeDTO>(e => e.ID == 2).FirstOrDefault();
EmployeeProjectDTO projectManagerEmployeeProject = projectManager.EmployeeProjects.
Where<EmployeeProjectDTO>(ep => ep.Project.ID == developerEmployeeProject.Project.ID).FirstOrDefault();
projectManager.EmployeeProjects.Remove(projectManagerEmployeeProject);

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

Based on the code above the following service methods will be called:

  • UpdateEmployeeDTO (Developer instance)
  • InsertEmployeeProjectDTO (Developer instance)
  • UpdateEmployeeDTO (Project Manager instance)
  • DeleteEmployeeProjectDTO (Project Manager instance)

One of the first things you may say is, well I did not modify anything on the EmployeeDTO objects, all I did was modify the EmployeeProjectDTOs. Well the great thing about RIA Services and the ChangeSet is that it recognizes a concept typically referred to as shallow and deep dirty. Even though the Employee DTOs were not directly modified, objects they were associated with are direct making them dirty (i.e. deep dirty). It is great that this is done because this allows you to potentially perform an operation on the EmployeeDTO if you need, like maybe update a time stamp or lock a record.

Scenario 2

In this scenario, let’s remove all of the employee projects from all the employees (i.e. the project has ended and the employees no longer need to be associated to the project however we do not want to delete the project itself).

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Delete the project associations
foreach (EmployeeDTO employee in _context.EmployeeDTOs)
{
foreach (EmployeeProjectDTO employeeProject in employee.EmployeeProjects)
{
employee.EmployeeProjects.Remove(employeeProject);
}
}

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • UpdateEmployeeDTO (Developer)
  • DeleteEmployeeProjectDTO (Developer)
  • UpdateEmployeeDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Project Manager)
  • UpdateEmployeeDTO (Program Director)
  • DeleteEmployeeProjectDTO (Program Director)

Now you may be looking at the code above and say, if you want to remove all of the associations from a project what was just done is pretty error prone given I have no criteria to select which project has objects deleted. Plus, it would be more efficient to just remove the associations directly off the ProjectDTO. I agree. To do this, the first thing you will try to do is go _context.ProjectDTOs and quickly find out that the property does not exist. This is because there is no query methods yet on the domain service that returns a collection of ProjectDTOs. Even though in the GetEmployees() method I have initiated the objects to do what is need, it is not possible. All you need to do is add an empty method like the following to the domain service:

public List<ProjectDTO> GetProjects()
{
return new List<ProjectDTO>();
}

Now you will be able to write the following code:
_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

foreach (ProjectDTO project in _context.ProjectDTOs) {
foreach (EmployeeProjectDTO employeeProject in project.EmployeeProjects)
{
project.EmployeeProjects.Remove(employeeProject);
}
}

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • UpdateProjectDTO (Project ABC)
  • DeleteEmployeeProjectDTO (Developer)
  • DeleteEmployeeProjectDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Program Director)

This is still not the greatest example because I still called GetEmployees() to delete the projects. It would be better to call GetProjects() however I wanted to demonstrate a point that I have to a method on the RIA Services to return a collection of ProjectDTOs.

Scenario 3

Now in this scenario, let’s take the previous code and add to it code to create a brand new project and associate existing employees to that project.

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Delete the project associations
foreach (ProjectDTO project in _context.ProjectDTOs) {
foreach (EmployeeProjectDTO employeeProject in project.EmployeeProjects)
{
project.EmployeeProjects.Remove(employeeProject);
}
}

//Get the employees
EmployeeDTO developer = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 1).FirstOrDefault();
EmployeeDTO projectManager = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 2).FirstOrDefault();
EmployeeDTO programDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 3).FirstOrDefault();
EmployeeDTO regionalDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 4).FirstOrDefault();

//Now let's add a new project
ProjectDTO newProject = new ProjectDTO();
newProject.Name = "New Project";
newProject.RegionalDirector = regionalDirector;

//Add the project to the context
_context.ProjectDTOs.Add(newProject);

//Create the EmployeeProjectDTO
EmployeeProjectDTO employeeProject1 = new EmployeeProjectDTO();
employeeProject1.Employee = developer;
employeeProject1.Project = newProject;

EmployeeProjectDTO employeeProject2 = new EmployeeProjectDTO();
employeeProject2.Employee = projectManager;
employeeProject2.Project = newProject;
employeeProject2.IsProjectManager = true;

EmployeeProjectDTO employeeProject3 = new EmployeeProjectDTO();
employeeProject3.Employee = programDirector;
employeeProject3.Project = newProject;
employeeProject3.IsProgramDirector = true;

//Create project associations
newProject.EmployeeProjects.Add(employeeProject1);
newProject.EmployeeProjects.Add(employeeProject2);
newProject.EmployeeProjects.Add(employeeProject3);

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • InsertProjectDTO (New Project)
  • InsertEmployeeProjectDTO (Developer)
  • InsertEmployeeProjectDTO (Project Manager)
  • InsertEmployeeProjectDTO (Program Director)
  • UpdateProjectDTO (ABC Project)
  • DeleteEmployeeProjectDTO (Developer)
  • DeleteEmployeeProjectDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Program Director)

One thing that you will notice when running this code is that some of the references to objects will be null. For instance in the InsertEmployeeProjectDTO calls, the EmployeeProjectDTO.Employee will be null but EmployeeProjectDTO.EmployeeID will not. This goes back to my earlier discussion on why we had to keep the primitive types for associations. Please note that even though EmployeeProjectDTO.Employee is null on the RIA Service Method call to InsertEmployeeProjectDTO, it is NOT null back on the code in the Silverlight project.

To test some things, I wanted to see if I could achieve the same thing but change some lines of code to instead create the new project from the employee perspective. You will notice I removed the line to add the ProjectDTO to the context and added the EmployeeProjectDTOs directly to the EmployeeDTOs.

_context.Load<EmployeeDTO>(_context.GetEmployeesQuery());

//Delete the project associations
foreach (ProjectDTO project in _context.ProjectDTOs) {
foreach (EmployeeProjectDTO employeeProject in project.EmployeeProjects)
{
project.EmployeeProjects.Remove(employeeProject);
}
}

//Get the employees
EmployeeDTO developer = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 1).FirstOrDefault();
EmployeeDTO projectManager = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 2).FirstOrDefault();
EmployeeDTO programDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 3).FirstOrDefault();
EmployeeDTO regionalDirector = _context.EmployeeDTOs.Where<EmployeeDTO>(e => e.ID == 4).FirstOrDefault();

//Now let's add a new project
ProjectDTO newProject = new ProjectDTO();
newProject.Name = "New Project";
newProject.RegionalDirector = regionalDirector;

//Create the EmployeeProjectDTO
EmployeeProjectDTO employeeProject1 = new EmployeeProjectDTO();
employeeProject1.Employee = developer;
employeeProject1.Project = newProject;

EmployeeProjectDTO employeeProject2 = new EmployeeProjectDTO();
employeeProject2.Employee = projectManager;
employeeProject2.Project = newProject;
employeeProject2.IsProjectManager = true;

EmployeeProjectDTO employeeProject3 = new EmployeeProjectDTO();
employeeProject3.Employee = programDirector;
employeeProject3.Project = newProject;
employeeProject3.IsProgramDirector = true;

//Associate to the employee and manager
developer.EmployeeProjects.Add(employeeProject1);
projectManager.EmployeeProjects.Add(employeeProject2);
programDirector.EmployeeProjects.Add(employeeProject3);

//Save the changes
if (_context.HasChanges)
{
_context.SubmitChanges();
}

The following domain services methods will be called in the following order:

  • InsertProjectDTO (New Project)
  • UpdateEmployeeProjectDTO (Developer)
  • InsertEmployeeProjectDTO (Developer)
  • UpdateEmployeeProjectDTO (Project Manager)
  • InsertEmployeeProjectDTO (Project Manager)
  • UpdateEmployeeProjectDTO (Program Director)
  • InsertEmployeeProjectDTO (Program Director)
  • UpdateProjectDTO (ABC Project)
  • DeleteEmployeeProjectDTO (Developer)
  • DeleteEmployeeProjectDTO (Project Manager)
  • DeleteEmployeeProjectDTO (Program Director)

As you can see the exact same result was achieved.

Now if you have been paying attention you may be asking yourself, how come when I add the EmployeeProjectDTOs I do not add them to both the EmployeeDTOs and ProjectDTO like the following:

//Associate to the employee and manager
developer.EmployeeProjects.Add(employeeProject1);
projectManager.EmployeeProjects.Add(employeeProject2);
programDirector.EmployeeProjects.Add(employeeProject3);

//Create project associations
newProject.EmployeeProjects.Add(employeeProject1);
newProject.EmployeeProjects.Add(employeeProject2);
newProject.EmployeeProjects.Add(employeeProject3);

Well if I did this I would get the following error:

Submit operation failed. Invalid change-set : Entity for operation '0' has multiple parents.

The best I can figure is the ChangeSet just cannot handle this scenario because it does not want to potentially add the EmployeeProject objects more than once.

Alternate Approach

In some other posting I was reading when researching this topic I saw some examples where the following as presented as a solution. On the RIA Services instead of having the individual insert, update and delete methods for each object type, instead have an update method for the root object and then have methods like the following:

namespace MVVM.Web.Service
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Security.Principal;

using Model;

[EnableClientAccess()]
[RequiresAuthentication()]
public class EmployeeDomainService : DomainService
{

public void UpdateEmployeeDTO(EmployeeDTO employee)
{
System.Diagnostics.Debug.WriteLine("Update EmployeeDTO");
SaveDTO(employee);
}

public void UpdateProjectDTO(ProjectDTO project)
{
System.Diagnostics.Debug.WriteLine("Update ProjectDTO");
SaveDTO(project);
}

private void SaveDTO(EmployeeDTO employee)
{
ChangeOperation employeeCO = this.ChangeSet.GetChangeOperation(employee);

switch (employeeCO)
{
case ChangeOperation.Insert:
//Add code to insert the EmployeeDTO
employee.ID = count++;
break;

case ChangeOperation.Update:
//Add code to update the EmployeeDTO
break;

case ChangeOperation.Delete:
//Add code to delete the EmployeeDTO
break;

case ChangeOperation.None:
break;
}

foreach (EmployeeProjectDTO employeeProject in this.ChangeSet.GetAssociatedChanges(employee, e => e.EmployeeProjects))
{
SaveDTO(employeeProject);
}
}

private void SaveDTO(ProjectDTO project)
{
ChangeOperation projectCO = this.ChangeSet.GetChangeOperation(project);

switch (projectCO)
{
case ChangeOperation.Insert:
//Add code to insert the EmployeeDTO
project.ID = count++;
break;

case ChangeOperation.Update:
//Add code to update the EmployeeDTO
break;

case ChangeOperation.Delete:
//Add code to delete the EmployeeDTO
break;

case ChangeOperation.None:
break;
}

foreach (EmployeeProjectDTO employeeProject in this.ChangeSet.GetAssociatedChanges(project, p => p.EmployeeProjects))
{
SaveDTO(employeeProject);
}
}

private void SaveDTO(EmployeeProjectDTO employeeProject)
{
ChangeOperation employeeProjectCO = this.ChangeSet.GetChangeOperation(employeeProject);
switch (employeeProjectCO)
{
case ChangeOperation.Insert:
//Add code to insert the EmployeeProjectDTO
employeeProject.ID = count++;
break;

case ChangeOperation.Update:
//Add code to update the EmployeeProjectDTO
break;

case ChangeOperation.Delete:
//Add code to delete the EmployeeProjectDTO
break;

case ChangeOperation.None:
break;
}
}

}
}

I have tested this and it would be a viable approach and support all of the examples that I presented earlier. I personally prefer the previous approach. Only drawback of this approach is you have to make sure you are always working with root objects and do not do this for non-root objects because it is possible that you can run a transaction twice and cause an error.