Generated code - Using the entity collection classes, SelfServicing
Preface
Per entity definition in a project, LLBLGen Pro will generate an entity collection class. This class is used to work on
more than one entity at the same time and it is used to retrieve more than one entity of the same type from the database.
This section describes the different kinds of functionality bundled in the collection classes and how to utilize that functionality in
your code.
Entity retrieval into an entity collection object
Entity collection objects can be filled with entities retrieved from the database using several ways. Below we'll walk you
through the ones you will use the most.
Using a query
Querying for a set of entities and fetch them into an EntityCollection<T>
can be done in several ways. Below we'll fetch all Customer instances
which match the filter Country=='Germany'. This is illustrated using
three different queries: one using our low-level native Api, one using our
QuerySpec API and one using Linq to LLBLGen Pro. When using QuerySpec or
Linq to LLBLGen Pro, be sure to have the correct namespaces included in your
code file.
- QuerySpec (C#)
- QuerySpec (VB.NET)
- Linq (C#)
- Linq (VB.NET)
- Low-level API (C#)
- Low-level API (VB.NET)
// QuerySpec, C#
var customers = new CustomerCollection();
var qf = new QueryFactory();
var q = qf.Customer.Where(CustomerFields.Country == "Germany");
customers.GetMulti(q);
' QuerySpec, VB.NET
Dim customers As CustomerCollection()
Dim qf As new QueryFactory()
Dim q As qf.Customer.Where(CustomerFields.Country = "Germany")
customers.GetMulti(q)
// Linq, C#
var metaData = new LinqMetaData();
var q = from c in metaData.Customer where c.Country == "Germany" select c;
var customers = ((ILLBLGenProQuery)q).Execute<CustomerCollection>();
' Linq, VB.NET
Dim metaData As new LinqMetaData()
Dim q = From c in metaData.Customer Where c.Country = "Germany" Select c
Dim customers = CType(q, ILLBLGenProQuery).Execute(Of CustomerCollection)()
// Low-level API, C#
CustomerCollection customers = new CustomerCollection();
customers.GetMulti(CustomerFields.Country=="Germany");
' Low-level API, VB.NET
Dim customers As New CustomerCollection()
customers.GetMulti(CustomerFields.Country="Germany")
Using a related entity
The easiest way to retrieve a set of entities in an entity collection class, is by using a related entity, which in turn is used as
a filter. For example, let's use our user "CHOPS" again and let's retrieve all the order entities for that customer:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS"); // fetches the entity with pk 'CHOPS'
OrderCollection orders = customer.Orders; // Lazy-loads the related orders
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS") ' fetches the entity with pk 'CHOPS'
Dim orders As OrderCollection = customer.Orders ' Lazy-loads the related orders
The entity inside 'customer' is used to filter the orders in the persistent storage and retrieve them as entity instances in the
OrderCollection 'orders'. The collection is filled internally by calling an overloaded version of customer.
GetMultiOrders().
That method will do the actual retrieval. You can call
GetMultiOrders() yourself directly too:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS"); // fetches the entity with pk 'CHOPS'
OrderCollection orders = customer.GetMultiOrders(false);
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS") ' fetches the entity with pk 'CHOPS'
Dim orders As OrderCollection = customer.GetMultiOrders(False)
'false/False' is passed in as parameter, which tells
GetMultiOrders to fetch the entities from the persistent storage if the orders for
this customer aren't already fetched, and just return the collection if the orders are already fetched. This is called
lazy loading or
Load on demand, since the collection of orders is loaded when you ask for it, not when the customer is loaded.
When you specify true/True, GetMultiOrders() will refetch the related order entities
even if the orders were already fetched. This can be
handy if you want to refresh the set of orders in a customer you hold in memory.
This is called a
forced fetch.
In our example of Customer and an Orders collection,
the
CustomerEntity has a property called
AlwaysFetchOrders. Default for this property is 'false'. Setting this property to
to true, will load the related entity each time you access the property. The
aformentioned
forced fetch is different from AlwaysFetch
FieldMappedOnRelation in that
a forced fetch will clear the collection first, while AlwaysFetch
FieldMappedOnRelation does not. A forced fetch will thus remove new entities
added to the collection from that collection as these are not yet stored in the database.
To apply sorting and a maximum number of items to return for every time you do customer.Orders for that particular customer object,
you can set the sortclauses and the maximum number of entities to return by calling customer.
SetCollectionParametersOrders():
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
ISortExpression sorter = new SortExpression(OrderFields.OrderDate | SortOperator.Descending);
customer.SetCollectionParametersOrders(10, sorter);
OrderCollection orders = customer.GetMultiOrders(false);
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
Dim sorter As ISortExpression = New SortExpression( _
New SortClause(OrderFields.OrderDate, SortOperator.Descending))
customer.SetCollectionParametersOrders(10, sorter)
Dim orders As OrderCollection = customer.GetMultiOrders(False)
Now a call to
GetMultiOrders or customer.Orders (which is used in databinding scenarios also) will sort the orders on OrderDate,
descending and will return a maximum of 10 order entities.
If Order is in an inheritance hierarchy, the fetch is polymorphic. This means that if the customer entity, in this case customer "CHOPS", has references to
instances of different derived types of Order, every instance in customer.Orders is of the type it represents, which effectively means that
not every instance in Orders is of the same type. See for more information about polymorphic fetchs also
Polymorphic fetches.
Using a prefetch path
An easy way to retrieve a set of entities can be by using a Prefetch Path, to read related entities together with the entity or entities to fetch. See
for more information about Prefetch Paths and how to use them:
Prefetch Paths.
Entity data manipulation using collection classes
When you have fetched a set of entities in a collection and for example have
bound this collection to a grid, the user probably has altered one or more
entity's fields in the collection. When you want to save these changes to
the persistent storage, the easiest way to do so is to use the
collection's
SaveMulti() method. This method traverses all objects
inside the collection and if the object is
dirty, (which means, it's
been changed and should be updated in the persistent storage) it is saved.
The complete save action is done in a single transaction if no transaction
is currently available, otherwise the action is performed inside the
existing transaction. (See for more information about transactions the
section
Transactions).
Entity collections support
saving entities recursively, as discussed in
Saving entities recursively section in Using the entity classes. Just pass true
for the
recurse parameter in one of
SaveMulti()'s overloads
and all entities are saved recursively.
Deleting one or more entities from the persistent storage
To delete a set of entities from the persistent storage, you can use the
collection's
DeleteMulti() method to achieve your goal. This method
works with the objects inside the collection and deletes them one by one
from the persistent storage inside a transaction if no transaction is
currently available, otherwise the action is performed inside the existing
transaction. (See for more information about transactions the section
Transactions).
Note:
|
DeleteMulti(*) is not supported for entities which are in a
hierarchy of type TargetPerEntity. This is by design, as
the delete action isn't possible in one go with proper checks due to referential integrity issues.
|
Client side sorting
The entity collections don't implement
IBindingList, as the entity collections use
EntityView<T> classes
to bind to grids and other controls and let these views do the filtering and sorting of the entity collection data.
It's recommended you use an
EntityView<T> class to sort and filter an entity collection instead of using the Sort() methods directly on an
entity collection. See for more information about
EntityView<T> classes:
Generated code - using entity views with entity collections.
As entity collections implement
IEnumerable<T> you can also use Linq
to Objects to sort and filter the entities inside the collection. With both
methods (EntityView and Linq to Objects) the data inside the collection
isn't altered nor sorted.
Hierarchical projections of entity collections
LLBLGen Pro allows you to create projections of the full graph of all the
entities inside a given entity collection onto a DataSet or a Dictionary
object. A hierarchical projection is a projection where all entities in the
entity collection plus all their related entities and so on, are grouped
together per entity type. Say you have the following graph in memory: a set
of
CustomerEntity instances, which contain each a set of
OrderEntity instances and each
OrderEntity instance refers to an
EmployeeEntity instance. This projection functionality is implemented
on the entity collection class, in the method
CreateHierarchicalProjection.
With LLBLGen Pro it's possible to
project this graph onto a DataSet which will result in per entity type a new
DataTable object with all instances of that entity type (and the data
relations setup correctly). You can also project it onto a Dictionary with
per entity type an entity collection which contains the entities of that
type. Projections are defined in instances of the
IViewProjectionData
interface which is implemented in the
ViewProjectionData class. This
class combines per type projections (as shown below in the example) which
are then used as one projection on the complete graph.
By default,
when projecting to a DataSet, only the entity types which have instances in
the graph get a DataTable in the resulting DataSet. If you want to have a
DataSet where there are always an expected number of DataTable instances (so
for entities which aren't in the graph, they're empty), you can pre-create
the DataSet and pass the pre-created DataSet to the projection routine.
LLBLGen Pro's runtime library contains helper routines to produce an empty
DataSet with empty DataTables, the correct columns and the proper
DataRelation objects setup based on a prefetch path specified. Please
consult the
LLBLGen Pro Reference Manual for the
GeneralUtils
class'
ProduceEmptyDataSet and
ProduceEmptyDataTable routines.
Examples
The following examples will show you both projections (to DataSet and to Dictionary) of the earlier described graph of customers - Orders - Employees. The examples will first fetch the complete graph of customers, orders and employees and will then create a projection of that graph. Usage of custom projections per property and additional filters is also shown by the examples.
Please refer to the
LLBLGen Pro reference manual for details about the generic ViewProjectionData class and its constructors.
The queries are using the low-level API, but you can also use Linq to
LLBLGen Pro or QuerySpec for these queries.
Projection to DataSet
// C#
CustomerCollection customers = new CustomerCollection();
PrefetchPath path = new PrefetchPath(EntityType.CustomerEntity);
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees);
customers.GetMulti(null, path);
// setup projections per type.
List<IEntityPropertyProjector> customerProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity));
// add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
// the value of the IsNew property of the customer entities.
customerProjections.Add(new EntityPropertyProjector(new EntityProperty("IsNew"), "IsNew"));
List<IEntityPropertyProjector> orderProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity));
List<IEntityPropertyProjector> employeeProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity));
List<IViewProjectionData> projectionData = new List<IViewProjectionData>();
// create the customer projection information. Specify a filter so only customers from Germany
// are projected.
projectionData.Add(new ViewProjectionData<CustomerEntity>(
customerProjections, (CustomerFields.Country == "Germany"), true));
projectionData.Add(new ViewProjectionData<OrderEntity>(orderProjections, null, false));
projectionData.Add(new ViewProjectionData<EmployeeEntity>(employeeProjections));
DataSet result = new DataSet("projectionResult");
customers.CreateHierarchicalProjection(projectionData, result);
' VB.NET
Dim customers As New CustomerCollection()
Dim path As New PrefetchPath(EntityType.CustomerEntity)
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees)
customers.GetMulti(Nothing, path)
' setup projections per type.
Dim customerProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity))
' add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
' the value of the IsNew property of the customer entities.
customerProjections.Add(New EntityPropertyProjector(New EntityProperty("IsNew"), "IsNew"))
Dim orderProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity))
Dim employeeProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity))
Dim projectionData As New List(Of IViewProjectionData)()
' create the customer projection information. Specify a filter so only customers from Germany
' are projected.
projectionData.Add(New ViewProjectionData(Of CustomerEntity)( _
customerProjections, (CustomerFields.Country = "Germany"), True))
projectionData.Add(New ViewProjectionData(Of OrderEntity)(orderProjections, Nothing, False))
projectionData.Add(New ViewProjectionData(Of EmployeeEntity)(employeeProjections))
Dim result As New DataSet("projectionResult")
customers.CreateHierarchicalProjection(projectionData, result)
The same projectors as used with the projection to the DataSet are usable with a projection to a Dictionary, which is almost equal to the DataSet example.
Projection to Dictionary
// C#
CustomerCollection customers = new CustomerCollection();
PrefetchPath path = new PrefetchPath(EntityType.CustomerEntity);
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees);
customers.GetMulti(null, path);
// setup projections per type.
List<IEntityPropertyProjector> customerProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity));
// add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
// the value of the IsNew property of the customer entities.
customerProjections.Add(new EntityPropertyProjector(new EntityProperty("IsNew"), "IsNew"));
List<IEntityPropertyProjector> orderProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity));
List<IEntityPropertyProjector> employeeProjections = EntityFields.ConvertToProjectors(
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity));
List<IViewProjectionData> projectionData = new List<IViewProjectionData>();
// create the customer projection information. Specify a filter so only customers from Germany
// are projected.
projectionData.Add(new ViewProjectionData<CustomerEntity>(
customerProjections, (CustomerFields.Country == "Germany"), true));
projectionData.Add(new ViewProjectionData<OrderEntity>(orderProjections, null, false));
projectionData.Add(new ViewProjectionData<EmployeeEntity>(employeeProjections));
Dictionary<Type, IEntityCollection> projectionResults = new Dictionary<Type, IEntityCollection>();
customers.CreateHierarchicalProjection(projectionData, projectionResults);
' VB.NET
Dim customers As New CustomerCollection()
Dim path As New PrefetchPath(EntityType.CustomerEntity)
path.Add(CustomerEntity.PrefetchPathOrders).SubPath.Add(OrderEntity.PrefetchPathEmployees)
customers.GetMulti(Nothing, path)
' setup projections per type.
Dim customerProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.CustomerEntity))
' add an additional projector so the destination DataTable will have an additional column called 'IsNew' with
' the value of the IsNew property of the customer entities.
customerProjections.Add(New EntityPropertyProjector(New EntityProperty("IsNew"), "IsNew"))
Dim orderProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.OrderEntity))
Dim employeeProjections As List(Of IEntityPropertyProjector) = EntityFields.ConvertToProjectors( _
EntityFieldsFactory.CreateEntityFieldsObject(EntityType.EmployeeEntity))
Dim projectionData As New List(Of IViewProjectionData)()
' create the customer projection information. Specify a filter so only customers from Germany
' are projected.
projectionData.Add(New ViewProjectionData(Of CustomerEntity)( _
customerProjections, (CustomerFields.Country = "Germany"), True))
projectionData.Add(New ViewProjectionData(Of OrderEntity)(orderProjections, Nothing, False))
projectionData.Add(New ViewProjectionData(Of EmployeeEntity)(employeeProjections))
Dim projectionResults As New Dictionary(Of Type, IEntityCollection)()
customers.CreateHierarchicalProjection(projectionData, projectionResults)
Note: |
If you just want a structure with per entity type a collection with all the instances of that type in the entity graph, so not really a projection to new copies of the entities, please use the routine ObjectGraphUtils.ProduceCollectionsPerTypeFromGraph. The ObjectGraphUtils class is located in the ORMSupportClasses namespace and contains a variety of routines working on entity graphs. Please see the LLBLGen Pro reference manual for details on this class and this method. |
Tracking entity remove actions
Removing an entity from a collection by calling
entitycollection.
Remove(toRemove) or
entitycollection.
RemoveAt(index) is an ambiguous action: do you want to remove the entity from the collection to further process the entities left, or do you want to get rid of the entity completely, both in-memory and also in the database? This is the reason why LLBLGen Pro doesn't perform deletes on the database automatically if you remove an entity from a collection, you have to explicitly specify what entities to remove.
Tracking which entities are removed from an entity collection to be removed from the database can be a bit cumbersome if the collection is bound to a grid for example. To overcome this, LLBLGen Pro has a feature which makes an entity collection track the entities removed from it by using
another entity collection. This way, you can keep track of which entities are removed from the entity collection and pass them on to a
Unit of work object for persistence in one transaction together with the rest of the entities which have changed.
The extra collection is necessary because an entity which is removed from the collection isn't there anymore, so it can't be referred to by the collection itself. To enable removal tracking in an entity collection, set its
RemovedEntitiesTracker property to a collection into which you want to track the removed entities from the collection. This collection can then be added to a
UnitOfWork object for deletion by using the method
unitofwork.
AddCollectionForDelete(collectionWithEntitiesToDelete) or you can delete the entities by calling
entitycollection.
DeleteMulti() on the entity collection with the tracked removed entities.
The following example illustrates this.
// C#
// First fetch all customers from Germany with their orders.
CustomerCollection customers = new CustomerCollection();
PrefetchPath path = new PrefetchPath(EntityType.CustomerEntity);
path.Add(CustomerEntity.PrefetchPathOrders);
customers.GetMulti(CustomerFields.Country == "Germany", path);
// we now will add a tracker collection to the orders collection of customer 0.
OrderCollection tracker = new OrderCollection();
customers[0].Orders.RemovedEntitiesTracker = tracker;
// after this, we can do this:
customers[0].Orders.Remove(myOrder);
// and myOrder is removed from the in-memory collection customers[0].Orders
// and it's placed in tracker. We can now delete the entities in tracker
// by using a UnitOfWork object or by calling tracker.DeleteMulti().
' VB.NET
' First fetch all customers from Germany with their orders.
Dim customers As New CustomerCollection()
Dim path As New PrefetchPath(EntityType.CustomerEntity)
path.Add(CustomerEntity.PrefetchPathOrders)
customers.GetMulti(CustomerFields.Country = "Germany", path)
' we now will add a tracker collection to the orders collection of customer 0.
Dim tracker As New OrderCollection()
customers(0).Orders.RemovedEntitiesTracker = tracker
' after this, we can do this:
customers(0).Orders.Remove(myOrder)
' and myOrder is removed from the in-memory collection customers[0].Orders
' and it's placed in tracker. We can now delete the entities in tracker
' by using a UnitOfWork object or by calling tracker.DeleteMulti().
Note: |
Tracking removal of an entity isn't used by the Clear() method, because Clear is often used to clean up a collection and not to remove entities from the database, so to avoid false positives and the deletion of entities which weren't suppose to be deleted, removal tracking isn't available for the Clear method. |