Generated code - Using the context, SelfServicing
Preface
Selfservicing and Adapter both support so called
uniquing contexts,
it allows you to make sure that
one entity class instance per loaded
entity instance is known in your
semantic context (e.g. method,
object), as the runtime framework makes it possible to load a given entity
with a given PK multiple times, each time in a new entity class instance.
These contexts, implemented in the
Context class, available in the
ORM Support classes, represent a semantic context in your program. Within
these semantic contexts, the representing
Context object assures that
an entity instance (the
data) loaded by the framework is loaded in
just one entity
class instance. This is e.g. required for re-fetching
trees of objects using
prefetch paths,
or for situations where more than one object with the same data is
problematic.
This section discusses the Context object more in
detail. It is not strictly necessary to use a Context object in your
application, e.g. in state-less environments like ASP.NET it's not of much
use. Though it can sometimes be required to have just one entity class
instance with a given entity's data in a given semantic context, e.g. in an
edit form in a windows forms application.
The Context class
When you pass an entity instance using remoting or webservices to a server
which then returns it back after processing, you won't reference the same
instance of the entity class you sent to the server. This is because of the
serialization and deserialization which takes place during remoting or using
webservices. The
Context class identifies entity instances (the
data) using the PK field values. New entities aren't directly added to
the
Context's internal object cache, as for example Identity columns
won't have a value for the PK field until they're saved and the transaction
has been committed. When an entity is saved and the transaction is committed
(if any), the entity class instance the entity data is in is added to the
Context's cache if the entity is new. A non-new entity which is added to
a
Context object, is directly added to the Context's internal object
cache, if the entity class instance hasn't been added to another Context
already.
Entity class instances can be added to the Context at any
time, as well as entity collections. When an entity class instance is added
to a
Context, its internally referenced related entity class
instances and entity collection objects are added to the same
Context
object as well. When an entity collection is added to a
Context
object, all its contained entity class instances will be added to the
Context and every entity class instance added to the entity collection
after that point will be added to the same
Context object the
collection is added to as well, which makes it easy to work with a Context
object, as it is mostly transparent.
The
Context objects
don't act as a cache which is used to prevent database activity. Every query
is still executed on the database. If an entity instance is already loaded
in the used
Context object, the entity data read from the database is
not added to a new entity class instance, but the entity class instance
already known by the context containing the same entity instance is updated.
If the already known class instance is marked as
dirty, the data
isn't updated and the loaded entity data is simply skipped and the already
known entity class instance is returned as is. This is done in the
Get
method of the
Context object. The
Context has a flag to
disallow this particular action:
SetExistingEntityFieldsInGet. See
the LLBLGen Pro reference manual for details on this flag.
You can of
course use the
Context as an object cache for single entity fetches,
though keep in mind that a
Context object is simply acting as a
unique entity
class instance supplier (one entity class instance per
loaded entity instance), it doesn't fetch entities from the database, so if
you request an entity instance from a
Context object using
Get
and the
Context object can't find it in its cache, you have to test
if the returned entity
class instance does indeed contain a fetched
entity instance (
entity.IsNew is false)
or a new entity class
instance
(entity.IsNew is true)
. Adding an entity
class instance which is already present in the
Context is a no-op, as well
as when an entity class instance is already part of another
Context
object. After an entity is added to a
Context object, and a 1:1/m:1
reference is set to an entity class instance, the related entity is not
added to the
Context automatically, this has to be done manually by
the developer, though when an entity is added to a collection which is added
to a context, the entity is added to that
Context as well. The
Context object an entity is added to is returned by the entity's
ActiveContext property.
When an entity is deleted, the status of
the entity is set to Deleted by the delete methods. The
Context.
Get
method will remove an entity from its internal cache if the entity is
deleted and not participating in a transaction.
Using the Context class
The Context class should be seen as a convenience providing class for uniquing within a semantic context. It shouldn't be confused with a UnitOfWork +
Object Fetch object, because it leaves that functionality to other objects and methods.
Retrieving instances from a Context
A
Context object supplies a
Get method which offers different
ways to retrieve the already loaded instance for a given entity. As a
Context object uses the value(s) of the PK field(s), you can use this to
retrieve the unique instance. Below are the different ways illustrated: it
will try to retrieve the instance which already contains the entity data for
the customer with CustomerID "CHOPS".
// C#
// using a factory
CustomerEntity c = (CustomerEntity)myContext.Get(new CustomerEntityFactory(), "CHOPS");
// using a fetched entity
CustomerEntity c = new CustomerEntity("CHOPS");
c = (CustomerEntity)myContext.Get(c);
' VB.NET
' using a factory
Dim c As CustomerEntity = CType(myContext.Get(new CustomerEntityFactory(), "CHOPS"), CustomerEntity)
' using a fetched entity
Dim c As New CustomerEntity("CHOPS")
c = CType(myContext.Get(c), CustomerEntity)
Single entity fetches
To be able to load the entity's data into a new entity class instance if the
Context used doesn't have an instance with that data present and just
return the already loaded instance if the
Context does have an
instance of the entity class with the entity data, use the construct
mentioned above:
// C#
CustomerEntity c = (CustomerEntity)myContext.Get(new CustomerEntity("CHOPS"));
' VB.NET
Dim c As CustomerEntity = CType(myContext.Get(new CustomerEntity("CHOPS")), CustomerEntity)
This will fetch customer "CHOPS" from the database but the context will check if the entity is already loaded in this context. If so,
it will return that instance, not the newly created instance. If the entity object isn't known by the Context, it is added to the Context and the
Context returns the instance created in the Get() method call.
Entities can also be added manually first and then fetched:
// C#
CustomerEntity c = new CustomerEntity();
myContext.Add(c);
c.FetchUsingPK("CHOPS");
' VB.NET
Dim c A New CustomerEntity()
myContext.Add(c)
c.FetchUsingPK("CHOPS")
Or, using a unique constraint:
// C#
CustomerEntity c = new CustomerEntity();
myContext.Add(c);
c.FetchUsingUCCompanyName("Foo Inc.");
' VB.NET
Dim c A New CustomerEntity()
myContext.Add(c)
c.FetchUsingUCCompanyName("Foo Inc.")
Though it has to be understood that the actual entity instance inside the
entity
class instance referenced by the variable 'c' is only unique
if the particular entity instance hasn't been loaded yet. This is due to the
c = new CustomerEntity() line; it creates a
new entity
class
instance so adding it to a context doesn't make it
the entity
class instance holding the entity instance. Fetching using unique
constraints is a bit problematic in this case. To avoid that you can do:
// C#
CustomerEntity c = new CustomerEntity();
c.FetchUsingUCCompanyName("Foo Inc."); // fetch.
c = (CustomerEntity)myContext.Get(c); // get unique version. No db activity.
' VB.NET
Dim c As New CustomerEntity()
c.FetchUsingUCCompanyName("Foo Inc.") ' fetch.
c = CType(myContext.Get(c), CustomerEntity) ' get unique version. No db activity.
Prefetch Path fetches
Fetching an entity and using a prefetch path use either
FetchUsingPK() or
FetchUsingUCFieldnames(). Both have an overload
which accepts a
Context object. If you're fetching a graph and you want to have for every already loaded entity in a particular
Context the
instance in which the entity is already loaded, you can pass in the context in which these entity objects already are added to. The fetch
logic will then build the object graph using the instances from the passed in
Context, otherwise it will read the entity data in newly created
entity objects.
Note: |
When fetching an entity collection, you have to add the collection to fetch to the context object and then call the GetMulti method. Doing so will set the collection's DoNotAddIfPresent property to true. |
Entity Save calls
When
Save() is called on an entity class instance, the base class' Save
method will signal the Context it is in (if any), that the entity in
question is saved. This allows the
Context to do housekeeping on
new/existing entity class instances. This takes into account running
transactions, meaning that a rollback doesn't leave the
Context in an
undefined state, but in the state as before the transaction started.
Multi-entity activity
Actions on entity collections work inside the active context if the collection is first added to a context. All persistence
logic will re-use objects from the
Context object if the entity collection used is added to a context.
SaveMulti() will first add any entities saved to the context the collection is in, if the entity isn't already in the context. s
Remarks
- PK values shouldn't be changed. The Context relies on non-changing PK values.
- A Context shouldn't be used as a cache, nor should it kept alive for a long time, just long enough for the
semantic context to use unique objects in.
- Deleted entities which are deleted in the database directly are not picked up by the
Context. This is something the developer has to take into
account when deleting entities directly.
- As the Context class doesn't use any locking mechanism, the
Context object isn't thread-safe and should be used for single-thread semantic contexts