Generated code - Advanced filter usage, Adapter
Preface
This section builds upon the previous sections about filtering and shows you how to use the introduced concepts and classes in more
advanced topics and situations.
Re-using filters
A predicate (expression) object is an object that is used to construct a complete WHERE clause. However, a predicate expression
object can be re-used for filtering on, for example, other values for a particular field without recreating the predicate objects.
Say we want to filter on a given order ID in our Typed View Invoices, by using this expression:
// [C#]
PredicateExpression invoicesFilter = new PredicateExpression(InvoicesFields.OrderID > 11000);
' [VB.NET]
Dim invoicesFilter As New PredicateExpression(InvoicesFields.OrderID > 11000)
The value used to compare with is 11000. This is passed as a parameter value to the query for the parameter that is used to
compare with Invoices.OrderID. However, if a new query on Invoices has to be
executed after the query using the above filter, the predicate can be
re-used, the value just has to be altered. First we have to cast the
predicate object to the right type to alter the value later:
// [C#]
IPredicateExpression invoicesFilter = new PredicateExpression();
FieldCompareValuePredicate filterElement = (FieldCompareValuePredicate)(InvoicesFields.OrderID > 11000);
invoicesFilter.Add(filterElement);
' [VB.NET]
Dim invoicesFilter As New PredicateExpression()
Dim filterElement As New FieldCompareValuePredicate( _
InvoicesFields.OrderID, Nothing, ComparisonOperator.GreaterThan, 11000)
invoicesFilter.Add(filterElement)
We can now set the Value for this predicate to another value to compare with:
// [C#]
// ... invoiceFilter is used in code
// Re-use the filter with a value of 10000. Set the value to 10000 to be able to do that
filterElement.Value = 10000;
' [VB.NET]
' ... invoiceFilter is used in code
' Re-use the filter with a value of 10000. Set the value to 10000 to be able to do that
filterElement.Value = 10000
And the next time you specify the invoiceFilter as a filter, the value to compare the field
OrderID with will be 10000.
Negative predicates
Every predicate object can be negated, i.e. will be true when the predicate itself is
not true. This is
accomplished by specifying 'true' for
negated when using the predicate class constructors. Negated is false by default.
Each predicate type knows for itself where to place the NOT statement, so this is being taken care of by the
predicate itself. You can negate a predicate or predicate
expression by setting its
Negate property to true.
Using the native language filter construction methods, you can also negate a predicate by simply prefixing it with '!' (C#) or 'Not' (without the
quotes), as shown in the following example, which filters on employeeid not equal to 2. (note that you also could have used a '!=' (C#) or '<>' (VB.NET)
operator instead of the equation operator.)
// C#
bucket.PredicateExpression.Add(!(OrderFields.EmployeeID==2));
// QuerySpec extension methods:
.Where(Functions.Not(OrderFields.Employee==2));
' VB.NET
bucket.PredicateExpression.Add(Not (OrderFields.EmployeeID=2))
' QuerySpec extension methods:
.Where(Functions.Not(OrderFields.Employee=2))
Filtering on entity type
In the situation where you want to fetch entities of a particular type (and all subtypes of that particular type), if the entity type to fetch is
in an inheritance hierarchy, it can be cumbersome
to formulate the exact filter, after all the predicate classes don't supply you with a proper filter to filter on a type.
To filter on a particular type, use the following general mechanism. Say you want to limit a fetch to only
BoardMember entities, which is a subtype
of
Manager, which is a subtype of
Employee. The given filter is build in the object
filter in the following example:
// C#
// add a filter which filters on boardmembers
filter.PredicateExpression.Add(BoardMemberEntity.GetEntityTypeFilter());
'VB.NET
' add a filter which filters on boardmembers
filter.PredicateExpression.Add(BoardMemberEntity.GetEntityTypeFilter())
The method
GetEntityTypeFilter(), which is available in all entities which are part of an inheritance hierarchy, produces an IPredicateExpression
object which filters on the entity type you call the method on, so in our example on BoardMember.
All subtypes of the type you're filtering on will also match the filter, as they're also of the type you're filtering on. (BoardMember is-a Manager is-a Employee).
Note: |
For filtering entity instances on type in memory, the DelegatePredicate is recommended with a delegate which filters using .NET native type filtering using Type.IsAssignableFrom(). |
Multi-entity filters
Sometimes you may want to filter on values in related entities. This is achieved by creating a
RelationPredicateBucket and adding the required Relations.
The
RelationPredicateBucket is also used to hold additional filters; these filters are added to the
PredicateExpression property of the
RelationPredicateBucket.
For example, suppose you wanted to retrieve all customers who bought a product from any supplier in France. This
requires a filter on
Country, but
Country is not
part of the
Customer entity, it is part of the
Supplier entity. Here's how to achieve this in code:
// [C#]
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>();
RelationPredicateBucket bucket = new RelationPredicateBucket();
bucket.Relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerID);
bucket.Relations.Add(OrderEntity.Relations.OrderDetailsEntityUsingOrderID);
bucket.Relations.Add(OrderDetailsEntity.Relations.ProductEntityUsingProductID);
bucket.Relations.Add(ProductEntity.Relations.SupplierEntityUsingSupplierID);
bucket.PredicateExpression.Add(SupplierFields.Country == "France");
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntityCollection(customers, bucket);
}
// QuerySpec alternative
var qf = new QueryFactory();
var q = qf.Customer
.From(QueryTarget
.InnerJoin(CustomerEntity.Relations.OrderEntityUsingCustomerId)
.InnerJoin(OrderEntity.Relations.OrderDetailEntityUsingOrderId)
.InnerJoin(OrderDetailEntity.Relations.ProductEntityUsingProductId)
.InnerJoin(ProductEntity.Relations.SupplierEntityUsingSupplierId))
.Where(SupplierFields.Country == "France");
var customers = new EntityCollection<CustomerEntity>();
using(var adapter = new DataAccessAdapter())
{
adapter.FetchQuery(q, customers);
}
' [VB.NET]
Dim customers As New EntityCollection(Of CustomerEntity)()
Dim bucket As New RelationPredicateBucket()
bucket.Relations.Add(CustomerEntity.Relations.OrderEntityUsingCustomerID)
bucket.Relations.Add(OrderEntity.Relations.OrderDetailsEntityUsingOrderID)
bucket.Relations.Add(OrderDetailsEntity.Relations.ProductEntityUsingProductID)
bucket.Relations.Add(ProductEntity.Relations.SupplierEntityUsingSupplierID)
bucket.PredicateExpression.Add(SupplierFields.Country = "France")
Using adapter As New DataAccessAdapter()
adapter.FetchEntityCollection(customers, bucket)
End Using
' QuerySpec alternative
Dim qf As New QueryFactory()
Dim q = qf.Customer _
.From(QueryTarget _
.InnerJoin(CustomerEntity.Relations.OrderEntityUsingCustomerId) _
.InnerJoin(OrderEntity.Relations.OrderDetailEntityUsingOrderId) _
.InnerJoin(OrderDetailEntity.Relations.ProductEntityUsingProductId) _
.InnerJoin(ProductEntity.Relations.SupplierEntityUsingSupplierId)) _
.Where(SupplierFields.Country == "France")
Dim customers As new EntityCollection(Of CustomerEntity)()
Using adapter As New DataAccessAdapter()
adapter.FetchQuery(q, customers)
End Using
First, the EntityCollection object is created which will contain the
entities that are retrieved from the persistent storage. Next, a new
RelationPredicateBucket
is created to hold the
RelationCollection and the
PredicateExpression
objects
we want to use in our filter. We now add each relation in the correct order. Start with the target entity, in this case
Customer, and work your way down
to the entity you want to filter on. In this case the
SupplierEntity. Each entity, on both sides of 'Relations' is included in the complete scope of the query,
thus
ProductEntity.Relations.SupplierEntityUsingSupplierId will include both Product and Supplier and thus you can filter on fields in either or both of these
entities.
After the relations are added to the
RelationCollection property of our bucket we add the search filter to the
PredicateExpression property of our bucket.
We do this by adding one predicate, a
FieldCompareValuePredicate, which compares Supplier.Country with the value "France". Now all the objects are ready to
be used and are passed as parameters to the
FetchEntityCollection method of the created
DataAccessAdapter object. This will retrieve all
Customer objects
meeting the requirements of the filter we just defined.
There is no limit to the number of relations you can add to the RelationCollection of our bucket, however keep in mind that each added
relation will result in an INNER JOIN statement, unless a
JoinHint
has been specified using a different overload of
Add().
In the example, all entities in the relations are added once. If you want to filter on an entity twice,
or if you use an entity twice in two, different relations, you have to specify aliasses for the entities in the relations. See
Advanced filtering below for more
information.
Custom filters for EntityRelations
In the section above, Multi-entity filters, it was described how relations
could be specified to construct a JOIN path. The JOIN clauses themselves are
determined from the relation objects, thus FK-PK compares which result in
the ON clause. Sometimes it is important to specify additional predicates in
this ON clause. You can do this by specifying a
PredicateExpression instance for the
CustomFilter property of the
EntityRelation you add to a
RelationCollection.
In the example below we add a custom predicate to the
EntityRelation object of the relation Customer-Order and which filters on Order.ShipCountry="Mexico".
It uses the example of Multi-entity filters.
// C#
IPredicateExpression customFilter = new PredicateExpression();
customFilter.Add(OrderFields.ShipCountry == "Mexico"));
// ...
relationsToUse.Add(CustomerEntity.Relations.OrderEntityUsingCustomerID).CustomFilter = customFilter;
// ...
// QuerySpec alternative (qf is QueryFactory instance). It lets you define the ON clause in full.
...
.From(qf.Customer.InnerJoin(qf.Order)
.On((CustomerFields.CustomerId==OrderFields.CustomerId)
.And(OrderFields.ShipCountry=="Mexico")));
' VB.NET
Dim customFilter As IPredicateExpression = New PredicateExpression()
customFilter.Add(OrderFields.ShipCountry = "Mexico")
' ...
relationsToUse.Add(CustomerEntity.Relations.OrderEntityUsingCustomerID).CustomFilter = customFilter
' ...
' QuerySpec alternative (qf is QueryFactory instance). It lets you define the ON clause in full.
...
.From(qf.Customer.InnerJoin(qf.Order) _
.On((CustomerFields.CustomerId=OrderFields.CustomerId) _
.And(OrderFields.ShipCountry="Mexico")))
Please pay special attention to the flag
EntityRelation.
CustomFilterReplacesOnClause. If this flag is set to true, it
will make the join construction logic to use the value of
CustomFilter
as the ON clause instead of appending it with AND to the field relation clause.
Advanced filtering
The predicates and relations discussed up till now can do a fair amount of filtering for you. However sometimes you need more advanced filtering, for example
when an entity has to be joined multiple times to the join list, using aliases and you want to filter with one predicate on the fields of one alias and
in another predicate on another alias. An example would be: get all Customers who have a visiting address in Amsterdam and a billing address in Rotterdam.
Customer has two relations with Address: Customer.VisitingAddressID - Address.AddressID and Customer.BillingAddressID - Address.AddressID. Simply adding
the relation CustomerEntity.Relations.AddressUsingVisitingAddressID to the RelationCollection will work, but when you add the relation
CustomerEntity.Relations.AddressUsingBillingAddressID, you have two times the Address entity in the join list, how are you going to target one of them in a
predicate?
The solution is to alias the entities in the relation added to the
RelationCollection, and also to use the same alias in a predicate. If you omit an alias, it is
considered not aliased and if you have aliased an entity in an earlier added relation to the same
RelationCollection, it will be considered a different entity in the
join list. So aliassing Customer to "C" in the first relation and in the
second relation you do not specify an alias for Customer, you'll get 2 times
a Customer entity in the join list.
Our example of Customer and the two Address entities with the two City predicates will result in the following code. Notice the alias usage in the predicates as well.
// C#
RelationPredicateBucket bucket = new RelationPredicateBucket();
bucket.Relations.Add(CustomerEntity.Relations.AddressEntityUsingVisitingAddressID, "VisitingAddress");
bucket.Relations.Add(CustomerEntity.Relations.AddressEntityUsingBillingAddressID, "BillingAddress");
bucket.PredicateExpression.Add((AddressFields.City.SetObjectAlias("VisitingAddress")=="Amsterdam") &
(AddressFields.City.SetObjectAlias("BillingAddress")=="Rotterdam"));
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>();
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntityCollection(customers, bucket);
}
' VB.NET
Dim bucket As New RelationPredicateBucket()
bucket.Relations.Add(CustomerEntity.Relations.AddressEntityUsingVisitingAddressID, "VisitingAddress")
bucket.Relations.Add(CustomerEntity.Relations.AddressEntityUsingBillingAddressID, "BillingAddress")
bucket.PredicateExpression.Add( _
((AddressFields.City.SetObjectAlias("VisitingAddress")="Amsterdam") And
(AddressFields.City.SetObjectAlias("BillingAddress")="Rotterdam"))
Dim customers As New EntityCollection(Of CustomerEntity)()
Using adapter As New DataAccessAdapter()
adapter.FetchEntityCollection(customers, bucket)
End Using
Note:
|
The aliases specified have to be valid aliases in SQL |