Up until now, the class Product only contains scalar properties. Genome allows you to map interconnected object graphs, where an object instance can point to one or more related objects. This section is covered in Step2, found under {Genome installation folder}\Samples\GettingStarted\Step2.
Let us extend the sample domain model to include some relations in the object graph:

The model above contains the following singular object relations:
Additionally, the following 1:n relations exist in the model:
Finally, the model contains some arbitrary queries for Product:
To have an n:m relationship as well, let us create two additional classes:

Employee to Territory n:m relationship
Employee defines a Collection of Territories it is assigned to.
Territory in turn has a collection of assigned Employees.
Genome provides a set of standard patterns to map relationships in the object graph. In addition, the LINQ mapping feature of Genome allows you to map relationships and arbitrary queries in the object graph. Properties of the domain model that have been mapped with LINQ or OQL can be reused in other LINQ (or OQL) mappings, thereby improving the code’s readability and maintainability.
The following standard relation patterns can be mapped with Genome:
|
Name |
NearObjectReference |
|
Description |
Represents a readable and writeable singular object reference from class A to class B. |
|
Representation |
Property of type B on class A. |
|
Representation |
Foreign key field(s) in table of A pointing to table of B. |
|
Example |
public abstract class OrderDetail : Persistent { public abstract Order Order }
<Type name="OrderDetail"> <Member name="Order"> <NearObjectReference/> </Member> </Type>
OrderDetail has a property Order that points to the order that the detail belongs to. |
|
Name |
OneToManyCollection |
|
Description |
Represents a 1:n relationship between two classes (A:B) |
|
Representation |
Property of Collection<B> on class A. This mapping feature expects that
there is a NearObjectReference mapped in class B pointing to A. |
|
Representation |
Query that loads all instances of B whose foreign key field(s) point to this A. |
|
Example |
public abstract class Order : Persistent { public abstract
Collection<OrderDetail> Items }
<Type name="Order"> <Member name="Items"> <OneToManyCollection/> </Member> </Type>
Order has a property Items that retrieves all OrderDetails that point to this order. Note that the property has a getter only, as items are added and removed through the operations of the collection. |
|
Name |
ManyToManyCollection |
|
Description |
Represents an n:m relationship between two classes (A:B). |
|
Representation |
Property of Collection<B> on class A. Property of Collection<A> on class B. The relationship can also be implemented and mapped in a single direction (omitting Collection<A> in B). This mapping feature can optionally
use an existing association class AB in the class model, whose instances
point to the corresponding instances of A and B to associate them. The
mapping feature can also hide the association class from the class model. |
|
Representation |
Connection table AB with foreign keys to A and B. Query that loads all instances of B that have an entry pointing to this A in the association table AB (and vice versa if mapped in both directions). |
|
Example |
public abstract class Employee : Persistent { public abstract
Collection<Territory> Territories }
public abstract class Territory : Persistent { public abstract
Collection<Employee> Employees }
<Type name="Employee"> <Member name="Territories"> <ManyToManyCollection> <Automatic/> </ManyToManyCollection> </Member> </Type>
<Type name="Territory"> <Member name="Employees"> <ManyToManyCollection> <Reverse/> </ManyToManyCollection> </Member> </Type>
Employee has a collection of associated territories. Each territory has a collection of associated employees. Note that the properties have a getter only, as items are added and removed through the operations of the collection. |
The following arbitrary class relationships can be mapped:
|
Name |
Arbitrary relationship to a set of objects |
|
Description |
Represents an arbitrary relationship from class A to B, expressed by an LINQ query. |
|
Representation |
Property of Set<B> on class A. The query is mapped by an <Linq/> element in the mapping file. |
|
Representation |
There is no specific pattern of representation for this relationship in the database, as it is expressed purely by a custom query mapped with LINQ. |
|
Example |
public abstract class Product : Persistent { public abstract Set<Product>
AlsoOrderedProducts }
<Type name="Product"> <Member name="AlsoOrderedProducts" > <Linq><![CDATA[ (from od in DataDomain.Extent<OrderDetail>() where od.Order.Items select od.Product).Distinct() ]]></Linq> </Member> </Type>
Product has a property called AlsoOrderedProducts which returns all other Products bought by Customers in the same Order. Note that the property has a getter only, as the property represents the read only results of a query. |
|
Name |
Arbitrary relationship to a single object |
|
Description |
Represents a read only singular association between class A and B expressed by an arbitrary LINQ query. |
|
Representation |
Property of B on class A. The query is mapped by an <Linq/> element in the mapping file. |
|
Representation |
There is no specific pattern of representation for this relationship in the database, as it is expressed purely by a custom query mapped with LINQ. |
|
Example |
public abstract class Product : Persistent { public abstract Product
TopAlsoOrderedProcut }
<Type name="Product"> <Member name="TopAlsoOrderedProduct" > <Linq><![CDATA[ (from p in AlsoOrderedProducts orderby p.OrderCount select p).FirstOrDefault() ]]></Linq> </Member> </Type> Product has a property called TopOrderedProducts which returns the most often ordered product of the set of AlsoOrderedProducts. Note that the property has a getter only, as the property represents the read only results of a query. Note that the LINQ mapping reuses the properties AlsoOrderedProducts and OrderCount of product, which are also mapped with LINQ. As you can see, composing LINQ expressions from existing ones mapped in the domain model can improve query logic readability and maintainability. |
Genome also supports returning scalar values from an LINQ query which are associated with the object instance and hence mapped as an LINQ property of the instance.
|
Name |
Arbitrary scalar value |
|
Description |
Returns an arbitrary scalar value from an LINQ query associated with the current instance of the object. |
|
Representation |
Property of scalar type on class A. The query is mapped by an <Linq/> element in the mapping file. |
|
Representation |
There is no specific pattern of representation for this relationship in the database, as it is expressed purely by a custom query mapped with LINQ. |
|
Example |
public abstract class Product : Persistent { public abstract int OrderCount { get; } }
<Type name="Product"> <Member name="OrderCount" > <Linq><![CDATA[ DataDomain.Extent<OrderDetail>(). Where(od => od.Product == this). Sum(od => (int?)od.Quantity) ?? 0 ]]></Linq> </Member> </Type>
Product has a property called OrderCount which returns the sum of all orders for this Product. Note that the property has a getter only, as the property represents the read only results of a query. |
|
Name |
Set of scalar values |
|
Description |
Returns a set of scalar values from an arbitrary LINQ query associated with the current instance of the object. |
|
Representation |
Property of Set<T> on class A where T is a scalar type. The query is mapped by an <Linq/> element in the mapping file. |
|
Representation |
There is no specific pattern of representation for this relationship in the database, as it is expressed purely by a custom query mapped with LINQ. |
|
Example |
public abstract class Product : Persistent { public abstract Set<int>
LastTenOrderedQuantities }
<Type name="Product"> <Member name="LastTenOrderedQuantities" > <Linq><![CDATA[ (from od in DataDomain.Extent<OrderDetail>() where od.Product == this orderby od.Order.OrderDate descending select od.Quantity).Take(10) ]]></Linq> </Member> </Type>
Product has a property called LastTenOrderedQuantities which returns the product quantity ordered in the last ten orders as a set of int. Note that the property has a getter only, as the property represents the read only results of a query. |
|
Name |
Array of scalar values |
|
Description |
Returns an array of scalar values from an arbitrary LINQ query associated with the current instance of the object. |
|
Representation |
Property of T[] on class A where T is a scalar type. The query is mapped by an <Linq/> element in the mapping file. |
|
Representation |
There is no specific pattern of representation for this relationship in the database, as it is expressed purely by a custom query mapped with LINQ. |
|
Example |
public abstract class Product : Persistent { public abstract int[] LastTenOrderedQuantitiesArray }
<Type name="Product"> <Member name="LastTenOrderedQuantitiesArray" > <Linq><![CDATA[ LastTenOrderedQuantities.ToArray() ]]></Linq> </Member> </Type>
Product has a property called LastTenOrderedQuantitiesArray which returns the product quantity ordered in the last ten orders as array of int. Note that the property has a getter only, as the property represents the read only results of a query. |
The difference between Set<T> and T[] is that Genome loads T[] as soon as the mapped property is accessed, while Set<T> is only loaded when it is enumerated.
Genome also supports returning new transient types from an LINQ query as well as returning arbitrary object[] or arrays of persistent types. These possibilities will be discussed later.
First, let’s add the new properties to the Product class:
public abstract class Product : Persistent
{
…
public abstract Product TopAlsoOrderedProduct { get; }
public abstract Set<Product> AlsoOrderedProducts { get; }
public abstract Set<int> LastTenOrderQuantities { get; }
public abstract int[] LastTenOrderQuantitiesArray { get; }
public abstract int OrderCount { get; }
…
}
Additional Properties on Business/Product.cs
After that, let us implement the Customer class:
using System;
using TechTalk.Genome;
namespace Business
{
public abstract class Customer : Persistent
{
public abstract Guid Id { get; set; }
public abstract string Name { get; set; }
public abstract Set<Order> Orders { get; }
public Customer(string name)
{
Id = Guid.NewGuid();
Name = name;
}
}
}
Customer has a Set of Orders. Note that Customer uses a client-side generated GUID which is initialised in the constructor as primary key identity.
Next we create the Order class:
using System;
using TechTalk.Genome;
namespace Business
{
public abstract class Order : Persistent
{
public abstract int Id { get; }
public abstract DateTime OrderDate { get; set; }
public abstract Customer Customer { get; set; }
public abstract Collection<OrderDetail> Items { get; }
public Order()
{
OrderDate = DateTime.Now;
}
}
}
The OrderDetail class associates Products with Orders:
using TechTalk.Genome;
namespace Business
{
public abstract class OrderDetail : Persistent
{
public abstract Order BelongsToOrder { get; }
public abstract Product Product { get; set; }
public abstract int Quantity { get; set; }
public OrderDetail(Product product, int quantity)
{
Product = product;
Quantity = quantity;
}
}
}
Note that BelongsToOrder is read only, as the OrderDetail is added to the Order.Items collection which sets the BelongsToOrder property of the OrderDetail.
Finally, let us implement Employee and Territory to have the n:m relationship in the class model:
using System;
using TechTalk.Genome;
namespace Business
{
public abstract class Employee : Persistent
{
public abstract Guid Id { get; set; }
public abstract string Name { get; set; }
public abstract Collection<Territory> Territories { get; }
public Employee(string name)
{
Id = Guid.NewGuid();
Name =name;
}
}
}
using System;
using TechTalk.Genome;
namespace Business
{
public abstract class Territory : Persistent
{
public abstract Guid Id { get; set; }
public abstract string Name { get; set; }
public abstract Collection<Employee> Employees { get; }
public Territory(string name)
{
Id = Guid.NewGuid();
Name = name;
}
}
}
Both classes use a client-side generated GUID which is initialised in the constructor as primary key identity.
To map the calculated properties, navigate to each of the properties of Product and select “Map as LINQ” in the “Genome Mapping” context menu:

Genome context menu in the C# source code editor for mapping a property in LINQ
The forward engineering wizard creates an empty LINQ property mapping where you can fill in the corresponding query. Due to compatibility you can always choose OQL as an equivalent form of property mapping, however OQL support may discontinue in the future versions of Genome.
You can conveniently switch between mapping file and source code (and vice versa) by selecting “Go to member” or “Go to type” in the Genome Mapping context menu in the mapping file:

Genome context menu in Xml mapping editor of the DataDomain schema project for navigating to C# source code
Navigation also works in the other direction, from the source code to the mapping file.
Alternatively, you can use a keyboard shortcut (default: Ctrl-Alt-G) which brings you to the corresponding member or type.
public abstract Set<Product> AlsoOrderedProducts { get; }
Additional property AlsoOrderedProducts in Business/Product.cs
<Type name="Product">
<Member name="AlsoOrderedProducts" >
<Linq><![CDATA[
(from od in DataDomain.Extent<OrderDetail>()
where od.Order.Items
.Select(i => i.Product).Contains(this)
select od.Product).Distinct()
]]></Linq>
</Member>
</Type>
LINQ mapping for AlsoOrderedProducts in Business.Mapping/Product.xml
The LINQ above selects all OrderDetails which belong to an Order where the Product list of all Items contain this Product. The resulting OrderDetails are then projected to a distinct Set of Products.
public abstract Product TopAlsoOrderedProduct { get; }
Additional property TopAlsoOrderedProduct in Business/Product.cs
<Member name="TopAlsoOrderedProduct" >
<Linq><![CDATA[
(from p in AlsoOrderedProducts
orderby p.OrderCount
select p).FirstOrDefault()
]]></Linq>
</Member>
LINQ mapping for TopAlsoOrderedProduct in Business.Mapping/Product.xml
The query above reuses the previous definition of AlsoOrderedProducts, orders it by their OrderCount (specified below) and returns the first Product of the result if exists.
public abstract int OrderCount { get; }
Additional property OrderCount in Business/Product.cs
<Member name="OrderCount" >
<Linq><![CDATA[
DataDomain.Extent<OrderDetail>().
Where(od => od.Product == this).
Sum(od => (int?)od.Quantity) ?? 0
]]></Linq>
</Member>
LINQ mapping for OrderCount in Business.Mapping/Product.xml
The code above finds all OrderDetails that reference this Product and then sums the result’s Quantities. Since Sum<> would return null on an empty set, we have to specify int? as the working type and finally decide what to do with a null value. Sum’s behaviour is deliberately designed so it resembles the SQL sum function.
public abstract Set<int> LastTenOrderQuantities { get; }
Additional property LastTenOrderQuantities in Business/Product.cs
<Member name="LastTenOrderedQuantities" >
<Linq><![CDATA[
(from od in DataDomain.Extent<OrderDetail>()
where od.Product == this
orderby od.Order.OrderDate descending
select od.Quantity).Take(10)
]]></Linq>
</Member>
LINQ mapping for LastTenOrderQuantities in Business.Mapping/Product.xml
The query above selects the last ten OrderDetails for this Product and keeping only the Quantity of the containing OrderDetail, resulting a Set of int.
public abstract int[] LastTenOrderQuantitiesArray { get; }
Additional property LastTenOrderQuantitiesArray in Business/Product.cs
<Member name="LastTenOrderQuantitiesArray" >
<Linq>
<![CDATA[
LastTenOrderQuantities.ToArray()
]]>
</Linq>
</Member>
LINQ mapping for LastTenOrderQuantitiesArray in Business.Mapping/Product.xml
The query above reuses LastTenOrderQuantities and returns an array of int instead of a Set of int.
Customer can also be forward engineered:
<Type name="Customer">
<RootInheritance/>
<PrimaryKey>
<Key>Id</Key>
</PrimaryKey>
<Sealed/>
<CodeGeneratedProxy/>
<Member name="Id">
<PersistentField/>
</Member>
<Member name="Orders">
<Linq><![CDATA[
DataDomain.Extent<Order>().
Where(o => o.Customer == this)
]]></Linq>
</Member>
<Member name="Name">
<PersistentField />
</Member>
</Type>
After forward engineering, you need to fill in the LINQ tag to enumerate all Orders of the current Customer.
The forward engineering wizard can automatically map Order fully. Having specified again a server-generated identity, we need to set up the NativeIdGenerator property for the identity field:
<Type name="Order">
<RootInheritance/>
<PrimaryKey>
<Key>Id</Key>
</PrimaryKey>
<Sealed/>
<CodeGeneratedProxy/>
<Member name="Id">
<PersistentField NativeIdGenerator="true"/>
</Member>
<Member name="OrderDate">
<PersistentField/>
</Member>
<Member name="Customer">
<NearObjectReference/>
</Member>
<Member name="Items">
<OneToManyCollection/>
</Member>
</Type>
For OrderDetail, the forward engineering wizard cannot automatically detect the primary key properties of the class. The primary key is composed of Order and Product and has to be specified accordingly:
<Type name="OrderDetail">
<RootInheritance/>
<PrimaryKey>
<Key>BelongsToOrder</Key>
<Key>Product</Key>
</PrimaryKey>
<Sealed/>
<CodeGeneratedProxy/>
<Member name="Order">
<NearObjectReference/>
</Member>
<Member name="Product">
<NearObjectReference/>
</Member>
<Member name="Quantity">
<PersistentField/>
</Member>
</Type>
Business.Mapping/OrderDetail.xml
After creating the mapping file for Employee by forward engineering, the collection mapping has to be updated to ManyToManyCollection:
<Type name="Employee">
<RootInheritance/>
<PrimaryKey>
<Key>Id</Key>
</PrimaryKey>
<Sealed/>
<CodeGeneratedProxy/>
<Member name="Id">
<PersistentField/>
</Member>
<Member name="Name">
<PersistentField />
</Member>
<Member name="Territories">
<ManyToManyCollection>
<Automatic />
</ManyToManyCollection>
</Member>
</Type>
Similarly, the forward engineered mapping of Territory needs to be configured to point back to the same connection table as employee is using for the association:
<Type name="Territory">
<RootInheritance/>
<PrimaryKey>
<Key>Id</Key>
</PrimaryKey>
<Sealed/>
<CodeGeneratedProxy/>
<Member name="Id">
<PersistentField/>
</Member>
<Member name="Name">
<PersistentField/>
</Member>
<Member name="Employees">
<ManyToManyCollection>
<Reverse/>
</ManyToManyCollection>
</Member>
</Type>
Business.Mapping/Territory.xml
To perform queries on the domain model, let us implement a method which creates some simple test data:
public static void CreateTestData()
{
Helper.InitialiseDatabase();
using(Context.Push(LocalContext.Create()))
{
Product[] products = {
Helper.dd.New<Product>("Genome Evaluation", 0.0m),
Helper.dd.New<Product>("Genome Community", 0.0m),
Helper.dd.New<Product>("Genome Express", 299.0m),
Helper.dd.New<Product>("Genome Professional",
1800.0m),
Helper.dd.New<Product>("Genome service hour",
125.0m),
Helper.dd.New<Product>("Genome database adapter",
5000.0m),
};
Helper.dd.New<Customer>("Jim");
Helper.dd.New<Customer>("Jane");
Helper.dd.New<Customer>("John");
Helper.dd.New<Customer>("Mary");
Context.CommitCurrent();
int orderCount;
Random randGen = new Random(0);
foreach (Customer customer in Helper.dd.Extent<Customer>())
{
orderCount = randGen.Next(5);
for(int i=0; i<orderCount; i++)
{
Order o = Helper.dd.New<Order>();
o.Customer = customer;
o.Items.Add(Helper.dd.New<OrderDetail>(
products[0],
1
));
o.Items.Add(Helper.dd.New<OrderDetail>(
products[randGen.Next(5)+1],
randGen.Next(10) + 1
));
}
}
Context.CommitCurrent();
}
}
CreateTestData() in Client/Program.cs
| Working with persistent objects (Create, Update, Delete) | Inheritance |