Hi, I'm new to DataObjects and have an initial beginners question. How best to implement a persistent calculated read-only property in DO4?

For example, if I have 3 persistent Fields - Qty, Price and Total - how would I define Total (Qty*Price) given an Entity's attributes and lifecycle?

-- AlexH


Updated at 16.12.2009 20:24:15

Many thanks Alex.

One of the things that concerned me, was that if using the manual approach, one would need to have complex checks like "IsLoading" or "IsSaving" etc. so that (parts of) the setter would not be called many times. But that doesn't seem to be the case?

This thread was imported from our support forum. The original discussion may contain more detailed answer. Original topic by alexh.

asked Dec 16 '09 at 07:14

Editor's gravatar image

Editor
46156156157


One Answer:

Alex Kofman wrote:

There are two principal ways: Override OnSetFieldValue method or implement property accessors manually:

Overriding OnSetFieldValue:

[Field]
    public int Price { get; set; }

    [Field]
    public int Quantity { get; set; }

    [Field]
    public int Total { get; private set; }

    protected override void OnSetFieldValue(FieldInfo field, object oldValue, object newValue)
    {      
      base.OnSetFieldValue(field, oldValue, newValue);

      if (field.Name=="Price" || field.Name=="Quantity")
        Total = Price * Quantity;
    }

Manual property accessors:

[Field]
    public int Price
    {
      get { return GetFieldValue<int>("Price"); }
      set
      {
        SetFieldValue("Price", value);
        Total = Price * Quantity;
      }
    }

    [Field]
    public int Quantity
    {
      get { return GetFieldValue<int>("Quantity"); }
      set
      {
        SetFieldValue("Quantity", value);
        Total = Price * Quantity;
      }
    }

    [Field]
    public int Total { get; private set; }

Note, that I used private setter for read-only property in both cases.


Alex (Xtensive) wrote:

See Advanced LINQ Translation chapter as well, especially, "Writing custom LINQ expression rewriter" topic there.

It's pretty easy to make such calculated properties work in LINQ queries as well.


Alex (Xtensive) wrote:

Exactly, you shouldn't check something like IsLoading\IsSaving - DO4 automatically loads any value, if it isn't available. See http://dataobjectsdotnet.googlecode.com ... rmance.htm - there is a good explanation of lazy loading (work on this chapter is in progress, but this part is finished).


Alex (Xtensive) wrote:

So first of all, Alex Kofman's answer was absolutely precise. But I'd like to provide an "advanced" reply. Imagine that we don't want to store a value of calculated property, but want to just cache it in memory.

Key ideas to keep in mind:

  • DO4 is designed to reflect the changes made to database transparently

  • So if you'll just cache the calculated value, at some moment you can discover it's stale (i.e. not the same as expected result calculated using current property values)

  • This means there must be a way allowing to recalculate it when this is necessary.

Basically, there are two possible ways of detecting this: a) After caching a value, you can subscribe on all the events that might change the result, including Session.TransactionOpen, TransactionRollbacked and notify your cached property on changes in sources. Any of these events must invalidate cached value. b) Instead of subscribing to transaction related events, you may store a reference to Transaction object along with cached value referencing a Transaction where the value was cached. But luckily, there is already TransactionalValue<t> type.

Approach a) is less attractive also because you must notify all the entities on completion of certain events - so this must happen even if the value cached inside these entities won't be used at all further. That's why we prefer (and use internally - to cache various transactional state) way b).

Check out the sample for way b) (it was committed just today to our repository).

Points of interest:

  • Order.TotalPrice is regular calculated non-persistent (but transactional - i.e. its getter always runs in a single transaction) property.

  • CustomLinqCompilerContainer.TotalPrice is LINQ expression rewriter allowing to use it in queries. It could be attached to any other TotalPriceXxx properties (or to all of them).

  • Order.TotalPriceCaching is non-persistent, but caching version of TotalPrice maintained always in sync, that is recalculated as rarely as it's possible (actually this depends on invalidation logic there, that is far from ideal in this sample). Its implementation shows how to use Session events to track changes in other properties, attach event handler just once per Session, etc.

  • Order.TotalPriceExpiring is non-persistent, but caching version of TotalPrice that isn't guaranteed to be in sync. Cached value there is simply expires in 1 second after each subsequent update.


Alex (Xtensive) wrote:

Later I'll show how to do similar tricks with queries to cache more complex calculations - there is almost nothing special (the only new type I'll use there will be CachedSequence<t>), but since caching of query results seems pretty common, feel I must show this as well.


psulek wrote:

Check out the sample for way b) (it was committed just today to our repository).

Link specified does not exist, but found this: CalculatedValueCachingTest.cs

answered Dec 16 '09 at 14:11

Editor's gravatar image

Editor
46156156157

Sorry, it seems I made a mistake by reading your question not carefully. You want to properly cache calculated value as well. I'll provide an example for this shortly.

(Dec 16 '09 at 14:11) Alex Yakunin Alex%20Yakunin's gravatar image

alexh wrote: Thanks Alex, that example you mention would be much appreciated.

(Dec 16 '09 at 14:11) Editor Editor's gravatar image

Yes, these links points to the tip revision of repository, so since the file was recently moved, the old one became obsolete.

Thanks for pointing on this!

(Dec 16 '09 at 14:11) Alex Yakunin Alex%20Yakunin's gravatar image

alexh wrote: Many thanks for for all the explanation Alex - Happy New Year to all.

-- Alex Hoffman

(Dec 16 '09 at 14:11) Editor Editor's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA