Quick Start
Introduction
In this article you will learn to use Euss. The process is made of a few simple
steps:
-
Create you domain model, i.e.
the classes you want to store
-
Code the application
-
Configure a simple data store
-
Define the metadata, describing your domain model
-
Execute the application
-
Configure advanced persistence engines, like o/r mapping, caching and
remoting
After this turorial, you will be able to choose from AOP, Code Generation and
Dynamic Proxies, you will understand what is the metadata for Euss, and how to
configure Euss to use different persistence engines like XML, generic SQL, O/R
mapping and In Memory Database.
Creating the Domain Model
The domain model is made of a set of .Net classes. In our example we will model
two classes: Customer and Order
Here is the class diagram of our model if we had decided to use a case tool:
All classes will be put in the namespace Shop.Domain.
Code Generation
In Euss, Code Generation is the most straighforward way to create an
application. Code generation is based on a description of the domain model.
This description can have multiple forms, among whom we have XMI files,
which are the XML representation of UML diagrams, and Euss domain model files,
a simpler and more understandable XML file type.
In a new Visual Studio .Net project:
-
Add a reference to the assembly Evaluant.Uss.dll
-
Right click on the project node and select Add then New Item...
-
In the category named Euss, select Euss Domain Model File
-
Name the file Shop.Domain.xml
-
Click on Add
By default, the newly created file has this content:
<?xml version="1.0" encoding="utf-8" ?>
<?evaluant-application progid="EUSS.GenerationModel"?>
<Model xmlns="http://euss.evaluant.com/schemas/GenerationModel.xsd">
<!--Sample class description-->
<Package name="Shop.Domain">
<!--
<Class name="HelloWorld">
<Property name="Message" type="System.String"></Property>
</Class>
-->
</Package>
</Model>
Remove the commented part and add the definition of the Customer and Order
classes described previously.
NB: As you type the content of the file you will see that you can benefit from
Visual Studio .Net's auto completion.
<?xml version="1.0" encoding="utf-8" ?>
<?evaluant-application progid="EUSS.GenerationModel"?>
<Model xmlns="http://euss.evaluant.com/schemas/GenerationModel.xsd">
<Package name="Shop.Domain">
<Class name="Customer">
<Property name="CompanyName" type="System.String" />
<Property name="Firstname" type="System.String" />
<Property name="Lastname" type="System.String" />
</Class>
<Class name="Order">
<Property name="Price" type="System.Single" />
<Property name="Currency" type="System.String" />
<Property name="DatePlaced" type="System.DateTime" />
</Class>
<Relationship type="composition">
<Parent name="Customer" role="Buyer" multiplicity="1" />
<Child name="Order" role="Orders" multiplicity="*" />
</Relationship>
</Package>
</Model>
As you save the file, the code generation process is automatically launched.
Then you can directly access the classes you just described. Behind the scene,
a Visual Studio .Net Custom Tool is used. If you expand the file group from Shop.Domain.xml
you will be able to see the generated code.
NB: If you need to add some specific business logic in the generated code, you
can use the .Net 2.0 new functionnality named Partial Classes where a
class can be made of different files. If you are using the framework 1.1 you
can add your own code in predefined locations, which are defined as regions.
When the code will be generated again, all those regions will be imported back.
We have finished with the creation of our classes. You can go directly to the
section Defining the Metadata
Dynamic Proxies
Dynamic Proxies is a method of wrapping any of your class with a dynamically
generated inherited class. This inherited class can have an extended behaviour
to implement the needed methods for communicating with the core of Euss. Using
this technique you can code you own classes; The aim is generally to remove the
dependency between your code and the Euss core. However, there are some rules
that you must comply with:
-
Your classes can't be sealed
-
The fields must be protected
-
The relationships must be declared as IList or IList<>
-
The relationship properties must be virtual
You will see these are not so much limitative constrainsts, and that any correct
object model can cope with those restrictions.
Here are the classes representing our domain model:
Order.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Shop.Domain
{
public class Order
{
protected float _price;
public float Price
{
get { return _price; }
set { _price = value; }
}
protected string _currency;
public string Currency
{
get { return _currency; }
set { _currency = value; }
}
protected DateTime _datePlaced;
public DateTime DatePlaced
{
get { return _datePlaced; }
set { _datePlaced = value; }
}
}
}
Customer.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Shop.Domain
{
public class Customer
{
protected string _companyName;
public string CompanyName
{
get { return _companyName; }
set { _companyName = value; }
}
protected string _firstname;
public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}
protected string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
protected IList<Order> _orders = new List<Order>();
public virtual IList<Order> Orders
{
get { return _orders; }
set { _orders = value; }
}
}
}
NB: The process of dynamically generating sub classes of your domain classes is
done while your application is executing, on the fly. Each time you try to load
some objects of a certain type, the code of Euss seeks for a corresponding
proxy, and generates it if not found.
We have finished with the creation of our classes. You can go directly to the
section Defining the Metadata
Aspect Oriented Programming
Aspect Oriented Programming principles are based on CIL generation. This can be
seen as code generation, but at the binary level. Thus the impact on your
source code is null, and comes as the model for the generation. Moreover, this
is the least contraining technique in Euss towards the definition of your
classes.
Behind the scene some new instructions will be inserted just after the
compilaton occurs, like methods call, implementation of interfaces, and
overrides.
The only constraint of this technique is that you have to define your
relationship properties using IList or IList<>. Thus the code you need is
defined below:
Order.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Shop.Domain
{
public class Order
{
private float _price;
public float Price
{
get { return _price; }
set { _price = value; }
}
private string _currency;
public string Currency
{
get { return _currency; }
set { _currency = value; }
}
private DateTime _datePlaced;
public DateTime DatePlaced
{
get { return _datePlaced; }
set { _datePlaced = value; }
}
}
}
Customer.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Shop.Domain
{
public class Customer
{
private string _companyName;
public string CompanyName
{
get { return _companyName; }
set { _companyName = value; }
}
private string _firstname;
public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
private IList<Order> _orders = new List<Order>();
public IList<Order> Orders
{
get { return _orders; }
set { _orders = value; }
}
}
}
The enhancing phase has to be done after each compilation. This can be done
using a command line tool shipping with the installation of Euss. Though, it is
also possbile to have this done automatically when using Visual Studio .Net.
For this to be done you need to had a Weaving Model File to the project
containing the classes you want to enhance. Thus you may decide which one of
all you domain classes should be able to persist in an Euss store.
Here are the steps:
-
Right click on the project node and select Add then New Item...
-
In the category named Euss, select Euss Weaving Model
-
Name the file Shop.Domain.Weaving.xml
-
Click on Add
Shop.Domain.Weaving.cs
<?xml version="1.0" encoding="utf-8" ?>
<?evaluant-application progid="EUSS.WeavingModel"?>
<Model xmlns="http://euss.evaluant.com/schemas/WeavingModel.xsd">
<Class name="Shop.Domain.Customer">
<Property name="CompanyName" field="_companyName" type="System.String" />
<Property name="Firstname" field="_firstname" type="System.String" />
<Property name="LastName" field="_lastName" type="System.String" />
<Relationship child="Shop.Domain.Order" property="Orders" field="_orders" multiplicity="*" type="aggregation" />
</Class>
<Class name="Shop.Domain.Order">
<Property name="Price" field="_price" type="System.Single" />
<Property name="Currency" field="_currency" type="System.String" />
<Property name="DatePlaced" field="datePlaced" type="System.DateTime" />
</Class>
</Model>
From now on, each time you will compile the project, the enhancing phase will
take place. The result of this process is displayed in the output window.
We have finished with the creation of our classes. You can go directly to the
section Defining the Metadata
Coding a Sample Application
The sample application will store some Customer and Order object
in a repository. Then we will see how to retrieve the objects we stored. With
Euss our application will be able to use any type of storage.
Create a new Console Application project name Shop.Application
Creating an Object Service
In Euss, the configuration is loaded once using a ObjectService. The
actual configuration will be placed in the file engines.config. It will
contain all our repositories' configuration.
Program.cs
ObjectService os = new ObjectService("engines.config");
Instantiating an Object Service
The ObjectService class is the most used one. It represents the actual
context of a database transaction. You can create instances by using the
previously initialized ObjectService.
ObjectContext oc = os.CreateObjectContext();
Serializing Objects
Now we need some objects to serialize. Whatever the thechnique you choose to
define you domain model, this is exactly the same code that you have to type.
Customer c1 = new Customer();
c1.CompanyName = "Walt Disney";
c1.Firstname = "Mickey";
c1.LastName = "Mouse";
Order o1 = new Order();
o1.Price = 123;
o1.Currency = "$";
o1.DatePlaced = new DateTime(2006, 02, 21);
Order o2 = new Order();
o2.Price = 321;
o2.Currency = "$";
o2.DatePlaced = new DateTime(2006, 01, 18);
c1.Orders.Add(o1);
c1.Orders.Add(o2);
Customer c2 = new Customer();
c2.CompanyName = "Dreamworks";
c2.Firstname = "Shrek";
c2.LastName = "Ogre";
Order o3 = new Order();
o3.Price = 10;
o3.Currency = "€";
o3.DatePlaced = new DateTime(2006, 03, 02);
c2.Orders.Add(o3);
And finally the code to serialize our objects.
ObjectContext oc = os.CreateObjectContext();
oc.InitializeRepository();
oc.BeginTransaction();
oc.Serialize(c1);
oc.Serialize(c2);
oc.CommitTransaction();
Be carreful with the use of the method InitializeRepository() as it will
actually flush any existing data in your repository. It's a hard initlization.
In this example we use this to ensure there is no existing data at the moment
we start our application.
Loading Objects
Now we can execute some queries over our repository to check if the objects
have been correctly saved.
Console.WriteLine("All Customers");
foreach(Customer c in oc.Load<Customer>(typeof(Customer)))
Console.WriteLine(c.Firstname);
Console.WriteLine("All Customers whose sum of Orders is more than 100");
foreach(Customer c in oc.Load<Customer>(typeof(Customer), "[ sum(Orders.Price) > 100 ]"))
Console.WriteLine(c.Firstname);
Console.WriteLine("All Orders from Mickey");
foreach(Order o in oc.Load<Order>(typeof(Customer), "[ Firstname = 'Mickey' ].Orders"))
Console.WriteLine("{0} {1} - {2}", o.Price, o.Currency, o.DatePlaced);
Console.WriteLine("All Orders from Mickey where the Price is greater than 200");
foreach(Order o in oc.Load<Order>(typeof(Customer), "[ Firstname = 'Mickey' ].Orders[ Price > 200 ]"))
Console.WriteLine("{0} {1} - {2}", o.Price, o.Currency, o.DatePlaced);
Configuring a Simple Data Store
Euss is based on a flexible provider pattern, resulting to a high flexibility
in the choice of the persistence mecanism. Thus you can switch from a simple
XML file to a production ready infrastructure involving Cache, O/R Mapping,
Distributed systems, simply by changing a configuration file.
As an example we will use a simple XML file. In the last section you will learn
to configure other persistence engines.
In the current Visual Studio .Net project:
-
Right click on the project node and select Add then New Item...
-
In the category named Euss, select Euss Engine Configuration File
-
The default filename is engines.config
-
Click on Add
As you can see, by default the file contains the configuration for an XML data
store.
<?xml version="1.0" encoding="utf-8"?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="Xml">
<PersistenceEngine Name="Xml" Factory="Euss.Evaluant.Xml.XmlFactory">
<FileName>euss.xml</FileName>
</PersistenceEngine>
</PersistenceEngines>
A configuration file is made of several PersistenceEngine tags. It means
that a single file can contain different data store configurations. We provide
a Name and a Factory. The name is used to set the default one in
the root tag. The factory actually sets which persistence engine has to be
used. Inside the provider's tag, there is the actual configuration. For the Xml
Persistence Engine we just need to set the FileName property.
NB: If you give a look at the initialization of the ObjectService in the
sample application, you will see that engines.config is seeked in the current
application's folder. However, as you added the file in the project's folder,
in won't be duplicated in the compilation folder. In Visual Studio .Net 2005
there is an option in the file's properties to copy automaticallye this file
into the build directory. If you don't check this option, you will have to
change the ObjectService's initialization by setting either "..\..\engines.config"
or by setting the full path of this file.
Defining the Metadata
In a general manner, metadata are data that describe other data. As the same
way in Euss, metadata represent information about the domain model, that is to
say all the classes, their properties, and the relationships. Those information
can be extracted from many sources, and Euss let you define them by several
manners. It is important that you understand how important the metadata are.
Without them Euss could not know which type is hidden behind a relation, or if
it is an aggregation or a composition for instance.
To set the metadata you must define a particular Metadata tag in the
configuration file.
Model Files
The easiest way to define the metadata is to use a Model File. If you
used code generation to create your domain classes then you must have an
existing model file. However you might have used an XMI file, then you can use
a command line application shipping with Euss to generate a model file from
this XMI.
If you chose Code Generation as your way to create the domain classes,
you just have to change your configuration file like this:
<PersistenceEngine Name="Xml" Factory="Evaluant.Euss.Xml.XmlProvider">
<FileName>euss.xml</FileName>
<Metadata Type="model">Shop.Domain.xml</Metadata>
</PersistenceEngine>
Metadata Files
Metadata files have a specific schema letting you provide any metadata in an
efficient manner. Euss is able to extract metadata from your assembly, but all
of them are generally not present. For instance, when you define a relationship
by code (using either dynamic proxies or AOP) you never explain if it is an
aggregation or a composition. Also with relationships, there is no way in the
.Net 1.1 to set the type of the related objects. Thus, using metadata files you
will be able to provide a set of none existing metadata that are mandatory.
To add a metadata file from Visual Studio .Net:
-
Right click on the project node and select Add then New Item...
-
In the category named Euss, select Euss Metadata File
-
Name the file Shop.Domain.Metadata.xml
-
Click on Add
Here is the file you content you should define if you whish was to tell Euss
that the relationship between Customer and Order is a
composition.
Shop.Domain.Metadata.xml
<?xml version="1.0" encoding="utf-8" ?>
<?evaluant-application progid="EUSS.MetaData"?>
<Model xmlns="http://euss.evaluant.com/schemas/MetaDataModel.xsd">
<Entity type="Shop:Domain:Customer">
<Reference name="Orders" type="Shop:Domain:Order" composition="true"/>
</Entity>
</Model>
NB: Note that the type are not defined like in C# or VB.Net using dots "."
between namespaces. This is because those informations are used at le low level
layer of Euss, which handles the namespaces by separating them using colons
":".
Then to add those metadata to our persistence engine's configuration, we have
to modify the file engines.config as below:
<PersistenceEngine Name="Xml" Factory="Evaluant.Uss.Xml.XmlProvider">
<FileName>euss.xml</FileName>
<Metadata Type="metadata">Shop.Domain.Metadata.xml</Metadata>
</PersistenceEngine>
Extracting Assembly Information
An assembly is full of metadata. Indeed, the metadata in Euss are mainly made
of class definitions. Thus it should be very easy to extract them from existing
assemblies. This one possible way of getting the metadata. However, as we have
seen in the previous section, an assembly could not contain all important
information. If you need to provide those information directly into your code
you can use a set of .Net Attributes specific for this. Thoses
attributes are defined in the namespace Evaluant.Uss.ObjectContext.Descriptors.
-
PersistenceProperty(fieldName[, type [, composition]])
Here is an example on how we would use it to describe the relationship between Customer
and Order.
private IList<Order> _orders;
[PersistentProperty("_orders", typeof(Order), true)]
public IList<Order> Orders
{
get { return _orders; }
set { _orders = value; }
}
Then, to automatically import those information from the domain assembly, we
had a Metadata section in our configuratioin file:
<PersistenceEngine Name="Xml" Factory="Evaluant.Uss.Xml.XmlProvider">
<FileName>euss.xml</FileName>
<Metadata Type="assembly">Shop.Domain</Metadata>
</PersistenceEngine>
Mixing Them All
The simplest way to define the metadata is to get all the information you can
from an existing model file or your assembly, then to create a specific
metadata file for those that are not defined in you primary source.
As an example we could mix importing the metadata from an assembly and a
metadata file like in the following example:
<PersistenceEngine Name="Xml" Factory="Evaluant.Uss.Xml.XmlProvider">
<FileName>euss.xml</FileName>
<Metadata Type="assembly">Shop.Domain</Metadata>
<Metadata Type="metadata">Shop.Domain.Metadata.xml</Metadata>
</PersistenceEngine>
Executing the Application
Here is the result of the sample application:
All Customers
Shrek
Mickey
All Customers whose sum of Orders is more than 100
Mickey
All Orders from Mickey
123 $ - 21/02/2006 00:00:00
321 $ - 18/01/2006 00:00:00
All Orders from Mickey where the Price is greater than 200
321 $ - 18/01/2006 00:00:00
And if we have a look at the XML file we can see this:
<USS>
<Entity Type="Shop:Domain:Customer" Id="Shop:Domain:Customer:cdc3aa34-d641-442b-8346-e43278bf41d8">
<Attribute Name="CompanyName">Walt Disney</Attribute>
<Attribute Name="Firstname">Mickey</Attribute>
<Attribute Name="Lastname">Mouse</Attribute>
<Reference Role="Orders" RefId="Shop:Domain:Order:adbf2411-0468-4ded-b475-48c587cbb7c7" />
<Reference Role="Orders" RefId="Shop:Domain:Order:c243a6f6-41e7-4fb1-b431-027a98a4be51" />
</Entity>
<Entity Type="Shop:Domain:Order" Id="Shop:Domain:Order:adbf2411-0468-4ded-b475-48c587cbb7c7">
<Attribute Name="Price">123</Attribute>
<Attribute Name="Currency">$</Attribute>
<Attribute Name="DatePlaced">632760768000000000</Attribute>
</Entity>
<Entity Type="Shop:Domain:Order" Id="Shop:Domain:Order:c243a6f6-41e7-4fb1-b431-027a98a4be51">
<Attribute Name="Price">321</Attribute>
<Attribute Name="Currency">$</Attribute>
<Attribute Name="DatePlaced">632731392000000000</Attribute>
</Entity>
<Entity Type="Shop:Domain:Customer" Id="Shop:Domain:Customer:db2a3480-1ddc-4573-b62d-eb5ae5edbb7b">
<Attribute Name="CompanyName">Dreamworks</Attribute>
<Attribute Name="Firstname">Shrek</Attribute>
<Attribute Name="Lastname">Ogre</Attribute>
<Reference Role="Orders" RefId="Shop:Domain:Order:b82700b0-9ac9-496e-a08d-faa78d685401" />
</Entity>
<Entity Type="Shop:Domain:Order" Id="Shop:Domain:Order:b82700b0-9ac9-496e-a08d-faa78d685401">
<Attribute Name="Price">10</Attribute>
<Attribute Name="Currency">€</Attribute>
<Attribute Name="DatePlaced">632768544000000000</Attribute>
</Entity>
</USS>
Configuring the Data Repository
Generic Relational Database
This repository is based on a sql database made of three tables to store
Entities, Relationships and Attributes. You don't need to take care about the
database design. It can be usefull and time saving for little applications or
in order to quicly create prototyping applications.
<?xml version="1.0" encoding="utf-8" ?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="SqlGeneric">
<PersistenceEngine Name="SqlGeneric" Factory="Evaluant.Uss.Sql.SqlProvider">
<ConnectionString>Server=.;DataBase=Shop;UID=sa;PWD=</ConnectionString>
<Metadata Type="model">Shop.Domain.xml</Metadata>
<Dialect>Evaluant.Uss.SqlMapper.MsSqlDialect</Dialect>
<Driver>Evaluant.Uss.SqlMapper.MsSqlDriver</Driver>
</PersistenceEngine>
</PersistenceEngines>
Specific Relational Database
This repository is based on a sql database. The schema of the database can be
designed regarding your needs and the domain model. A mapping file defines the
way entities are stored. This provider allows migrating or starting a new
developpment with an existing database.
<?xml version="1.0" encoding="utf-8" ?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="SqlMapper">
<PersistenceEngine Name="SqlServerSpecific" Factory="Evaluant.Uss.SqlMapper.SqlMapperProvider">
<ConnectionString>Server=.;DataBase=Shop;UID=sa;PWD=</ConnectionString>
<Metadata Type="model">Shop.Domain.xml</Metadata>
<MappingFileName>Shop.Mapping.Xml</MappingFileName>
<Dialect>Evaluant.Uss.SqlMapper.MsSqlDialect</Dialect>
<Driver>Evaluant.Uss.SqlMapper.MsSqlDriver</Driver>
</PersistenceEngine>
</PersistenceEngines>
Adding a Cache
A cache can be added on top of any data repository to increase performances. It
behaves like a layer between the client application and the repository, sending
request only if it is necessary.
<?xml version="1.0" encoding="utf-8" ?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="CacheSql">
<PersistenceEngine Name="CacheSql" Factory="Evaluant.Uss.Cache.CacheProvider">
<Delegator>
<PersistenceEngine Name="SqlMapper" />
</Delegator>
</PersistenceEngine>
<PersistenceEngine Name="SqlServerSpecific" Factory="Evaluant.Uss.SqlMapper.SqlMapperProvider">
<ConnectionString>Server=.;DataBase=Shop;UID=sa;PWD=</ConnectionString>
<Metadata Type="model">Shop.Domain.xml</Metadata>
<MappingFileName>Shop.Mapping.Xml</MappingFileName>
<Dialect>Evaluant.Uss.SqlMapper.MsSqlDialect</Dialect>
<Driver>Evaluant.Uss.SqlMapper.MsSqlDriver</Driver>
</PersistenceEngine>
</PersistenceEngines>
In Memory Database
To get highest performances, you can use the memory provider. All information is
loaded into memory. Thus all requests are done in memory without any disk
access.
<?xml version="1.0" encoding="utf-8" ?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="VMemory">
<PersistenceEngine Name="VMemory" Factory="Evaluant.Uss.Memory.MemoryProvider">
<Metadata Type="model">Shop.Domain.xml</Metadata>
</PersistenceEngine>
</PersistenceEngines>
In case you provide a back-end persistence engine, it will load all its data
during initialization, and then work extremely fast in read mode, while also
sending all write requests to the real repository.
<?xml version="1.0" encoding="utf-8" ?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="NVMemory">
<PersistenceEngine Name="NVMemory" Factory="Evaluant.Uss.Memory.MemoryProvider">
<Metadata Type="model">Shop.Domain.xml</Metadata>
<Delegator>
<PersistenceEngine Name="Xml" />
</Delegator>
</PersistenceEngine>
<PersistenceEngine Name="Xml" Factory="Evaluant.Uss.SqlMapper.XmlProvider">
<FileName>euss.xml</FileName>
<Metadata Type="model">Shop.Domain.xml</Metadata>
</PersistenceEngine>
</PersistenceEngines>
Distributed System
With a remoting provider configuration, all clients can share the same
persitence engine on the server side. For example, the server application can
be configured with a cache engine which will be shared between all clients.
This provides a server centric behaviour to any of you repository. EUSS is
shiped with a host Console application and a Windows Service making use of this
engine. On the client side, the configuration file needs only the location of
this service. The real repository is only described on the server side.
<?xml version="1.0" encoding="utf-8" ?>
<PersistenceEngines xmlns="http://euss.evaluant.com/schemas/EngineConfiguration.xsd" DefaultEngine="Remoting">
<PersistenceEngine Name="Remoting" Factory="Evaluant.Uss.Remoting.RemoteProvider">
<Host>192.168.1.100</Host>
<Port>8085</Port>
</PersistenceEngine>
</PersistenceEngines>