Hello everybody,

First of all, I want to thank the creators of DataObjects.Net. I have only recently started to put my hands on it and I am quite satisfied.

Regarding the reason of this post: I am doing some tests in order to get confident with this ORM framework. Although I don't know how to implement a certain "feature", which is common to the most of my applications.

Please consider the following example:

tblCompany <--- it's the table with the list of companies <fields...>

tblCompanyDataChange <---- table that stores information that can be changed and need to be tracked in time. <fields..>

With my previous implementation (not using DO.NET), I used to Load the Company, and during its loading I used to get only the first CompanyDataChanged associated with it.

To make you better understand, I am going to quote here a short-version of the query that was used:

SELECT * FROM tab_companies
INNER JOIN tab_company_data_changes
ON tab_company_data_changes.company_data_change_id =
(
SELECT MAX(tab_company_data_changes.company_data_change_id)
FROM tab_company_data_changes
WHERE tab_company_data_changes.company_id = tab_companies.company_id
)
WHERE tab_companies.company_id = @CompanyID

Now, I am trying to achieve the same result by using DataObjects.NET, and here comes my troubles.

I have created a TCompany object and a TCompanyChangedData object.

[HierarchyRoot]
    public class TCompany : Entity
    {
        [Field, Key]
        public int Id { get; private set; }
[Field]
        [Association(
                     OnOwnerRemove = OnRemoveAction.Cascade,
                     OnTargetRemove = OnRemoveAction.Deny)]
        private EntitySet<TCompanyChangedData> ChangedDataCollection { get; set; }

        private TCompanyChangedData DataChange
        {
            get
            {
                return ChangedDataCollection.OrderBy(TCompanyChangedData => TCompanyChangedData.Id).ElementAt(0);
            }
        }

        public String CorporateName
        {
            get { return DataChange.CorporateName; }
            set
            {
                ChangedDataCollection.Add(
                    new TCompanyChangedData
                    {
                        CorporateName = value,
                        LegalRepresentative = DataChange.LegalRepresentative
                    });
            }
        }

Ok, as you can see: I would like the object to be transparent to the user. The user should just know that in the object TCompany there is an "ID" and - say - a "CorporateName". However, when a user wants the CorporateName, the system should look up the TCompanyChangedData Collection and get just the very first one for the given company. On the other way, when a user wants to change a CorporateName, the system should create a new CompanyChangedData, copy all the information from the previous one across but the CorporateName, and then add it to the collection...

Now, I don't know if this is possible and I don't know which one is the best way to get this done.

Could someone advice me (guide me ;) ) on this?

Thanks in advance, Gianluca

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

asked Feb 18 '10 at 09:13

Editor's gravatar image

Editor
46154156157


One Answer:

Alex (Xtensive) wrote:

The main advice is: since current version of object is required in most of cases, I recommend you to duplicate the fields from TCompanyChangedData in TCompany. Getting additional query or join in almost any case is enough expensive, and this solution allows to avoid this.

Now - more interesting part: such things as TCompanyChangedData can be implemented in DO4 in very generic way: 1. There is Session.OperationCompleted event providing IOperation object describing the operation. All integrated operations, such as property setting, are already described there, + moreover, the operation logging framework is open, so you can create your own ones. Note that Operations framework is not fully finished yet (we plan to add more features there, e.g. undo support), but it is already used by DisconnectedState to maintain its operation log (in fact, DisconnectedState just subscribes to this event + few more related to transactions).

There is no part inManual covering Operations framework now. If you're interested, open Xtensive.Storage solution and look for SessionBound.OpenOperationContext method usage.

  1. To make some generic handler always listening this even, implement IModule (e.g. AuditTrailModule). All IModules are notified of certain domain events - you need OnBuilt event, where you can sign up your handler to Domain.SessionOpen event, so it could subscribe to Session.OperationCompleted event. When it's done, it will be enough to register an assembly containing AuditTrailModule in DomainConfiguration.Types to get audit trail working for all the Sessions in the Domain.

  2. You can create TChangeData<t> where T: Entity type containing T Changed object property to track changes related to any particular T. DO4 will automatically create & map all the instances of this generic type you need in your domain (although I recommend you to add more strict limitation like "where T: IAuditable").

See http://goo.gl/JYFo - there is a bit more comprehensive description. And see the first answer there - you avoid referential integrity constraints in audit tables. In DO4 this is achieved by applying [Association(OnTargetRemove = OnRemoveAction.None)] on reference property (= no FK will be created for it, and no reference fixup action will be performed on target removal).

  1. If you still need this, IAuditable.GetLastChangeDataEntry<t>() can be implemented as an extension method operating nearly as you wrote. Moreover, you can add support for this method in LINQ - by nearly this way: viewtopic.php?f=29&t=5767

P.S. Most likely we'll provide a generic framework for audit trail (and, likely, even undo-redo) in future. But for now this isn't scheduled.


Alex (Xtensive) wrote:

Forgot to add: I recommend you to store change data inside audit record in non-relational structures (e.g. as IChangeSet TChangeData<t>.Changes storing the actual data in persistent private byte[] SerializedChanges field). Likely, you won't query for it, i.e. it's just for history & recovery, so it's much easier to store it inside regular .NET objects.


Alex Kofman wrote:

I'd suggest you to add persistent reference from Company to last (actual) DataChange. It will allow to operate with versioned data more effectively:

[HierarchyRoot]
    public class TCompany : Entity
    {
        [Field, Key]
        public int Id { get; private set; }

        [Field]
        [Association(OnOwnerRemove = OnRemoveAction.Cascade,
                     OnTargetRemove = OnRemoveAction.Deny)]
        private EntitySet<TCompanyChangedData> ChangedDataCollection { get; set; }

        [Field]
        public TCompanyChangedData DataChange { get; set; }

        public String CorporateName
        {
            get { return DataChange.CorporateName; }
            set
            {
                DataChange = new TCompanyChangedData
                    {
                        CorporateName = value,
                        LegalRepresentative = DataChange.LegalRepresentative
                    };
                ChangedDataCollection.Add(DataChange);                    
            }
        }
    }

Note, that by default you can not use CorporateName in LINQ queries, you can just do following:

from company in Query.All<TCompany>()
where company.DataChange.CorporateName == "My company"
select company

If you want to write such queries

from company in Query.All<TCompany>()
where company.CorporateName == "My company"
select company

you need to use some advanced method described here.

answered Feb 18 '10 at 10:05

Editor's gravatar image

Editor
46154156157

gianluca wrote: Thank you very much!

I'll follow your advice.

Gianluca.

(Feb 18 '10 at 10:05) Editor Editor's gravatar image

I just made a port about audit logging with DO4: http://blog.alexyakunin.com/2010/03/sim ... -some.html

(Feb 18 '10 at 10:05) Alex Yakunin Alex%20Yakunin'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

Subscription:

Once you sign in you will be able to subscribe for any updates here

Tags:

×573

Asked: Feb 18 '10 at 09:13

Seen: 3,486 times

Last updated: Feb 18 '10 at 09:13

powered by OSQA