LINQ is .NET Language-Integrated Query. It's included in .NET Framework 3.5 and you can use it in Visual Studio 2008 projects. XPO has officially supported LINQ since v7.3 and since then we have continued to improve and support it. Nowadays LINQ to XPO is very mature, extendable (How to: Implement Custom Functions and Criteria in LINQ to XPO) and powerful (Free Joins). In the latest version we make it even easier to use LINQ to XPO since we only distribute one assembly, DevExpress.XPO.dll, which includes everything! (custom providers as well).
XPO can talk transparently to a large list of database systems. It was already possible to make complex queries using our standard criteria syntax, however using LINQ offers some great advantages.
- No magic strings, like you have in standard criteria syntax,
- Intellisense support making it easier and faster to construct your queries,
- Compile time checking,
- Learning to write LINQ queries/lambdas is a must learn for .NET developers,
The benefits are clear, however there is a caveat when using LINQ. It is not possible to use Edit and Continue and with even the smallest change you need to restart your debugging session. As a result your development speed is decreased dramatically.
The solution as always is to use the right tool and in this case the right tool is LINQPad. It provides a flexible UI allowing you to use LINQ in various ways.
In our latest version our team released an XPO context driver for LINQPad. Let’s see how to configure it.
Firstly we need to install the driver found at C:\DevExpress 2011.2\Components\Tools\DXperience\XPOContextDriver.lpx.

Secondly we need to setup a new connection.

In this example we are going to use the MainDemo assembly containing the business objects.

LINQPad populated the domain in a hierarchical treelist. This allows us to enjoy using drag & drop plus intellisence in its expression editor to form the LINQ queries. Furthermore it is possible to use your style of language as demonstrated below.

The cool part is that LINQPad, written by the great Joe Albahari, is available for free. It is strongly recommended as a great way to learn LINQ.
Now for the fun part, let’s see it in action. Imagine we have the following code in our VS.
var queryable = from c in contacts
let tasks = c.Tasks.Where(t => t.ActualWork < 0)
where tasks.Any()
orderby c.LastName
select new {
c.LastName,
Orders = tasks.Count(),
};
We wish to see the results of queryable variable ,the best way to achieve this is to set a breakpoint then hit F5. Next we wait for our app to load and navigate to the action that will hit the breakpoint. Finally we must select the queryable variable and hit Shift+F9 to invoke the debugger window and examine the result set. Now we can try to make our requirements more complex. Lets say we also want to examine the result set when ActualWork>10. The solution is really easy when using LINQPad, we simply need to copy paste the code from VS to LINQPad the expression editor like this,

After this we can quickly and easily form queries as we wish and examine their result sets instantly. Using the excellent and cheap LINQPad Autocompletion it is possible to use VS like intelligence to further speed up your development.

PS: eXpandFrameWork contributors are already using the Autocompletion feature since LINQPad offered a license for its contributors.
We would appreciate your feedback on this post. Has it been useful to you? Feel free to contact us with any further questions


WF4 uses a service oriented architecture and as a result any problem can be decoupled into smaller, easily solvable and testable services. XAF uses MVC architecture which, in a sense, is very similar to that used by WF4. We can compare XAF’s controllers to WF4 services. Moreover XAF’s Application does the same job as the WF4 server. The upshot of all this is that users should be able to get the feel of WF4 in no time at all. The XAF workflow module introduces a new layer that makes the already decoupled services aware of our business classes. After this the sky is the limit and over the next few posts I aim to demonstrate some of what can be achieved. For example the next post will focus on creating an event driven workflow initialization engine.
To get back to today’s post, we will discuss an implementation that is very decoupled and as a result it has very limited dependencies on other modules. It is worth noting that all XAF’s features are decoupled, persistent objects take on the role of domain mappers.
Take these requirements;
- an end user needs to be able to input an object type (and or) a property name,
- an object change needs to start the workflow either at client or at sever,
- workflows need to be aware of the object that has changed, its PropertyName and its property OldValue.
The custom workflow definition
We cannot use the default XAF XpoWorkFlowDefinition class in any way. This is because there are no fields to store the PropertyName and its OldValue. We should not even derive from the default XpoWorkFlowDefinition because we may face difficulties as this class is used by our workflow server. To cope with this issue it is necessary to create a custom ObjectChangedWorkflow definition as shown.

While we are doing this we also need to modify the default xaml of the workflow and add the two more arguments (propertyName, oldValue) as per our requirements.

Below you can see the UI of this custom workflow definition,

Up to here XAF has made things very straightforward for us. We have designed a normal persistent class to store our data and we have used attributes (PropertyEditorType, DataStourceProperty, TypeConverter etc) to configure the UI.
Registration of custom workflow definition
The next step is to register this custom workflow definition. To help with this task, eXpand, provides the WorkflowStartService<T> where T is the type of workflow. Furthermore for ObjectChangeWorkflow definitions the implementation is rather easy since there are no further requirements.
public class ObjectChangedWorkflowStartService : WorkflowStartService<ObjectChangedWorkflow> {
public ObjectChangedWorkflowStartService()
: base(TimeSpan.FromMinutes(1)) {
}
public ObjectChangedWorkflowStartService(TimeSpan requestsDetectionPeriod) : base(requestsDetectionPeriod) { }
protected override bool NeedToStartWorkflow(IObjectSpace objectSpace, ObjectChangedWorkflow workflow) {
return true;
}
protected override void AfterWorkFlowStarted(IObjectSpace objectSpace, ObjectChangedWorkflow workflow, Guid startWorkflow) {
}
}
Start workflow - Track Object Changes
Now, when I have registered workflows on the server, it's time to return to my task: start a workflow when a property has been changed.
In XAF, I can track changes with the help of the ObjectSpace.Committing and ObjectSpace.ObjectChanged events. However because we need to create only one request per object change, it is advisable to collect the changes in an array.
public class StartWorkflowOnObjectChangeController : ViewController<ObjectView> {
protected override void OnActivated() {
base.OnActivated();
if (TypeHasWorkflows()) {
ObjectSpace.ObjectChanged += PopulateObjectChangedEventArgs;
ObjectSpace.Committing += StartWorkFlows;
}
}
void PopulateObjectChangedEventArgs(object sender, ObjectChangedEventArgs objectChangedEventArgs) {
if (!string.IsNullOrEmpty(objectChangedEventArgs.PropertyName)) {
var changedEventArgs = _objectChangedEventArgses.FirstOrDefault(args => args.Object == objectChangedEventArgs.Object && args.PropertyName == objectChangedEventArgs.PropertyName);
if (changedEventArgs != null) {
_objectChangedEventArgses.Remove(changedEventArgs);
_objectChangedEventArgses.Add(new ObjectChangedEventArgs(changedEventArgs.Object, changedEventArgs.PropertyName, changedEventArgs.OldValue, objectChangedEventArgs.NewValue));
} else
_objectChangedEventArgses.Add(objectChangedEventArgs);
}
}
void StartWorkFlow(ObjectChangedEventArgs objectChangedEventArgs, ObjectChangedWorkflow objectChangedWorkflow) {
var o = objectChangedEventArgs.Object;
ITypeInfo typeInfo = XafTypesInfo.Instance.FindTypeInfo(o.GetType());
object targetObjectKey = typeInfo.KeyMember.GetValue(o);
if (objectChangedWorkflow.ExecutionDomain == ExecutionDomain.Server) {
CreateServerRequest(objectChangedEventArgs, objectChangedWorkflow, targetObjectKey, typeInfo);
} else {
InvokeOnClient(objectChangedEventArgs, objectChangedWorkflow, targetObjectKey);
}
}
As you will have noticed we have not used the default VS naming for ObjectSpace event handlers. This is because the names that have chosen give a more specific idea of how each method works.
The ObjectChanged event occurs each time a property is changed and the changes are collected in the objectChangedEventArgses array. The Committing event occurs once changes are ready to be sent to the server and workflows start for each entry. We have introduced two options for starting and executing workflows;
- Execute synchronously and locally,
- Send a request to the server and execute at the server asynchronously
Execute a workflow synchronously on the client
The next stage is to create activities at the client then on ObjectSpace CommitChanges from appropriate WorkflowDefinition and execute them immediatelly
public class StartWorkflowOnObjectChangeController : ViewController<ObjectView> {
void InvokeOnClient(ObjectChangedEventArgs objectChangedEventArgs, ObjectChangedWorkflow objectChangedWorkflow, object targetObjectKey) {
Activity activity = ActivityXamlServices.Load(new StringReader(objectChangedWorkflow.Xaml));
var dictionary = ObjectChangedStartWorkflowService.Dictionary(targetObjectKey, objectChangedEventArgs.PropertyName, objectChangedEventArgs.OldValue);
WorkflowInvoker.Invoke(activity, dictionary);
}
This is a simple code which can be found in nearly any WF4 example at
http://www.microsoft.com/download/en/details.aspx?id=21459.
Send a request to start workflow on the server
The second of our two methods involves starting the workflow at the server. Now we need to notify the server of the values of those arguments as well. In the manually starting workflows post we learnt that XAF does this by using XpoStartWorkflowRequest. This class has a different design however, and may create issues since it is used by XAF default services. Therefore instead of deriving from XpoStartWorkflowRequest we need to design a similar custom class.
public class ObjectChangedXpoStartWorkflowRequest : WFBaseObject, IObjectChangedWorkflowRequest {
[TypeConverter(typeof(StringToTypeConverter))]
public Type TargetObjectType {
get { return _targetObjectType; }
set { SetPropertyValue("TargetObjectType", ref _targetObjectType, value); }
}
#region IDCStartWorkflowRequest Members
public string TargetWorkflowUniqueId {
get { return GetPropertyValue<string>("TargetWorkflowUniqueId"); }
set { SetPropertyValue("TargetWorkflowUniqueId", value); }
}
[ValueConverter(typeof(KeyConverter))]
public object TargetObjectKey {
get { return GetPropertyValue<object>("TargetObjectKey"); }
set { SetPropertyValue<object>("TargetObjectKey", value); }
}
#endregion
#region IObjectChangedWorkflowRequest Members
public string PropertyName {
get { return _propertyName; }
set { SetPropertyValue("PropertyName", ref _propertyName, value); }
}
[ValueConverter(typeof(SerializableObjectConverter))]
[Size(SizeAttribute.Unlimited)]
public object OldValue {
get { return _oldValue; }
set { SetPropertyValue("OldValue", ref _oldValue, value); }
}
This is a very simple class, its only role is to store values in the database. Now instead of invoking workflows locally we only need to create ObjectChangedXpoStartWorkflowRequest objects.
public class StartWorkflowOnObjectChangeController : ViewController<ObjectView> {
void CreateServerRequest(ObjectChangedEventArgs objectChangedEventArgs, ObjectChangedWorkflow objectChangedWorkflow, object targetObjectKey, ITypeInfo typeInfo) {
var request = ObjectSpace.CreateObject<ObjectChangedXpoStartWorkflowRequest>();
request.TargetWorkflowUniqueId = objectChangedWorkflow.GetUniqueId();
request.TargetObjectType = typeInfo.Type;
request.TargetObjectKey = targetObjectKey;
request.PropertyName = objectChangedEventArgs.PropertyName;
request.OldValue = GetOldValue(objectChangedEventArgs);
}
In the next step we are going to create a service to consume these values from the server and start a workflow,
public class StartWorkflowOnObjectChangeService : BaseTimerService {
public override void OnTimer() {
using (var objectSpace = ObjectSpaceProvider.CreateObjectSpace()) {
//get all requests from the database
foreach (var request in objectSpace.GetObjects<ObjectChangedXpoStartWorkflowRequest>()) {
//find workflow
var definition = GetService<IWorkflowDefinitionProvider>().FindDefinition(request.TargetWorkflowUniqueId);
if (definition != null && definition.CanOpenHost) {
//Start the workflow passing in PropertyName && OldValue
if (GetService<ObjectChangedStartWorkflowService>().StartWorkflow(definition.Name,
request.TargetWorkflowUniqueId, request.TargetObjectKey, request.PropertyName, request.OldValue)) {
objectSpace.Delete(request);
objectSpace.CommitChanges();
}
}
}
}
}
At this point our server has all the information it needs to start workflows with arguments taken from persistent ObjectChangeXpoStartWorkFlowRequest objects.
I must admit that I have fully enjoyed preparing this post. The decoupled development experienced offered by the WF service oriented model is something that really appeals to me. At the same time XAF’s workflow module implementation made modeling the requirements a simple and enjoyable process. As usual it was possible to work directly on the problem and leave the hard work to non XAF developers.

