LinqConnect Documentation
In This Topic
    Object Identity
    In This Topic
    Object Identity
    LinqConnect Documentation
    Object Identity
    [email protected]

    When you are querying for a table row via an ORM, a new entity object that represents this row is created - that's simply what ORMs are for. But what if you fetch the same row via another query? Will you get the same object instance or a copy that is stored somewhere else in the memory? For LinqConnect, the answer is: yes, this would be the same object:

    Object cache

    To provide such handling of the object identity, DataContext creates an entity cache, where all entities created by the application or loaded from the database are placed. After another query gets the corresponding row from the database, this entity is got from the cache (instead of materializing a new object):

    Materializing query results

    The image shows how a result set returned by server is 'transformed' into a collection of entity objects. For each row from the result set, it is checked whether the corresponding entity is already in the cache. If it is, this entity is added to the resulting entity collection. If it is not, the entity is materialized from the row and added to both cache and result collection.

    This approach has the following advantages:

    • You always work with the same entity instance. If you do some changes to an entity in several places of your application, it is not possible that you end up with several separate objects having different states.
    • You do not need to watch over the entities you changed, and to specify that this particular object should be persisted to the database. Since DataContext holds the links to all entities, it saves all changed entities during the SubmitChanges call.
    • Fetch performance is slightly better, because entity materialization is omitted if this entity is already in the cache.

    The things one should keep in mind because of LinqConnect object caching:

    • The objects you work with are actually the ones from the cache. Since they can be got from the cache (not from the database) even on executing a query, the probability they become stale increases. For the information about resolving concurrency issues caused by stale data, refer to the topic on concurrency processing.
    • All changes are persisted based on the entity primary key. To find an object in the cache, LinqConnect has to have a hash code that allows differentiating entities. The most natural code for this is the entity key (generally, it will be the set of entity fields that are mapped to the primary key columns of the corresponding table, though you may mark some other fields as the entity key as well). The consequences of using entity key in this way are that this key should not be changed, and that no changes at all can be saved for entities without the key.

    Entity Cache Modes

    As it was said, fetched entities are saved into a cache just after being materialized. Thus, the context actually has to hold references to these objects even if a user needs them no more. If the application architecture allows DataContext instances to exist a long time, storing references to all entities that were used once may result in excessive memory consumption.

    To eliminate such a possibility, the entity cache may hold weak references to the entities instead of strong ones (and this is actually the default behaviour). In this case, unused entity objects will eventually be garbage-collected. If some future query returns the rows corresponding to such entities, new entity objects will be materialized from the result set (this also gives a collateral bonus of entities being up-to-date).

    If, for some reason, you do want to keep strong references to the entities in the cache, you can change the EntityCachingMode property of the DataContext class. Let us consider the following sample:


    static double GetFirstProductPrice(CrmDemoDataContext db) {
     
        Product product = db.Products.Where(p => p.ProductID == 1).Single();
        return product.Price.Value;
    }
     
    static void Main(string[] args) {
        CrmDemoDataContext context = new CrmDemoDataContext();
     
        // Configure the caching behaviour:
        context.EntityCachingMode = EntityCachingMode.WeakReference;
     
        // Get the original value of an entity field.
        // A separate method is used to ensure that the Product object is not used in the current scope.
        double initialPrice = GetFirstProductPrice(context);
     
        // Force the garbage collector to finalize unused objects.
        GC.Collect();
     
        // Use another context instance to change the value in the database.
        CrmDemoDataContext checkContext = new CrmDemoDataContext();
        Product updatedProduct = checkContext.Products.Where(p => p.ProductID == 1).Single();
        updatedProduct.Price += 10;
        checkContext.SubmitChanges();
     
        // Query for the product used in the 'GetFirstProductPrice' method.
        // If strong references were used, the entity should still be available in the cache.
        // Otherwise, has to materialize the entity again.
        Product product = context.Products.Where(p => p.ProductID == 1).Single();
     
        Console.WriteLine(
            "Original value: {0}.\n Current database value: {1}.\n Value in the 'product' object: {2}.",
            initialPrice,
            updatedProduct.Price,
            product.Price);
    }
    Private Shared Function GetFirstProductPrice(db As CrmDemoDataContext) As Double
     
        Dim product As Product = db.Products.Where(Function(p) p.ProductID = 1).[Single]()
        Return product.Price.Value
    End Function
     
    Private Shared Sub Main(args As String())
        Dim context As New CrmDemoDataContext()
     
        ' Configure the caching behaviour:
        context.EntityCachingMode = EntityCachingMode.WeakReference
     
        ' Get the original value of an entity field.
        ' A separate method is used to ensure that the Product object is not used in the current scope.
        Dim initialPrice As Double = GetFirstProductPrice(context)
     
        ' Force the garbage collector to finalize unused objects.
        GC.Collect()
     
        ' Use another context instance to change the value in the database.
        Dim checkContext As New CrmDemoDataContext()
        Dim updatedProduct As Product = checkContext.Products.Where(Function(p) p.ProductID = 1).[Single]()
        updatedProduct.Price += 10
        checkContext.SubmitChanges()
     
        ' Query for the product used in the 'GetFirstProductPrice' method.
        ' If strong references were used, the entity should still be available in the cache.
        ' Otherwise, has to materialize the entity again.
        Dim product As Product = context.Products.Where(Function(p) p.ProductID = 1).[Single]()
     
        Console.WriteLine( _
            "Original value: {0}." & vbLf & " Current database value: {1}." _
            & vbLf & " Value in the 'product' object: {2}.", _
            initialPrice, _
            updatedProduct.Price, _
            product.Price)
    End Sub
    

    In the sample, we create a context and set its caching mode. Using weak references is the default behaviour, thus setting EntityCachingMode may actually be omitted. We then get the price of a single product via this context; this is done in a separate method to ensure that the Product entity falls out of the scope. Since the cache holds a weak reference only, the next line ensures that this Product is garbage-collected. After that, we update the corresponding row in the database with another DataContext instance, and query for the same product with the initial context object. At this moment, the LinqConnect runtime cannot find the proper Product object in the cache (because this object was GC-ed) and so has to materialize it from the result set. Thus, 'updatedProduct.UnitPrice' should be equal to 'product.UnitPrice'.

    If we set the caching mode to keeping strong references:


        // Configure the caching behaviour:
        context.EntityCachingMode = EntityCachingMode.StrongReference;
        ' Configure the caching behaviour:
        context.EntityCachingMode = EntityCachingMode.StrongReference
    

    the runtime will find the Product instance created in the GetFirstProductPrice method in the cache. Thus, this object would be stale and 'product.UnitPrice' should be equal to the 'initalPrice' variable.

    If an entity has no key, it cannot be cached and change tracking is not enabled for such entity. LinqConnect does not allow modifications of such entities. An exception is raised when such entity is created or deleted, and updates of such entity are ignored.