Generated code - Using the entity classes, SelfServicing
Preface
When you generate code and you opt for the TwoClasses preset in combination of the SelfServicing template group
you'll notice that there are actually two classes (hence the two class scenario) per entity which are used in a 'base class' - 'derived class' way. When you
use the General preset, there is just one class,
EntityNameEntity.cs/vb, which contains the same functionality as
the
EntityNameEntityBase.cs/vb class generated in the TwoClasses situation). This section describes the TwoClasses situation
and how to use the base class and derived class for every entity in your code, though the topics addressed can easily be applied to the
generated code produced when using the General preset as well.
All entity classes derive from a central, generated base class called
CommonEntityBase. This class is the base class for all generated entity classes and it derives from the class
EntityBase, which is located in the ORMSupportClasses assembly. The
CommonEntityBase class is usable to add code (via a partial class or using the user code regions) to all generated entities without having to generate / add that code to all entity classes separately.
The section below is the same for entities in an inheritance hierarchy as for entities not in an inheritance hierarchy, unless stated otherwise.
Two classes
The base class, generally named
EntityNameEntityBase.cs/vb, for example OrderEntityBase.cs, is the class containing all
the logic and the implementations of various methods defined in the
EntityBase class in the ORMSupportClasses namespace. This base class derives from the central generated base class
CommonEntityBase. The other class,
EntityNameEntity.cs/vb, for example
OrderEntity.cs, is the class
you work with in your code; in other words, the class you use as the type to instantiate entity objects. It's recommended to use
partial classes.
You can create a partial class for either one of the generated entity classes. It's however recommended that if you're starting a new project,
you use the
General preset and partial classes as that will likely give you less generated code.
Adding your own code to the generated classes
for details on modifying the generated code.
Instantiating an existing entity
To load the
entity's data from the persistent storage, we use the generated class related to this entity's definition, create
an instance of that class and order it to load the data of the particular entity. As an example we're loading the entity identified
with the customerID "CHOPS" into an object.
Using the primary key value
One way to instantiate the entity in an object is by passing all primary key values to the constructor of the entity class to use:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
This will load the entity with the primary key value of "CHOPS" into the object named
customer,
directly from the persistent storage.
Another, less compact way is to use an empty entity object and to fetch it by calling its fetch method:
// [C#]
CustomerEntity customer = new CustomerEntity();
customer.FetchUsingPK("CHOPS");
' [VB.NET]
Dim customer As New CustomerEntity()
customer.FetchUsingPK("CHOPS")
Using a related entity
Another way to instantiate this same entity is via a related entity using
Lazy Loading. Let's load the order with ID 10254, which is an order of
customer "CHOPS", and via that order, get an instance of the entity "CHOPS".
// [C#]
OrderEntity order = new OrderEntity(10254); // fetches the order
CustomerEntity customer = order.Customer; // fetches the customer, through lazy loading
' [VB.NET]
Dim order As New OrderEntity(10254) ' fetches the order
Dim customer As CustomerEntity = order.Customer 'fetches the customer, through lazy loading
LLBLGen Pro automatically creates properties to retrieve related entities or collections of related entities, using an instance of a
given entity. It doesn't matter what type the relation between the two entities has: 1:n, m:1, 1:1 or m:n. In this case, Customer and
Order have an 1:n relationship (one customer can have multiple orders) from the Customer's point of view and a m:1 relationship (one
Order can have just one customer) from the Order's point of view.
A single entity property, which order.Customer is, uses the method GetSingle
FieldMappedOnRelation() to actually retrieve the entity.
You can use that method too, instead of the property:
// [C#]
OrderEntity order = new OrderEntity(10254);
CustomerEntity customer = order.GetSingleCustomer();
' [VB.NET]
Dim order As New OrderEntity(10254)
Dim customer As CustomerEntity = order.GetSingleCustomer()
If Customer is in an inheritance hierarchy, the fetch is polymorphic. This means that if the order entity, in this case order 10254, has a reference to
a derived type of Customer, for example
GoldCustomer, the entity returned will be of type
GoldCustomer. See also
Polymorphic fetches below.
Load on demand/Lazy loading
Once loaded, the entity is not loaded again, if you access the property again. This is called
load on demand or
lazy loading: the load action
of the related entity (in our example 'customer') is done when you ask for it, not when the referencing entity (in our example 'order') is loaded.
You can set a flag which makes the code load the related entity each time you access the property:
entity.AlwaysFetchFieldMappedOnRelation.
In our example of Order and Customer,
OrderEntity has a property called
AlwaysFetchCustomer and
CustomerEntity has a property called
AlwaysFetchOrders.
Default for these properties is 'false'. Setting these properties to true, will assure that the related entity is reloaded from the database
each time you access the property. This can be handy if you want to stay up to date with the related entity state in
the database. It can degrade performance, so use the property with care.
Another way to force loading of a related entity or collection is by specifying true for
the forceFetch parameter in the GetSingle
FieldMappedOnRelation call, or when the property contains a collection, GetMulti
FieldMappedOnRelation
call. Forcing a fetch has a difference with 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.
If you use a
prefetch path to read a
Customer and its related
Order entities from the database, the Orders will
not be re-loaded if you access the property after the fetch. A prefetch path which loads related entities makes sure that lazy loading will not undo
the work the prefetch path already performed.
When the related entity is not found in the database, for example
Customer
has an optional relation with
Address using
Customer.VisitingAddressID -
Address.AddressID and
myCustomer.VisitingAddress is accessed and
myCustomer doesn't have a related visiting address
entity, by default the generated code will return a new, empty entity, in this case a new
AddressEntity instance. You can then test the Fields.State
value of the returned entity, if it is a new entity or a fetched
entity (by comparing the Fields.State property with EntityState.New for a new entity or EntityState.Fetched for a fetched entity).
You can tell the entity to return null (C#) or Nothing (VB.NET) instead of a new entity if the entity
is not found by setting the property
FieldMappedOnRelationReturnNewIfNotFound to false. In our example of the
Customer and its optional VisitingAddress field, mapped on the relation Customer.VisitingAddressID - Address.AddressID, Customer will have a property
VisitingAddressReturnNewIfNotFound. Setting this property to false will make
myCustomer.VisitingAddress return null (C#) or Nothing (VB.NET) if the related Address entity is not found for myCustomer.
By default these flags are
set to true, to avoid code breakage with existing code already in production. You can change this default in the LLBLGen Pro designer: in the project
settings, change the project setting, in the
LLBLGen Pro Runtime
Framework subsection
, LazyLoadingWithoutResultReturnsNew to
false and re-generate your code. The code generator will now generate 'false' / False' for all
FieldMappedOnRelationReturnNewIfNotFound flags
in all entities which will make sure that if an entity doesn't exist, null / Nothing is returned instead of a new entity.
Note: |
Be aware that some code can trigger lazy loading while you didn't intent to. Consider Customer and Order which have an 1:n relation (and Order and Customer have a m:1 relation). The following code triggers the fetch of all orders for the myCustomer instance, while that wasn't the intention:
myCustomer.Orders.Add(myOrder);
while this code:
myOrder.Customer = myCustomer;
does the same thing, as LLBLGen Pro keeps both sides of a relation in sync, however this line of code doesn't trigger lazy loading. |
Using a unique constraint's value
The
Customer entity in our previous example also has a unique
constraint defined on its field
CompanyName. We can use that field to
load the same entity. Fetching the
entity using a unique constraint is done via two steps: first create an empty entity object, then fetch the entity data using a
method call. Because an entity can have more than one unique constraint, these have the fields in the unique constraint in the methodnames.
In the case of our example, the entity
Customer has a unique constraint with one field, CompanyName, which is utilized by method
FetchUsingUCCompanyName(companyName):
// [C#]
CustomerEntity customer = new CustomerEntity();
customer.FetchUsingUCCompanyName("Chop-suey Chinese");
' [VB.NET]
Dim customer As New CustomerEntity()
customer.FetchUsingUCCompanyName("Chop-suey Chinese")
Using a prefetch path
An easy way to instantiate an entity 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.
Using a collection class
Another way to instantiate an entity is by creating a collection class with one or more entities of the same entity definition (entity type,
like Customer) using the EntityCollection classes or via a related entity which has a 1:n relation with the entity to instantiate. For an example,
please see
Tutorials and Examples: How Do I? - Read all entities into a collection.
Using a Context object
If you want to get a reference to an entity object already in memory, you can use a
Context object, if that
object was added to that particular Context object. The example below retrieves a reference to the customer object with PK "CHOPS", if that entity
was previously loaded into an entity object which was added to that Context object. If the entity object isn't in the Context object, a new entity
object is returned. An example usage is shown below.
// C#
CustomerEntity customer = (CustomerEntity)myContext.Get(new CustomerEntityFactory(), "CHOPS");
if(customer.IsNew)
{
// not found in context, fetch from database
customer.Refetch();
}
' VB.NET
Dim customer As CustomerEntity = CType(myContext.Get(New CustomerEntityFactory(), "CHOPS"), CustomerEntity)
If customer.IsNew Then
' not found in context, fetch from database
customer.Refetch()
End If
Creating a new entity instance
This section discusses how to create a new entity and save it to the database and how to modify an existing entity and persist the changes.
To create a new entity, simply instantiate an empty
entity object, in this case a new Customer:
// [C#]
CustomerEntity customer = new CustomerEntity();
' [VB.NET]
Dim customer As New CustomerEntity()
To create the entity in the persistent storage, two things have to be done: 1) the entity's data (which is new) has to be stored in the
new entity object and 2) the entity data has to be persisted / saved in the persistent storage. Let's add the customer Foo Inc. to the
database, do the following:
// [C#]
customer.CustomerID = "FOO";
customer.Address = "1, Bar drive";
customer.City = "Silicon Valey";
customer.CompanyName = "Foo Inc.";
customer.ContactName = "John Coder";
customer.ContactTitle = "Owner";
customer.Country = "USA";
customer.Fax = "(604)555-1233";
customer.Phone = "(604)555-1234";
customer.PostalCode = "90211";
// save it
customer.Save();
' [VB.NET]
customer.CustomerID = "FOO"
customer.Address = "1, Bar drive"
customer.City = "Silicon Valey"
customer.CompanyName = "Foo Inc."
customer.ContactName = "John Coder"
customer.ContactTitle = "Owner"
customer.Country = "USA"
customer.Fax = "(604)555-1233"
customer.Phone = "(604)555-1234"
customer.PostalCode = "90211"
' save it
customer.Save()
Customer has another field,
Region, which isn't given a value.
Region can be NULL and will end up as NULL in the database as it has no
value in the entity saved.
This will save the data directly to the
persistent storage (database). The entity class instance
customer itself is marked 'out of sync', which means that the entity's data is
refetched
from the database when you try to read one of the object's field's value.
This way, you can immediately refetch values which are set inside the
database, e.g. default values for columns. The code is aware of
sequences / identity columns and will automatically set the value for an
identity / sequence column right after the
Save() method returns, thus is
available in the next statement after a call to
Save().
Because the entity saved is new (customer.IsNew is true),
Save() will use an INSERT query. After a successful save, the IsNew flag is set to false and the
State property of the Fields object of the saved entity is set to
EntityState.Fetched (if the entity is also refetched) or
EntityState.OutOfSync.
Note:
|
Fields which get their values from a trigger, from newid() or a
default constraint calling a user defined function are not
considered sequenced fields and these values will not be read back,
so you'll have to supply a value for these fields prior to saving
the entity. This isn't true for fields which are of type
unique_identifier on SqlServer 2005 when the DQE is set in
SqlServer2005/SqlServer2012 compatibility levels and the
field has in the database a default value of NEWSEQUENTIALID(). See:
Generated code - Database specific features
|
Note:
|
If the entity is in a hierarchy of type TargetPerEntityHierarchy you don't have to set the discriminator value for the entity type,
this is done for you automatically: just create a new instance of the entity type you want to use, and the discriminator value is automatically set and
will be saved with the entity.
|
Modifying an existing entity
Modifying an entity's data (the
entity instance) can be done in
multiple ways:
- Loading an existing entity in memory, alter one or more fields (not sequenced fields) and call
Save()
- Create a new entity, set the primary key values, set the IsNew to false, set one or more other fields' values and call
Save().
This will not alter the PK fields.
- Via one of the UpdateMulti*() methods defined in the collection class of the entity.
Option 1: Modifying an entity instance by altering entity class instance
properties.
The Save() method will see that the entity isn't new, and will use an UPDATE
query to alter the entity's data in the persistent storage and an INSERT
query to insert a new instance into the persistent storage. An UPDATE query
will only update the
changed fields in an entity that is saved. If no
fields are changed, no update is performed. If you've loaded an entity from
the database into memory and you've changed one or more of its primary key
fields, these fields will be updated in the database as well (except
sequenced columns). Changing PK fields is not recommended and changed PK
fields are not propagated to related entities fetched in memory.
An example for code using the first method:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS"); // fetches the entity
customer.Phone = "(605)555-4321";
customer.Save();
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS") ' fetches the entity
customer.Phone = "(605)555-4321"
customer.Save()
This will first load the Customer entity "CHOPS" into memory, alter one
field,
Phone, and then save the entity instance back into the
persistent storage. Because the loading of "CHOPS" already set the primary
key, we can just alter a field and call SaveEntity() . The Update query will
solely set the table field related to the entity field "Phone" to the new
value.
Option 2: Update an entity directly in the persistent storage
Reading an entity into memory first can be somewhat inefficient, if all we
need to do is an update of an entity row in the database. The following
procedure is more efficient in that it results in just an UPDATE query,
without first reading the entity data from the database. The following code
performs the same update as the previous example code illustrating option 1.
Because it doesn't need to read an entity first, we won't pass
true
to keep the connection open. Even though the PK field is changed, it is not
updated, because it is not previously fetched from the database.
// [C#]
CustomerEntity customer = new CustomerEntity();
customer.CustomerID="CHOPS";
customer.IsNew=false;
customer.Phone = "(605)555-4321";
customer.Save();
' [VB.NET]
Dim customer As New CustomerEntity()
customer.CustomerID = "CHOPS"
customer.IsNew = False
customer.Phone = "(605)555-4321"
customer.Save()
We have to set the primary key field, so the Update method will only update a single entity, the "CHOPS" entity.
Next, we have to mark the new, empty entity object as not being new, so
Save() will call the Update method, instead of the Insert method.
This is done by setting the flag IsNew to false. Next is the altering of a field, in this case "Phone", and the call of
Save(). This will not load the entity
back in memory, but because
Save() is called, it will be marked out of sync, and the next time you'll access a property of this entity's object,
it will be refetched from the persistent storage. Doing updates this way can be very efficient and
you can use very complex update constructs when you apply an Expression to the field(s) to update. See for more information about Expression objects for
fields
Field expressions and aggregates.
Notes:
|
- This same mechanism will work for fast deletes.
- Setting a field to the same value it already has will not set the field to a value (and will not mark the field as 'changed') unless the entity
is new.
- Each entity which is saved is validated prior to the save action. This validation can be a no-op, if no validation code has been added by the
developer, either through code added to the entity, or through a validator class.
See Validation per field or per entity for more information about LLBLGen Pro's validation functionality.
- (SQLServer specific) If the entity is saved into a table which is part of an indexed view, SqlServer requires that SET ARITHABORT ON is
specified prior to the actual save action. You can tell LLBLGen Pro to set that option, by calling the global method CommonDaoBase.SetArithAbortFlag(bool)
method. After each SQL statement a SET ARITHABORT OFF statement will be executed if the ArithAbort flag is set to true. Setting this flag affects
the whole application.
|
Option 3: Using entitycollection.UpdateMulti()
Entity collection classes allow you to manipulate an entity or group of
entities directly in the database without first fetching them into memory.
Below we're setting all 'Discontinued' fields of all product entities to
false if the CategoryId of the product is equal to 3.
UpdateMulti() (as well as its method for deletes
DeleteMulti which deletes entities directly from the
persistent storage) returns the number of entities affected by the call.
// [C#]
IPredicate filter = ProductFields.CategoryId == 3;
ProductEntity updateValuesProduct = new ProductEntity();
updateValuesProduct.Discontinued=true;
ProductCollection products = new ProductCollection();
products.UpdateMulti(updateValuesProduct, filter);
' VB.NET
Dim filter As IPredicate = ProductFields.CategoryId = 3
Dim updateValuesProduct As New ProductEntity()
updateValuesProduct.Discontinued=True
Dim products As New ProductCollection()
products.UpdateMulti(updateValuesProduct, filter)
Setting the EntityState to Fetched automatically after a save
By design an entity which was successfully saved to the database gets as
EntityState
OutOfSync. If you've specified to refetch the entity
again after the save, the entity is then refetched with an additional fetch
statement. This is done to make sure that default constraints, calculated
fields and elements which could have been changed after the save action
inside the database (for example because a database trigger ran after the
save action) are reflected in the entity after the save action. If you know
that this won't happen in your application, you can get a performance gain
by specifying that LLBLGen Pro should mark a successfully saved entity as
Fetched instead of OutOfSync. In this situation, LLBLGen Pro won't
perform a fetch action to obtain the new entity values from the database.
To use this feature, you've to set the static/Shared property
EntityBase.
MarkSavedEntitiesAsFetched to true (default is false).
This will be used for all entities in your application, so if you have some
entities which have to be fetched after the update (for example because they
have a timestamp field), you should keep the default, false. You can also
set this value using the config file of your application by adding the
following line to the
appSettings section of your application's
config file:
<add key="markSavedEntitiesAsFetched" value="true"/>
You don't need to refetch an entity if it has a sequenced primary key
(Identity or sequence), as these values are read back directly with the
insert statement.
Saving entities recursively
All entity objects and entity collection objects in SelfServicing support recursive saves. This means that if you have an entity object, say a
CustomerEntity, and its collection objects, e.g. customer.Orders, contain changed entities, or the entity references changed entities, these
entities will be saved as well when the particular entity is saved.
In SelfServicing, this logic is
not enabled by default, to be backwards
compatible. You have to call the
Save() (entities) or
entitycollection.SaveMulti() (entity collections) overloads which accept a boolean parameter to signal the routine to save all entities recursively or not. Pass
true
to the Save / SaveMulti call and the whole object graph is saved, that is: all entities reachable from the object the Save (or SaveMulti) method is
called on which are changed ('dirty').
All recursive save actions are performed inside a transaction. If the saved entity (the entity the
Save() method is called on) or the saved
entity collection is not participating in a transaction, a new transaction is created (ADO.NET transaction). If there is already a
transaction available, it is assumed all entities to save participate already in this transaction or can participate in this transaction
(i.e. are not participating in another transaction). If an error occurs during the recursive save, the current transaction is aborted and the
transaction is rolled back.
The logic automatically determines the order in which actions need to take place so foreign key violations do not occur.
For example:
- Instantiate a Customer entity, add a new Order object to its Orders collection. Now add OrderDetails objects to the new Order object's OrderDetails
collection,. You can simply save the Customer entity and all included new/'dirty' entities will be saved and any PK-FK relations will be
updated/synchronized automatically.
- Alter the Customer object in the example above, and save the Order object. The Customer object is saved first, then the Order and then the
OrderDetails objects with all PK-FK values being synchronized
FK-PK synchronization
Foreign key synchronization with a related Primary key field is done
automatically in code.
For example:
- Instantiate a Customer entity, add a new Order class
instance to its Orders collection. Now add OrderDetails class instances
to the new Order's OrderDetails collection. You can simply
save the Customer entity and all included new/'dirty' entities will be
saved and any PK-FK relations will be updated/synchronized
automatically.
- Alter the Customer object in the example above, and save the Order
object. The Customer object is saved first, then the Order and then the
OrderDetails objects with all PK-FK values being synced
This synchronization of FK-PK values is already done at the moment you set a
property to a reference of an entity object, for example myOrder.Customer =
myCustomer, if the entity (in this case myCustomer) is not new, or if the PK
field(s) aren't sequenced fields when the entity is new. Synchronization is
also performed after a save action, so identity/sequenced columns are also
synchronized.
If you set a foreign key field (for example
Order.CustomerID)
to a new value, the referenced entity by the foreign key (relation) the
field is part of will be dereferenced and the field mapped onto that
relation is set to null (C#) or Nothing (VB.NET). Example:
// C#
OrderEntity myOrder = new OrderEntity();
CustomerEntity myCustomer = new CustomerEntity("CHOPS");
myOrder.Customer = myCustomer; // A
myOrder.CustomerID = "BLONP"; // B
CustomerEntity referencedCustomer = myOrder.Customer; // C
' VB.NET
Dim myOrder As New OrderEntity()
Dim myCustomer As New CustomerEntity("CHOPS")
myOrder.Customer = myCustomer ' A
myOrder.CustomerID = "BLONP" ' B
Dim referencedCustomer As CustomerEntity = myOrder.Customer 'C
After line 'A', myOrder.CustomerID will be set to "CHOPS", because of the
synchronization between the PK of myCustomer and the FK of myOrder. At line
'B', the foreign key field CustomerID of myOrder is changed to a new value,
"BLONP". Because the FK field changes, the referenced entity through that FK
field, myCustomer, is dereferenced and myOrder.Customer will return
null/Nothing. Because there is no current referenced customer entity, the
variable
referencedCustomer will be set to null / Nothing at line
'C'.
The opposite is also true: if you set the property which
represents a related entity to null (Nothing), the FK field(s) forming this
relation will be set to null as well, as shown in the following example:
// C#
OrderEntity myOrder = new OrderEntity(10254);
CustomerEntity myCustomer = myOrder.Customer; // A
myOrder.Customer = null; // B
' VB.NET
Dim myOrder As New OrderEntity(10254)
Dim myCustomer As CustomerEntity = myOrder.Customer ' A
myOrder.Customer = Nothing 'B
At line A, lazy loading will fetch the customer related to order 10254. At line B, this customer is de-referenced. This means that the FK field of
order creating this relation, myOrder.CustomerId, will be set to null (Nothing). So if myOrder is saved after this, NULL will be saved in the field
Order.CustomerId
Deleting an entity
Deleting an entity is very easy, it's as simple as Saving an entity. Simply fetch the entity into memory and call
Delete(). You can also delete
an entity using an entity collection or remove it from the persistent storage directly (both methods use DeleteMulti* overloads, see
Deleting one or more entities from the persistent storage)
To delete it the simple way: fetch it and call delete:
// [C#]
CustomerEntity customer = new CustomerEntity("CHOPS");
customer.Delete();
' [VB.NET]
Dim customer As New CustomerEntity("CHOPS")
customer.Delete()
It's recommended to add the entity object to a transaction object if you want to be able to roll back the delete later on in your routine. See for more information
about transactions the section about
Transactions.
Polymorphic fetches
Already mentioned early in this section is the phenomenon called
'Polymorphic fetches'. Imagine the following entity setup: BoardMember
entity has a relation (m:1) with CompanyCar. CompanyCar is the root of a
TargetPerEntityHierarchy inheritance hierarchy and has two subtypes:
FamilyCar and SportsCar. Because BoardMember has the relation with
CompanyCar, a field called 'CompanyCar' is created in the BoardMember entity
which is mapped onto the m:1 relation BoardMember - CompanyCar.
In the database, several BoardMember instances have been stored, as well as several different CompanyCar instances, of type FamilyCar or SportsCar.
Using lazy loading, you can load the related CompanyCar instance of a given BoardMember's instance by simply calling the 'CompanyCar' property:
// C#
CompanyCarEntity car = myBoardMember.CompanyCar;
' VB.NET
Dim car As CompanyCarEntity = myBoardMember.CompanyCar
However, 'car' in the example above, can be of a different type. If for example the
BoardMember instance in
myBoardMember has a
FamilyCar as company car
set, 'car' is of type
FamilyCar. Because the fetch action can result in multiple types, the fetch is called
polymorphic. So, in our example, if 'car' is
of type
FamilyCar, the following code would also be correct:
// C#
FamilyCarEntity car = (FamilyCarEntity)myBoardMember.CompanyCar;
' VB.NET
Dim car As FamilyCarEntity = CType(myBoardMember.CompanyCar, FamilyCarEntity)
Would this BoardMember instance have a SportsCar set as company car, this code would fail at runtime with a specified cast not valid exception.
FetchPolymorphic
Each entity which is in an inheritance hierarchy has a method called
FetchPolymorphic, which is a static/Shared method. This method lets you
fetch an entity which is a subtype of the entity you call the method on.
E.g. if CompanyCar with ID '4' is a FamilyCar, you can do the
following to fetch the entity into a FamilyCar instance:
// C#
FamilyCarEntity car = (FamilyCarEntity)CompanyCarEntity.FetchPolymorphic(null, 4, null);
' VB.NET
Dim car As FamilyCarEntity = CType(CompanyCarEntity.FetchPolymorphic(Nothing, 4, Nothing), FamilyCarEntity)
As this method accepts a transaction, it can be handy in some cases to use this method over a constructor call. To keep things simple, you should first
look at the constructor method:
// C#
FamilyCarEntity car = new FamilyCarEntity(4);
' VB.NET
Dim car As New FamilyCarEntity(4)
FetchPolymorphicUsingUC...
Another way to fetch an entity polymorphically is when it has a unique constraint and is in a hierarchy. You then can use the unique constraint's values to
fetch an entity polymorphically similar to the
FetchPolymorphic method for fetching an entity using the primary key. Say Employee has an unique constraint
on 'Name'. To fetch an employee polymorphically, you can use the following code.
// C#
BoardMemberEntity b = (BoardMemberEntity)EmployeeEntity.FetchPolymorphicUsingUCName(null, "J.D. Rockefeller III", null);
' VB.NET
Dim b As BoardMemberEntity = CType(EmployeeEntity.FetchPolymorphicUsingUCName(Nothing, "J.D. Rockefeller III", Nothing), BoardMemberEntity)
Note:
|
Be aware of the fact that polymorphic fetches of entities in a
TargetPerEntity hierarchy use JOINs between the root entity's target
and all subtype targets when the root type is specified for the
fetch. This can have an inpact on performance. |
Concurrency control
There is an overloaded
Save() variant which takes a
predicate object, also known as a filter. This is also the case for
Delete().
This filter is constructed using the objects described in
Getting started with filtering and can be used to set
a condition when the update (or delete, when the filter is used as a parameter to
Delete()) has to take place (the filter is ignored when the entity is new).
For example, a predicate object that contains a field = value compare clause for a timestamp column in the table where the entity-to-update is located.
If the entity's timestamp column is not the same (if you have defined that in your predicate object passed to
Save()), the save is not performed, or
in the case of calling Delete() with a predicate, the delete will not take place.
To filter on the original database values fetched into the entity to be
saved, you can create for example
FieldCompareValuePredicate
instances which use the
EntityField's
DbValue property. Even
though a field is changed in memory through code, the
DbValue
property of a field will have the original value read from the database. You
can use this for optimistic concurrency schemes. See for an example below.
If the field is NULL in the database,
DbValue is null (C#) or Nothing
(VB.NET).
LLBLGen Pro supports another form of supplying predicates for filters during Save or Delete actions: implementing
IConcurrencyPredicateFactory.
You can implement this interface to produce, based on the type of action (save or delete) and the entity the predicate is for, an
IPredicateExpression object
which is then used as the filter for the action (Save or Delete). Each entity object has a property,
ConcurrencyPredicateFactoryToUse, which can be set to
an instance of
IConcurrencyPredicateFactory. If specified, each Save() and Delete() call on the entity will consult this object for a filter object.
This is also the case for recursive saves. If you want concurrency control deep down a recursive save, it's key that you set those object's
ConcurrencyPredicateFactoryToUse property to an instance of
IConcurrencyPredicateFactory.
IConcurrencyPredicateFactory instances can't be
shared between Adapter and SelfServicing code, however the source code can
be shared.
Below an example implementation of
IConcurrencyPredicateFactory,
which returns predicates which test for equality on
EmployeeID for
the particular
order. This will make sure the Save or Delete action
will only succeed if the entity in the database has still the same value for
EmployeeID as the in-memory entity had when it was loaded from the
database.
// [C#]
private class OrderConcurrencyFilterFactory :
IConcurrencyPredicateFactory
{
public IPredicateExpression CreatePredicate(
ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
{
IPredicateExpression toReturn = new PredicateExpression();
OrderEntity order = (OrderEntity)containingEntity;
switch(predicateTypeToCreate)
{
case ConcurrencyPredicateType.Delete:
toReturn.Add(OrderFields.EmployeeID == order.Fields[(int)OrderFieldIndex.EmployeeID].DbValue);
break;
case ConcurrencyPredicateType.Save:
// only for updates
toReturn.Add(OrderFields.EmployeeID == order.Fields[(int)OrderFieldIndex.EmployeeID].DbValue);
break;
}
return toReturn;
}
}
' [VB.NET]
Private Class OrderConcurrencyFilterFactory
Implements IConcurrencyPredicateFactory
Public Function CreatePredicate( _
predicateTypeToCreate As ConcurrencyPredicateType, containingEntity As object) _
As IPredicateExpression Implements IConcurrencyPredicateFactory.CreatePredicate
Dim toReturn As IPredicateExpression = New PredicateExpression()
Dim order As OrderEntity = CType(containingEntity, OrderEntity)
Select Case predicateTypeToCreate
Case ConcurrencyPredicateType.Delete
toReturn.Add(OrderFields.EmployeeID = _
order.Fields(CInt(OrderFieldIndex.EmployeeID)).DbValue)
Case ConcurrencyPredicateType.Save
' only for updates
toReturn.Add(OrderFields.EmployeeID = _
order.Fields(CInt(OrderFieldIndex.EmployeeID)).DbValue))
End Select
Return toReturn
End Function
End Class
During recursive saves, if a save action fails, which can be caused by a
ConcurrencyPredicateFactory
produced predicate, thus if no
rows are affected by the save action, an ORMConcurrencyException is thrown by the save logic, which will terminate any transaction started by
the recursive save.
To set an IConcurrencyPredicateFactory object when an entity is created or initialized, please see the section
Adding your own code to the generated classes which discusses various ways to modify the generated code to
add your own initialization code which for example sets the IConcurrencyPredicateFactory instance for a particular object. You can also set an IConcurrencyPredicateFactory
instance of an entity using the
ConcurrencyPredicateFactoryToUse property of an EntityCollection to automatically set the ConcurrencyPredicateFactoryToUse property
of an entity when it's added to the particular entity collection.
A third way to set an entity class instance's
IConcurrencyPredicateFactory is by using
Dependency Injection.
Entities, NULL values and defaults
Some Entity fields are optional and aren't set to a value in all cases, which
makes them
undefined or
null. This makes the fields
nullable. Nullable fields
often have a 'default' value set in the database; this is a value which is inserted by the database server when a NULL is inserted in such a column.
These default values are defined in the table definition itself.
NULL values read from the database
If a field is NULL in the database, the in-memory value will be null / Nothing. This means that the CurrentValue
property of the field object in the entity's Fields collection (entity.Fields[index].CurrentValue)
will be null / Nothing in this situation, not a default value, and the
field's property will return null / Nothing as well.
Setting a field to NULL
To set a field to null, in a new entity, simply don't provide a value for
the field. The INSERT query will not set the corresponding table field with
a value, as the entity field wasn't changed (because you didn't supply a value for it).
If you have set a default value for that table field, the database engine
will automatically fill in the default value for that field in the database.
If you want to set a field of an existing entity to NULL, you first
fetch the entity from the database and after that you set the field's
property to null / Nothing. When the entity is saved after that, the UPDATE
query will set the corresponding table field to NULL.
Example:
// [C#]
OrderEntity order = new OrderEntity(10254);
order.ShippingDate = null;
order.Save();
' [VB.NET]
Dim order As New OrderEntity(10254)
order.ShippingDate = Nothing
order.Save()
To test whether a field is null, simply read the field's property and test
whether it's null / Nothing.
Extending an entity by intercepting activity calls
During the entity's lifecycle and the actions in which the entity participates, various methods of the entity are called, and which might be a good candidate for
your own logic to be called as well, for example when the entity is initialized you might want to do your own initialization as well. The entity classes offer
a variety of methods for you to override to perform tasks in various situations. These methods
all start with
On and can be found
in the LLBLGen Pro reference manual in the class
EntityBase. The entity classes also offer events for some situations, like the Initializing and Initialized
events.
If you want to perform a given action when one of these methods are called, you can override them in the generated entity classes, preferably
using the methods discussed in
Adding your own code to the generated classes.
Note:
|
OnTransactionCommit and OnTransactionRollback are called on any entity participating in the transaction, no matter if there was
an action on the entity or not. To check if an entity was saved during a transaction, test the entity's Fields.State property. If it's OutOfSync, the
entity was saved.
|
IDataErrorInfo implementation
Entity classes implement IDataErrorInfo.
To utilize this interface in your own code, two methods are available:
SetEntityError and
SetEntityFieldError, which allows external code to set the error of a field and/or entity. If
true is passed for the argument
append of
SetEntityFieldError, the error message
is appended to an existing message for that field using a semi-colon as separator.
Entity field validation, which is triggered by setting a field to a value, sets the field's error
message if an
exception occurs or when the custom field validator fails. The error message is appended to an existing message.
Field data versioning
One innovative feature of LLBLGen Pro is its field data versioning. The fields of an entity, say a
CustomerEntity, can be versioned and saved under a name inside
the entity object itself. Later, you can decide to rollback the entity's field values at a later time. The versioned field data is contained inside the entity
and can pass with the entity remoting borders and is saved inside the XML produced by WriteXml(). All fields are versioned at once, you can't version a field's
values individually.
The following example loads an entity, saves its field values, alters them and then rolls them back, when an exception occurs.
// C#
CustomerEntity customer = new CustomerEntity("CHOPS");
customer.SaveFields("BeforeUpdate");
try
{
// show a form to the user which allows the user to
// edit the customer entity
ShowModifyCustomerForm(customer);
}
catch
{
// something went wrong. Entity can be altered. Roll back
// fields so further processing won't be affected by these
// changes which are not completed
customer.RollbackFields("BeforeUpdate");
throw;
}
' VB.NET
Dim customer As New CustomerEntity("CHOPS")
customer.SaveFields("BeforeUpdate")
Try
' show a form to the user which allows the user to
' edit the customer entity
ShowModifyCustomerForm(customer)
Catch
' something went wrong. Entity can be altered. Roll back
' fields so further processing won't be affected by these
' changes which are not completed
customer.RollbackFields("BeforeUpdate")
Throw
End Try