Skip to content
merijndejonge edited this page Feb 23, 2018 · 4 revisions

EntityGraphs

This document is the original EntityGraph documentation and needs to be updated because there are still parts specific for WCF RIA services.

Introduction

For a concrete example of EntityGraph check out EntityGraphCarParkExample.

In WCF RIA Services, a data model has a rather flat structure: it consists of a collection of entities and their associations. The structure that is present in your data model and which is representative for your ___domain model is not present. As a consequence, RIA typically operates on individual entities or on entity collections, and a significant amount of work may have to be spent to define operations that take the structure of your ___domain into account.

Entity graphs are a means to improve this situation. An entity graph is a collection of entities and their associations that form a logical unit. Entity graph operations operate on the collection of entities as a whole rather than on individual entities.

An example is depicted in the Figure below. It shows part of the AdventureWorks data model. This picture shows the Product entity and its associations. The red-colored entities form an entity graph for Product. It indicates that a Product essentially is formed by the collection of the colored entities and associations.

Alt text Figure 1: The entity graph for Product in the Adventure works data model

Installation

To install EntityGraph you can either:

  • Download the source code, and build it your self, or
  • Add EntityGraph directly to your projects using pre-build NuGet packages. The latter is probably the simplest way, although it may not always give you the most recent version of EntityGraph.

Use cases

Cloning.

If you need a copy of a collection of entities you may end up in writing a lot of code that deal with cloning the entity by creating new instances of your entities, copying their values, and creating all associations between them. Typically, you don't clone everything, but a specific set of entities. For instance, if you would clone a Product (see Figure 1) you would clone e.g., BillOfMaterial, but not ProductVendor. An entity graph is designed to define exactly this kind of information. Given an entity graph for Product, cloning becomes a single, and generic, operation

Detaching

Detaching an entity from the ___domain context is a complex operation if also its associations have to be detached. This is because you have to program explicitly what entities have to be detached. With an entity graph this becomes a single operation. For example, detaching a Product (see Figure 1) automatically detaches all red-colored associated entities.

Deleting

How to use RIA to delete all products in your data base without loading them all in your ___domain context? If you delete them from your ___domain context, you know that the products loaded in your ___domain context will be removed, but you don't have the guarantee that all product are deleted in your database (since not all may have been loaded in your ___domain context). To make things worse, deleting entities using RIA is difficult because you have to code explicitly what additional entities have to be removed in order not to break any foreign key constraint. With an entity graph you can easily detach all products (and have all associated entities detached automatically). In addition, you define a single ___domain service operation that deletes the product. In combination with cascading deletes in your database you have the guarantee that all products and all associations are removed correctly.

Observing

An entity graph observes any property or collection change in any of its entities. This means you can use a NotifyPropertyChanges, or NotifyCollectionChange for the graph as a whole

Validation

RIA data validation is powerful and easy to use, but it's primary use is for validating individual entities. Entity graph data validation, on the other hand, is designed to validate the collection of entities of the graph.

Usage

Defining entity graphs

Your data model is itself a graph of entities. An entity graph shape defines a sub graph that forms an entity graph. For example:

var graphShape = new EntityGraphShape()
    .Edge<Product, UnitMeasure>(product => product.UnitMeasure)
    .Edge<Product, ProductSubcategory>(product => product.ProductSubcategory);

This shape defines an entity graph graph that includes a Product's UnitMeasure and ProductSubcategory. Entity graph shapes can be constructed at run time. Different shapes can be defined for the same data model.

Instantiating entity graphs.

To use entity graphs, you need to add references to the EntityGraph.Net.dll and EntityGraph.Silverlight.dll. Next you must add the following statement to your RIA application:

using RiaServicesContrib.DomainServices.Client;

Given an object product of type Product, you can obtain the entity graph defined by the entity graph shape defined in graphShape as follows:

var graph = product.EntityGraph(graphShape);

Examples

Below are a couple of examples of using entity graphs. The unit tests that are part of the entity graph implementation contain a lot more examples.

Given an entity graph graph, we can iterate over its members using ordinary Linq expressions:

foreach(var element in graph)
{
}

If the elements of your graph are contained in a ___domain context, you can detach the full graph from the context and keep the graph structure in tact with a single command:

ctx.As.Add(a);
graph.DetachEntityGraph(ctx.As);

To check whether any of the elements of the graph has pending changes, use the graph's HasChanges property. To get all changes in the form of a change set an entity graph has the GetChanges method:

if(graph.HasChanges){
   var changeSet = graph.GetChanges();
}

You can observe property changes of any entity in the graph using the INotifyPropertyChanged interface of an entity graph:

graph.PropertyChanged += (sender, evnt) => { /* do something */ };

Similarly for collections:

graph.CollectionChanged += (sender, evnt) => { /* do something */ };

You can create a copy (clone) of all the entities in the graph using the Clone method. This will not only make copy of all properties but also of all associations between them. The result is a collection of copies of the entities in the graph, which are structured in a completely identical shaped graph:

var clone = graph.Clone();

The Entity graph API

The entity graph API consists of two layers. The lowest layer forms a RIA-independent layer. This means that entity graph can also be used for non-RIA applications (e.g., in combination with entity framework). This layer provides the following functionality:

  • IEnumerable
  • INotifyPropertyChanged
  • INotifyCollectionChanged
  • GraphValidation
  • GraphComparison The second layer is RIA specific. It (re-)implements several operations of a RIA service ___domain context:
  • Cloning
  • Clone comparison
  • EntityChangeSet
  • DetachEntityGraph
  • RemoveEntityGraph
  • HasChanges
  • IEditableObject

The unit tests that are part of the entity graph implementation contain examples of all of these features. Please have a look there for further information. These tests are using the following data models, with corresponding entity graphs.

Alt text

Figure 2: The entity graphs for the unit tests of the entity graph implementation

Data Validation

Introduction and motivation

The EntityGraph library contains an alternative implementation for data validation that is designed to be more flexible than the data validation mechanism of RIA and to support cross entity validation. This validation mechanism improves the following aspects of data validation in RIA:

  • The validation rules are decoupled from the data model. This means that your data model does not need to be annotated with data validation attributes. By doing so, we have more control over which rules to apply (e.g., supporting dynamic rule sets, customer-specific rule sets etc. Yet, the framework is flexible enough to also support the attribute-based validation mechanism of RIA (actually, the validation framework contains a couple of them to demonstrate that RIA-style validation is also supported).
  • The validation mechanism supports instance based validation (in addition to class-based validation as RIA does). This means that different rules can be associated to entities depending on the context. In RIA always the same rules are validated for an entity of type T, because the validation rules are coupled with the class definition of T. In the entity graph validation mechanism you can validate a rule set r1 for on entity of type T in context C1 and another rules set r2 in context C2.
  • Validation rules can span multiple entities. The validation rules are only executed when the corresponding properties are changed. So, for example, we can easily express for a Car, which has an Engine as well as a collection of Wheels and Doors, that if it has more than 4 wheels it must have a diesel engine and no more than 2 doors.
  • The validation mechanism can be instantiated with a more advanced Validation reporting type than ValidationResult in RIA. The class ValidationResult in RIA holds an error string and an arry of property names of the properties that are in error. To do more advanced things than just highlighting properties in error in the UI, we need to have a more advanced class than ValidationResult. Unfortunately, the class ValidationResult is sealed, so we can't subclass it to extend it with additional needs. For that reason, we made validation result a type parameter of the entity graph validation mechanism.
  • Validation rules can be defined (almost) anywhere. We use MEF to find them. This means we can easily develop validation rules in separate assemblies, which can evolve pretty independently of your application and they don't need to live in the same assembly as your data model.
  • The decoupling of the validation rules from the data model makes it very easy to define unit tests for the validation rules.

Signature-based validation

The central idea is that validation rules have an explicit signature that defines which properties of a collection of entities form the input of a validation rule. We call this signature-based validation. The collection of entities can be a singleton to get single entity validation or else the validation rule spans multiple entities. An entity graph implements the INotifyPropertyChanged and INotifyCollectionChanged interfaces. So, whenever something happened in the entity graph, we can look up the signatures of available validation rules to see if the corresponding properties or collections play a role in any of these rules. If so, the corresponding validation rules are executed.

Actually, in addition to signature-based validation, the validation framework can be instantiated with class-level validation like RIA. We will discuss this later.

The signature of a validation rule is a collection of typed InputOutput and InputOnly Lamba expressions which each defining the path from an entity to the property that forms the input of a validation rule. A signature forms the connection mechanism of a validation rule to the object graph that is to be validated. For example:

   InputOutput<Car,IEnumerable<Wheel>>(car => car.Wheels),
   InputOutput<Car,IEnumerable<Door>>(car => car.Doors),
   InputOnly<Car,EngineType>(car => car.Engine.EngineType)

This defines that the validation rule depends on three properties spanning two different entity types (Car and Engine).

The corresponding validation rule should implement the following validation method:

ValidationResult Validate(IEnumerable<Wheel> wheels, IEnumerable<Door> doors, EngineType engineType)

You can also give a different name to the method and use the ValidateMethod attribute.

The signature and the validation rule itself should be defined in a class that derives from ValidationRule<TResult> where TResult can be instantiated with System.ComponentModel.DataAnnotations.ValidationResult or an alternative class to represent validation results.

ValidationResult Validate(IEnumerable<Wheel> wheels, IEnumerable<Door> doors, EngineType engineType)
{
   bool hasValidationError = false;
   ... // validation logic comes here
   
   if(hasValidationError)
   {
      return new ValidationResult("some error message");
   }
   else
   {
      return ValidationResult.Success
   }
}

To make your validation rules known to the entity graph validator, the containing assembly needs to be registered:

MEFValidationRules.RegisterAssembly(your_validation_assembly)

MEF is used to find validation methods of type ValidationRule<TResult> in the registered assemblies. This means that the validation rules are decoupled from your data model and from your application. I.e., your data model does not need to be annotated with validation rules and you can place the rules in 1 or more separate assemblies. In your application you only need to register the assemblies that contain your validation rules. ,

To initialize data validation for your entity graph, create an instance of the entity graph. E.g.,

var graphShape= new EntityGraphShape()
            .Edge<Car, Wheel>(Car => Car.Wheels)
            .Edge<Car, Door>(Car => Car.Doors)
            .Edge<Car, Engine>(Car => Car.Engine);
var graph = mycar.EntityGrah(graphShape);

From this moment on, the entity graph will call your validation rules whenever one of the involved properties changes. E.g., when you set mycar.Engine.Type to EngineType.Gaz, or when you attach a wheel to mycar.Wheels.

Entity graph validation components and interfaces

IValidationRule. Every validation rule should implement the IValidationRule interface. It has an event handler ValidationResultChanged, a property Result that holds the validation result, and a method InvokeValidate(TRoot entity) that validates the given entity.

ValidationRule. Abstract base class for signature-based validation rules. It implements IValidationRule and contains a dispatcher that translates a call to the generic InvokeValidate method to a call to the rule-specific Validate method.

IValidationRulesProvider The framework works with the concept of validation rules providers. A validation rules provider implements a particular mechanism for returning the collection of validation rules for an entity. The interface consists of a single method that returns a dictionary containing the validators for a given entity: Dictionary<Tuple<TEntity, string>, List<IValidationRule<TResult>>> GetValidationRules(TRoot root);. The keys of this dictionary are formed by TEntity, property name tuples, the values of the dictionary are lists of validation rules that should be executed when the property of the given entity changes.

MEFValidationRulesProvider. This is one of the two implementation of IValidationRulesProvider that we currently have. It is a MEF-based mechanism to locate the validation rules for a given entity.

ValidationEngine. This is the central class in the validation framework. It takes care of collecting the validation rules for an entity using the passed-in validation rules provider. It has an ValidationResultChanged event handler that is triggered when any of the validation results of the validation rules changes. Furthermore, it has a Validate method that executes all known validation rules for the given object and property.

EntityValidator This abstract class forms the interface between an entity and the validation engine. There is a RIA-specific implementation of this class. When it is instantiated with a validation rules provider and an entity, it will apply the provided validation rules to the given entity and related entities, according to the signatures of the validation rules.

GraphValidator. The components and interfaces thus far are not specific for entity graphs. The GraphValidator enables data validation on entity graphs. It instantiates a validation engine object and uses the events form the INotifyPropertyChanged and INotifyCollectionChanged interfaces of the involved entities to call the Validate method of the validation engine whenever a property or collection of any of the involved entities changed.

Attribute-based validation

The class ValidationEngine refers to the interface IValidationRulesProvider to obtain validation rules for an entity. The class can be instantiated with different implementations of this interface for finding validation rules in different ways. In fact, we can implement a rules provider that collects attribute-based validation rules like RIA. We call this attribute-based validation, because in this case the set of validation rules for an entity is defined by the collection of validation attributes that decorate the class definition. To realize attribute-based validation, we created the following additional classes / interfaces:

IValidationAttribute. Interface that extends the IValidationRule interface and is used to identify attribute-based validation rules.

ClassValidationRulesProvider. This is the second implementation of IValidationRulesProvider. It collects and returns the attribute-based validation rules defined for the given entity.

ValidationAttribute. This is an abstract base class for attribute-based validation rules. It implements the InvokeValidate method and contains some administrative properties. We've implemented the following two attribute-based validators that are (almost) similar to the RIA-equivalents: RequiredAttribute, RegularExpressionAttribute. They are to demonstrate that we can do RIA-style validation as well. In addition we created NoDuplicatesAttribute as yet another example of creating an attribute-based validator. This one validates that a collection does not contain duplicate elements.

EntityValidator. This RIA-specific class contains all the machinery to attach a validation engine and a ClassValidationRulesProvider to a RIA entity, add it inserts all the validation errors to the entity's ValidationErrors collection. There are two subclasses of this class that demonstrate how the validation engine can be used with completely different rule provider mechanisms.

ClassEntityValidator This is a subclass of EntityValidator that uses the ClassValidationRulesProvider for obtaining validation rules. Use this class if you want to use attribute-based validation.

MEFEntityValidator This is a subclass of EntityValidator that uses the MEFValidationRulesProvider for obtaining validation rules. Use this class if you want to use signature-based validation.

Asynchronous validation

The validation examples until now all execute synchronously. This is alright for validation rules that don't take long to run and that do not require access to additional resources such as a remote web service. If your validation rules require access to a remote server, or, for any other reason take long to run, the validation framework supports asynchronous validation rules.

An asynchronous validation rule should derive from AsyncValidationRule and its validation method should return an instance of ValidationOperation. Set the Result property of this instance when your validation rule completes. This is demonstrated in the example below.

public class MyAsyncValidationRule : AsyncValidationRule
{
   public MyAsyncValidationRule()
      : base(
         InputOutput<Car,IEnumerable<Wheel>>(car => car.Wheels)
      )
   { }
   public ValidationResult Validate(IEnumerable<Wheel> wheels)
   {
      var operation = new ValidationOperation();
      DispatcherTimer timer = new DispatcherTimer();
      timer.Interval = new TimeSpan(0, 0, 0, 0, 10000); // A length time interval of 10000 Milliseconds 
      timer.Tick += (s, e) =>
      {
         timer.Stop();
         operation.Result = new ValidationResult("some validation error");
      };
      timer.Start();
      return operation;
   }
}

Notes

  • As with the rest of the EntityGraph project, the implementation of entity graph validation is split in a part that is RIA-independent and a part that is specific for RIA.
  • The main part of the validation mechanism is independent of entity graphs and can be instantiated with different types.
  • The unit tests of the EntityGraph project contain examples of both signature-based and attribute-based validation.