Using the EntityView<T> class

The EntityView<T> is a class which is used to create in-memory views on an entity collection object and allows you to filter and sort an in-memory entity collection without actually touching the data inside the entity collection. An entity collection can have multiple EntityView<T> objects, similar to the DataTable - DataView combination. This section describes how to use the EntityView<T> class in various different scenarios.

EntityView<T> and Linq to Objects

EntityView<T> implements IEnumerable<T> which allows you to access its contents in foreach loops or for example in Linq to Object queries. This enables you to create specific views on a larger entity collection which are used in different processing methods utilizing Linq to Objects over the views created.

It can be preferable to use EntityView<T> objects instead of a Linq to Objects query over the entity collection object as views are kept in-sync with the underlying data, a linq query's results isn't.

DataBinding and EntityView<T> instances

Entity collections don't bind directly to a bound control. They always bind through their EntityView<T> object (returned by the property DefaultView, see below). The EntityView<T> approach allows you to create multiple EEntityView<T> instances on a single entity collection and all bind them to different controls as if they're different sets of data.  

Creating an EntityView<T> instance

Creating an EntityView<T> object is straightforward:

var customers = new CustomerCollection();
customers.GetMulti(null); // fetch all Customers
var customerView = new EntityView<CustomerEntity>(customers);
Dim customers As New CustomerCollection()
customers.GetMulti(Nothing) ' fetch all Customers
Dim customerView As New EntityView(Of CustomerEntity)(customers)

This creates an EntityView<CustomerEntity> object on the entity collection customers, so it lets you view t the data in the entity collection 'customers'. EntityView<T> objects don't contain any data: all data you'll be able to access through an EntityView<T> is actually data residing in the related entity collection.

You can also use the entity collection's DefaultView property to create an EntityView<T>. This is similar to the DataTable's DefaultView property: every time you read the property, you'll get the same view object back. This is also true for the entity collection's DefaultView property.

The DefaultView property calls the virtual method CreateDefaultEntityView() which you can override to customize the EntityView<T> instance created for 'DefaultView'.

Instead of using the EntityView<T> class, you can use the IEntityView interface, if you for example don't know the generic type. The EntityView<T> constructor has various overloads which let you specify an initial filter and / or sort expression. You can also set the filter and / or sort expression later on as described below.

Filtering and sorting an EntityView<T>

The purpose of an EntityView<T> is to give you a 'view' based on a filter and / or a sort-expression on an in-memory entity collection. Which entities from the related entity collection is available to you through a particular EntityView<T> object depends on the filter used for the EntityView<T>.

In which order the entities are available to you is controlled by the used sort expression. As the related entity collection is not touched, you can have as many EntityView<T> objects on the same entity collection, all exposing different subsets of the data in the entity collection, in different order.

Filtering and sorting an EntityView<T> is done through normal LLBLGen Pro predicate and sort-clause classes. See for more information about predicate classes: Getting started with filtering and The predicate system.

The following example filters the aforementioned customers collection on all customers from the UK:

customerView.Filter = (CustomerFields.Country == "UK");

You could also have specified this filter with the EntityView<T> constructor. As soon as the EntityView<T>'s Filter property is set to a value, the EntityView<T> object resets itself and will apply the new filter to the related entity collection and all matching entity objects will be available through the EntityView<T> object.

The EntityView<T>'s sorter uses the same system. Let's sort our filtered EntityView<T> on 'CompanyName', ascending. For more information about sort-clauses and sortexpression objects, please see: Generated code - Sorting.

customerView.Sorter = new SortExpression(CustomerFields.CompanyName.Ascending());

Use a Predicate<T> or Lambda expression for a filter

EntityView<T> has a couple of constructors which accept a lambda expression or Predicate<T> object as a filter instead of an LLBLGen Pro predicate object. The example below filters the passed in collection of CustomerEntity instances on the Country property:

var customersFromGermany = new EntityView<CustomerEntity>(customers, c=>c.Country=="Germany"); 

Using the DelegatePredicate<T>, a developer can also use a Predicate<T> delegate or Lambda expression to filter the EntityView<T> instance after it's been created:

var customersFromGermany = new EntityView<CustomerEntity>(customers); 
customersFromGermany.Filter = new DelegatePredicate<CustomerEntity>(c=>c.Country=="Germany");

Multi-clause sorting

The EntityView<T> allows you to sort the data using a SortExpression which makes sorting on multiple fields at once possible. The following example sorts customerView on City ascending and on CompanyName descending:

var sorter = new SortExpression(CustomerFields.City.Ascending());
sorter.Add(CustomerFields.CompanyName | SortOperator.Descending);
customerView.Sorter = sorter;

If you want to sort on a property which isn't related to an entity field, you've to use the class EntityProperty instead of an entity field. So if you instead of sorting on CompanyName, want to sort on the entity property IsDirty, to get all the changed entities first, and then the non-changed entities, you've to use this code instead:

var sorter = new SortExpression(CustomerFields.City.Ascending());
sorter.Add(new EntityProperty("IsDirty").Ascending());
customerView.Sorter = sorter;

EntityProperty is usable in any construct which works with an entity field, as long as it's in-memory sorting or filtering. Below you'll learn how to filter an EntityView<T>'s data using an entity property.

Filtering using multiple predicates

To filter the customers collection on all customers from the UK which entities have been changed, use the following code below. It also illustrates the usage of EntityProperty again: it filters on a property which isn't an entity field. Keep in mind that not all predicate classes are usable for in-memory filtering: please consult the section Generated code - The predicate system which classes are usable.

var filter = new PredicateExpression(CustomerFields.Country.Equal("UK"));
filter.AddWithAnd(new EntityProperty("IsDirty").Equal(true));
customerView.Filter = filter;

View behavior on collection changes

When an entity changes in the related entity collection of the EntityView<T>, it can be the entity doesn't match the filter set for the view anymore and the EntityView<T> therefore removes the entity from itself: it's no longer available to you through the EntityView<T>.

As it might be desirable to control when and how this behavior is enforced by the EntityView<T>, it's configurable by specifying a PostCollectionChangeAction value with the EntityView<T> constructor or by setting the EntityView<T>'s DataChangeAction property. The following list describes the various values and their result on the EntityView<T>'s behavior:

  • NoAction (do nothing), i.e.: don't re-apply the filter nor the sorter.
  • ReapplyFilterAndSorter (default). Reapplies the filter and sorter on the collection.
  • ReapplySorter. Reapplies the sorter on the collection, not the filter.

By default, the EntityView<T> will re-apply the filter and sorter. There's no setting for just the filter, as re-applying the filter could alter the set of entities in the view, which could change the order of the data as in: it's no longer ordered and has to be re-sorted. If the related collection fires a reset event (when it is sorted using its own code or cleared), the view is also reset and filters are re-applied as well as sorters.

If a new entity is added to the collection through code, it is not added to the view in NoAction mode or in ReapplySorter mode, because no filter is re-applied. If it's added through data-binding, it actually is added to the view, as it is added through the EntityView2<T>, because an entity collection is bound to a bound control via an EntityView<T>, either an EntityView<T> object you created and bound directly or through the EntityView<T> object returned by the entity collection's DefaultView property.

Projecting data inside an EntityView<T> on another data-structure

A powerful feature of the EntityView<T> class is the ability to project the data in the EntityView<T> onto a new data-structure, like an entity collection, DataTable or custom classes. Projections are a way to produce custom lists of data ('dynamic lists in memory') based on the current data in the EntityView<T> and a collection of projection objects. Projection objects are small objects which specify which entity field or entity property should be used in the projection and where to get the value from.

For example, because the raw projection data can be used to re-instantiate new entities, the data can be used to produce a new entity collection with new entities. How the data is projected depends on the projection engine used for the actual projection. For more information about projections please also see: Fetching DataReaders and projections.

Projections are performed by applying a set of projection objects onto an entity and then by passing on the result data array for further storage to a projection engine, or projector, the projected data is placed in a new instance of a class, for example an entity class, but this can also be a DataRow or a custom class. The array is an array of type object. You can use filters during the projection as well, to limit the set of data you want to project from the EntityView<T> data. 

Projection objects: EntityPropertyProjector

A projection object is an instance of the EntityPropertyProjector class and it defines how to project a single entity field in an entity. An EntityPropertyProjector instance contains at most two IEntityFieldCore instances (e.g. a normal entity field objects or an EntityProperty object) and an optional Predicate, e.g. a FieldCompareValuePredicate, or a PredicateExpression.

The first IEntityFieldCore instance is mandatory. This is the default value to use for the projection. If a Predicate is specified, and it resolves to true, the default value (thus the first IEntityFieldCore) is used as value to use for the projection, otherwise the second IEntityFieldCore instance.

The EntityPropertyProjector also contains a Name property which is used to produce the name of the result field. The projection routine used is free to use this name for column purpose (e.g. during a projection onto a DataTable) but can also use it for entity field setting (projection onto an entity).

If a developer wants to execute a piece of code to manipulate the value to use for the projection, prior to storing it into the projected slot, the developer can derive his own class from EntityPropertyProjector and override ValuePostProcess(). This routine is normally empty and expects the value and the entity being processed. 

Projecting an EntityView<T>'s data is done by the CreateProjection method of an EntityView<T> object. LLBLGen Pro comes with three different projection engines: one for projecting data onto a DataTable (the class DataProjectorToDataTable), one for projecting data onto an entity collection (the class DataProjectorToEntityCollection) and one for projecting data onto a list of custom classes (the class DataProjectorToCustomClass).

You can write your own projection engine as well: implement the interface IEntityDataProjector to be able to use the engine in projections of EntityView<T> data. If you also want to use the same engine in projections of result-sets as discussed in Fetching DataReaders and projections, you also should implement the similar interface IGeneralDataProjector. Because the interfaces can re-use the actual projection engine logic, it's easy to re-use projection code for both projection mechanisms.

Only the data which is available to you through the EntityView<T> can be projected. You can't project nested data inside entities nor entity data not in the EntityView<T>. In that case, create a new EntityView<T> on the same entity collection using a different filter and project that EntityView<T> object instead.

Creating EntityPropertyProjector instances for all entity fields. Sometimes you want to project all fields of a given entity and to avoid creating a lot of EntityPropertyProjector objects if your entity has a lot of fields, you can use the shortcut method on EntityFields: EntityFields.ConvertToProjectors(EntityFieldsFactory.CreateEntityFieldsObject(EntityType.entitynameEntity))

This method will return List of IEntityPropertyProjector objects, one for each entity field of the specified entity type. The classes are used in the examples below. 

Examples of EntityView<T> projections

Projection to datatable.

var customers = new CustomerCollection();
customers.GetMulti(null); // fetch all customers
// create a view of all customers in germany
var customersInGermanyView = new EntityView<CustomerEntity>(customers,
     CustomerFields.Country.Equal("Germany"), null );
// create projection of these customers of just the city and the customerid.
// for that, define 2 propertyprojectors, one for each field to project
var propertyProjectors= new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector(CustomerFields.City, "City"));
propertyProjectors.Add( new EntityPropertyProjector(CustomerFields.CustomerId, "CustomerID"));
var projectionResults = new DataTable();
// create the actual projection.
customersInGermanyView.CreateProjection(propertyProjectors, projectionResults);

Dim customers As New CustomerCollection()
customers.GetMulti(Nothing) ' fetch all customers
' create a view of all customers in germany
Dim customersInGermanyView As New EntityView(Of CustomerEntity)( customers, _
    CustomerFields.Country.Equal("Germany"), Nothing)
' create projection of these customers of just the city and the customerid.
' for that, define 2 propertyprojectors, one for each field to project
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.City, "City"))
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID"))
Dim projectionResults As New DataTable()
' create the actual projection.
customersInGermanyView.CreateProjection( propertyProjectors, projectionResults )

After this code, the datatable projectionResults contains two columns, City and CustomerID, and it contains the data for the fields City and CustomerId of each entity in the EntityView<T>, which are all entities with Country equal to "Germany".

Projection to entity collection

The following example performs a projection onto an entity collection. Clerk is another subtype of Employee.

// fetch all managers
var managers = new ManagerCollection();
managers.GetMulti(null);
// now project them onto 2 new clerk entities, by just projecting the employee fields 
var propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add(new EntityPropertyProjector(EmployeeFields.Id, "Id"));
propertyProjectors.Add(new EntityPropertyProjector(EmployeeFields.Name, "Name"));
propertyProjectors.Add(new EntityPropertyProjector(EmployeeFields.StartDate, "StartDate"));
propertyProjectors.Add(new EntityPropertyProjector(EmployeeFields.WorksForDepartmentId, "WorksForDepartmentId"));
var clerks = new ClerkCollection();
var managersView = managers.DefaultView;
// project data to transform all managers into clerks. ;)
managersView.CreateProjection( propertyProjectors, clerks );
' fetch all managers
Dim managers As New ManagerCollection()
managers.GetMulti(Nothing)
' now project them onto 2 new clerk entities, by just projecting the employee fields 
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.Id, "Id"))
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.Name, "Name"))
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.StartDate, "StartDate"))
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.WorksForDepartmentId, "WorksForDepartmentId"))
Dim clerks As New ClerkCollection()
Dim managersView As EntityView(Of ManagerEntity) = managers.DefaultView
' project data to transform all managers into clerks. ;)
managersView.CreateProjection( propertyProjectors, clerks )

After this code, the collection clerks contains ClerkEntity instances with only the EmployeeEntity fields (inherited by ClerkEntity from its base type EmployeeEntity, which is also the base type of ManagerEntity) filled with data.

Projection to custom classes

The code below uses the class TestCustomer which is given below the projection example code (in C#). The projection also shows how to project a property of an entity which isn't an entity field, namely IsDirty, using the EntityProperty class. In .NET 3.5 or higher, you can also use Linq to Objects to achieve the same goal. EntityView<T> implements IEnumerable<T> and can be used as a sequence source in a linq query.

var customers = new CustomerCollection();
customers.GetMulti(null);
var allCustomersView = customers.DefaultView;
var customCustomers = new List<TestCustomer>();
var customClassProjector = 
    new DataProjectorToCustomClass<TestCustomer>( customCustomers );
var propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID"));
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City"));
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CompanyName, "CompanyName"));
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.Country, "Country"));
propertyProjectors.Add( new EntityPropertyProjector( new EntityProperty("IsDirty"), "IsDirty"));
// create the projection
allCustomersView.CreateProjection( propertyProjectors, customClassProjector );

//--------------------------------------
// Alternative linq to objects variant. 
var customCustomers = (from c in allCustomersView
                    select new TestCustomer 
                    { 
                        CustomerID = c.CustomerId, City = c.City,
                        CompanyName = c.CompanyName, Country = c.Country,
                        IsDirty = c.Dirty
                    }).ToList();
 ' VB.NET
Dim customers As New CustomerCollection()
customers.GetMulti(Nothing)
Dim allCustomersView As EntityView(Of CustomerEntity) = customers.DefaultView
Dim customCustomers As New List(Of TestCustomer)()
Dim customClassProjector As New DataProjectorToCustomClass(Of TestCustomer)( customCustomers )
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID"))
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.City, "City"))
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CompanyName, "CompanyName"))
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.Country, "Country"))
propertyProjectors.Add( New EntityPropertyProjector( new EntityProperty("IsDirty"), "IsDirty"))
' create the projection
allCustomersView.CreateProjection( propertyProjectors, customClassProjector )

'--------------------------------------
' Alternative linq to objects variant
var customCustomers = (From c In allCustomersView _
                    Select New TestCustomer With _
                    { 
                        .CustomerID = c.CustomerId, .City = c.City, _
                        .CompanyName = c.CompanyName, .Country = c.Country, _
                        .IsDirty = c.Dirty _
                    }).ToList()

The custom class, TestCustomer:

/// <summary>
/// Test class for projection of fetched entities onto custom classes using a custom projector.
/// </summary>
public class TestCustomer
{
    public TestCustomer()
    {
    }

    #region Class Property Declarations
    public string CustomerID { get; set;}
    public string City { get; set; }
    public string CompanyName { get; set;}
    public string Country { get; set;}
    public bool IsDirty { get; set; }
    #endregion 
}
Distinct projections.

It can be helpful to have distinct projections: no duplicate data exists in the projection results. Creating a distinct projection is simply passing false / False for allowDuplicates in the CreateProjection method.

The following example shows a couple of projection related aspects: it filters the entity view's data using a Like predicate prior to projecting data, so you can limit the data inside an EntityView<T> used for the projection, and it shows an example how a predicate is used to choose between two values in an entity to determine the end result of projecting an entity.

The example uses Northwind like most examples in this documentation. The code contains Assert statements, which are left to show you how many elements to expect at that point in the method.

var customers = new CustomerCollection();
customers.GetMulti(null);
var customersInGermanyView = 
    new EntityView( customers, CustomerFields.Country.Equal("Germany"), null);
Assert.AreEqual( 11, customersInGermanyView.Count);

// create straight forward projection of these customers of just the city and the customerid.
var propertyProjectors= new List<IEntityPropertyProjector>();
propertyProjectors.Add(new EntityPropertyProjector(CustomerFields.City, "City"));
propertyProjectors.Add(new EntityPropertyProjector(CustomerFields.CustomerId, "CustomerID"));
var projection = new DataTable();
customersInGermanyView.CreateProjection(propertyProjectors, projection );
Assert.AreEqual( 11, projection.Rows.Count);

// do distinct filtering during the following projection. It projects ContactTitle and IsNew
propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.ContactTitle, "Contact title"));
// any entity property can be used for projection source.
propertyProjectors.Add( new EntityPropertyProjector(new EntityProperty("IsNew"), "Is new"));
projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection, false);
Assert.AreEqual( 7, projection.Rows.Count );

// do distinct filtering and filter the set to project. Re-use previous property projectors. 
// 3 rows match the specified filter, distinct filtering makes it 2.
projection = new DataTable();
customersInGermanyView.CreateProjection(propertyProjectors, 
                    projection, false, CustomerFields.ContactTitle.StartsWith("Marketing"));
Assert.AreEqual( 2, projection.Rows.Count );

// use alternative projection source based on filter.
projection = new DataTable();
propertyProjectors = new List<IEntityPropertyProjector>();
// bogus data, but performs what we need: for all contacttitles not matching the filter, CustomerId is used.
propertyProjectors.Add(new EntityPropertyProjector( CustomerFields.ContactTitle, 
        "Contact title", CustomerFields.ContactTitle.StartsWith("Marketing"), CustomerFields.CustomerId));
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID"));
// create a new projection, with distinct filtering, which gives different results 
// now, because ContactTitle is now sometimes equal to CustomerId
customersInGermanyView.CreateProjection( propertyProjectors, projection, false );
Assert.AreEqual( 11, projection.Rows.Count );
foreach( DataRow row in projection.Rows )
{
    if( !row["Contact title"].ToString().StartsWith( "Marketing"))
    {
        Assert.AreEqual( row["Contact title"], row["CustomerID"] );
    }
}

Aggregates aren't supported in in-memory projections though Expressions are. All expressions are fully evaluated, where '+' operators on strings result in string concatenations. The new DbFunctionCall object to call database functions inside an Expression object is ignored during expression evaluation.