Updating an Entity

Posts   
 
    
luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 04-Mar-2008 07:42:59   

I've noticed that the llblgen Auditor will generate an InsertOfNewEntity audit event when recursively saving an entity w/ prefetch path, even though the entity is not new

Example Code:


IndividualEntity i = new IndividualEntity(100000001);
IPrefetchPath2 p = new PrefetchPath2((int)EntityType.IndividualEntity);
IPredicateExpression predicate = new PredicateExpression();

// addresses
p.Add(IndividualEntity.PrefetchPathAddressHome1);
p.Add(IndividualEntity.PrefetchPathAddressWork1);
p.Add(IndividualEntity.PrefetchPathAddressWork2);
using (DataAccessAdapter a = new DataAccessAdapter())
{
    a.FetchEntity(i, p);
}

i.Middlename = DateTime.Now.ToLongTimeString();
i.AddressPrimaryId = i.AddressWork1Id;
i.AddressWork1Id = i.AddressWork1Id;

Audit.IndividualAuditor audit = (Audit.IndividualAuditor)i.AuditorToUse;
audit.Reason = "Changed MiddleName";
audit.Fullname = Individual.Fullname(i);

using (DataAccessAdapter a = new DataAccessAdapter())
{
    a.SaveEntity(i, true);
}

As you can see in the above, I fetch an Individual Entity w/ prefetched AddressEntity, then change the middlename field (I use the time string to ensure a new value and thus new audit)

I then do a recursive save and the audit log records the following 2 action which I think are erroneous (or misleading?):


EntityFieldSet
Changed Id to: 100000001. (Was: NULL)

InsertOfNewEntity 
PK(Id:100000001). RelatedEntityName: PK(Id:100000001). MappedFieldName: AddressEntity

1.) I don't understand why the entity's primaryKey field is triggering an audit event. This is not a new entity and therefore the PK should already be set.

2.) Again, the IndividualEntity is not new, yet an InsertOfNewEntity event is recorded.

Is this a bug or am I doing something wrong? I'm not inserting a new entity, yet the auditor seems to be telling me that I am. Also, it is interesting to note that if I do not recursively save, then I only get the first audit event listed above (set id from null to int) but not the InsertOfNewEntity audit event.

Any clarification on this would be most appreciated, Thanks!

Version 2.5 Final Feb 4, 2008 c# asp.net 2

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 04-Mar-2008 10:27:50   

Would you please check if the following is true just after the FetchEntity: (i.Fields.State == EntityState.Fetched).

Also please examine the generated SQL queries to find out what's going on.

luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 04-Mar-2008 16:10:36   

Hi Walaa, thank you for the reply. I have confirmed that the entity is indeed fetched. Please see the attachment for the SQL output. It shows that the SELECT, then the UPDATE. The only INSERT is for the audit events.

luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 05-Mar-2008 03:58:19   

To correct my original post, I should have said "save with refetch" not recursive save. (Not sure if that makes a difference)

Okay, so I decided to test this out with the Auditing example provided by the SD on their site.

First, I created a test page with the following wired to a button click:


protected void Button1_Click(object sender, EventArgs e)
{
    try
    {
        // 1.) Fetch Entity
        CustomerEntity c = new CustomerEntity("ALFKI");
        using (DataAccessAdapter a = new DataAccessAdapter())
        {
            a.FetchEntity(c);
        }
        if (c.Fields.State != EntityState.Fetched)
        {
            throw new SystemException("Failed to Fetch Customer Entity");
        }


        // 2.) Make a unique change to entity
        c.ContactName = DateTime.Now.ToLongTimeString();


        // 3.) Save
        using (DataAccessAdapter a = new DataAccessAdapter())
        {
            a.SaveEntity(c);
        }
    }
    catch (Exception ex)
    {
        Response.Write("ERROR! Error Message is: " + ex.Message);
    }
}

Basically I fetch the CustomerEntity, update a field, and save. The auditInfo table returns 3 events, of which the first is:


EntityFieldSet
. FieldSet: CustomerId. OriginalValue: NULL. CurrentValue: ALFKI

I can't understand why an update would generate such an event, seeing as the CustomerID was never null during the whole button click. Can't help to think that I'm missing something obvious here?

TIA!

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 05-Mar-2008 10:46:30   

Please see the attachment for the SQL output. It shows that the SELECT, then the UPDATE. The only INSERT is for the audit events.

No attachement was found.

EntityFieldSet . FieldSet: CustomerId. OriginalValue: NULL. CurrentValue: ALFKI

I can't understand why an update would generate such an event, seeing as the CustomerID was never null during the whole button click.

That's the value of the CustomerId set by the following line:

CustomerEntity c = new CustomerEntity("ALFKI");

The auditor audits this action of setting the CustomerId.

Maybe it shouldn't have done so.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Mar-2008 11:58:12   

Will look into it.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Mar-2008 14:16:01   

1) please post/attach the auditor code you're using. 2) The auditor instance, is that inserted into many entities or just this one?

The PK set audit is as Walaa said: the pk value set.

Frans Bouma | Lead developer LLBLGen Pro
luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 05-Mar-2008 23:21:26   

Walaa wrote:

Please see the attachment for the SQL output. It shows that the SELECT, then the UPDATE. The only INSERT is for the audit events.

No attachement was found.

I've re-attached the sql queries. Not sure what happended there.

Attachments
Filename File size Added on Approval
sqlDebug.txt 22,699 05-Mar-2008 23:21.34 Approved
luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 06-Mar-2008 00:45:18   

Otis wrote:

1) please post/attach the auditor code you're using.

It is attached in zip file. (both ButtonClick event editing the IndividualEntity and the auditor DI class)

Otis wrote:

2) The auditor instance, is that inserted into many entities or just this one?

My auditor is based off the llblgen example, however it is split up into 2 audit entities, 1 to record entity level changes, and another entity to record field level changes (field name, old value, new value). Both entities are subTypes of a generic Audit entity.

If you are asking if the auditor is injected into more than 1 entity, it is currently only used for the IndividualEntity.

Otis wrote:

The PK set audit is as Walaa said: the pk value set.

Okay, so this is not a bug but a design choice. Not a problem, I can update the auditor to ignore these. I guess I'm not understanding the design choice, seeing as the Audit event is on field change, and the primaryId field was never changed (no sql insert). I understand that the id for the in-memory object was set, but then I guess all the other fields were set on fetch as well, yet only this field showed up as an audit event. I guess you can shine a light on the design benefit?

Please let me know if you need further information for me. I really appreciate the help!

Attachments
Filename File size Added on Approval
Audit.zip 5,228 06-Mar-2008 00:45.27 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 06-Mar-2008 10:15:50   

luciusism wrote:

Otis wrote:

1) please post/attach the auditor code you're using.

It is attached in zip file. (both ButtonClick event editing the IndividualEntity and the auditor DI class)

Otis wrote:

2) The auditor instance, is that inserted into many entities or just this one?

My auditor is based off the llblgen example, however it is split up into 2 audit entities, 1 to record entity level changes, and another entity to record field level changes (field name, old value, new value). Both entities are subTypes of a generic Audit entity.

If you are asking if the auditor is injected into more than 1 entity, it is currently only used for the IndividualEntity.

Why place the public auditor classes inside another class?

Otis wrote:

The PK set audit is as Walaa said: the pk value set.

Okay, so this is not a bug but a design choice. Not a problem, I can update the auditor to ignore these. I guess I'm not understanding the design choice, seeing as the Audit event is on field change, and the primaryId field was never changed (no sql insert). I understand that the id for the in-memory object was set, but then I guess all the other fields were set on fetch as well, yet only this field showed up as an audit event. I guess you can shine a light on the design benefit? Please let me know if you need further information for me. I really appreciate the help!

It's really a side effect of how the field was set: the PK field is set through the property, which triggers the auditor. If we want to prevent that we need all kinds of hassle code to work around that.

Btw, I don't really see the problem with the insert/update. Here's your query quoted from the .txt you attached:

Query: INSERT INTO [nemalink].[dbo].[audit] ([AffectedEntityId], [AffectedEntityTypeId], [AffectedEntityName], [ActionTypeId], [Action], [createdById], [createdDate])  VALUES (@AffectedEntityId, @AffectedEntityTypeId, @AffectedEntityName, @ActionTypeId, @Action, @CreatedById, @CreatedDate);SELECT @Id_AuditEntity=SCOPE_IDENTITY()
Parameter: @Id_AuditEntity : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Output. Value: <undefined value>.
Parameter: @AffectedEntityId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 100000001.
Parameter: @AffectedEntityTypeId : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 1.
Parameter: @AffectedEntityName : AnsiString. Length: 150. Precision: 0. Scale: 0. Direction: Input. Value: "IndividualEntity".
Parameter: @ActionTypeId : Byte. Length: 1. Precision: 3. Scale: 0. Direction: Input. Value: 9.

** Parameter: @Action : AnsiString. Length: 250. Precision: 0. Scale: 0. Direction: Input. Value: "Updated Individual: Kahng, Lucius (Luke) 9.". ** Parameter: @CreatedById : Int32. Length: 0. Precision: 10. Scale: 0. Direction: Input. Value: 100000001. Parameter: @CreatedDate : DateTime. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 3/4/2008 9:57:04 AM.

So it correctly logged the right action. I checked the code, and it definitely calls the right routines. I also can't reproduce it with our tests: updates log update audits, inserts log insert audits.

Frans Bouma | Lead developer LLBLGen Pro
luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 07-Mar-2008 07:06:53   

Why place the public auditor classes inside another class?

I guess I'm not understanding the question here. flushed I placed all my Auditor related classes into an Audit class. I'd like them to share private members.

It's really a side effect of how the field was set: the PK field is set through the property, which triggers the auditor. If we want to prevent that we need all kinds of hassle code to work around that.

Thank you for the clarification. I filter these actions out at the auditor level so it's not a problem.

So it correctly logged the right action. I checked the code, and it definitely calls the right routines. I also can't reproduce it with our tests: updates log update audits, inserts log insert audits.

Thanks again for taking to look at this. Unfortunately I'm left a little confused by your reply here. Which "code" did you check, yours or mine? When I execute the button click event as provided in the op, I get an audit action type of "InsertOfNewEntity". The purpose of the sql was to show Walaa that the button event was not inserting an entity. If there is no insert, then the InsertOfNewEntity event should not have been recorded in the Audit table.

The only way to trigger InsertOfNewEntity would be on AuditInsertOfNewEntity() or AuditReferenceOfRelatedEntity(). In my case, the AuditReferenceOfRelatedEntity() override is causing the InsertOfNewEntity event. (And only when I prefetch a related entity, such as the AddressEntity in the op example)

I'm sure it's the case that my custom Auditor is somehow erroneously triggering AuditReferenceOfRelatedEntity(). What I don't understand is how this could happen since llblgen determines when the method is called. I never call it explicitly, I just override it.

Thanks again Frans

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 07-Mar-2008 10:36:01   

luciusism wrote:

Why place the public auditor classes inside another class?

I guess I'm not understanding the question here. flushed I placed all my Auditor related classes into an Audit class. I'd like them to share private members.

Ok, in that case it's logical, yet, making classes share private members really breaks OO principles like encapsulation. I'd look into making the code not rely on that kind of mechanisms, but always communicate via public/internal methods/properties. Even derived classes.

So it correctly logged the right action. I checked the code, and it definitely calls the right routines. I also can't reproduce it with our tests: updates log update audits, inserts log insert audits.

Thanks again for taking to look at this. Unfortunately I'm left a little confused by your reply here. Which "code" did you check, yours or mine?

Mine.

When I execute the button click event as provided in the op, I get an audit action type of "InsertOfNewEntity". The purpose of the sql was to show Walaa that the button event was not inserting an entity. If there is no insert, then the InsertOfNewEntity event should not have been recorded in the Audit table.

True. The thing is that the persistence logic only calls Insert audits if the action was an insert. The code is pretty clear simple_smile Also, your attached sql trace shows that it logged an UPDATE, not an insert, so it does call the right audit event.

The only way to trigger InsertOfNewEntity would be on AuditInsertOfNewEntity() or AuditReferenceOfRelatedEntity(). In my case, the AuditReferenceOfRelatedEntity() override is causing the InsertOfNewEntity event. (And only when I prefetch a related entity, such as the AddressEntity in the op example)

I must have missed that in your code. Why do you call InsertOfNewEntity on that event? Audit reference of related entity is of course called when you prefetch related entities, as the related entity (e.g. order) is then associated with the entity (e.g. customer).

I'm sure it's the case that my custom Auditor is somehow erroneously triggering AuditReferenceOfRelatedEntity(). What I don't understand is how this could happen since llblgen determines when the method is called. I never call it explicitly, I just override it. Thanks again Frans

it's in your code ->


            public override void AuditReferenceOfRelatedEntity(IEntityCore entity, IEntityCore relatedEntity, string mappedFieldName)
            {
                try
                {

                    AddAuditedEntity(
                        entity,
                        ActionType.InsertOfNewEntity, // <<<<<<<<<<<<<<<<
                        string.Format("{0}. RelatedEntityName: {0}. MappedFieldName: {1}",
                            GetPrimaryKeyInfoFromEntity(entity), relatedEntity.LLBLGenProEntityName, mappedFieldName)
                    );
                }
                catch (Exception ex)
                {
                    Utilities.ErrorLog("ERROR swe32rertrt55: Failed to Commit Audit Action", ex.ToString());
                }
            }

stuck_out_tongue_winking_eye

Copy-paste detected! wink

Frans Bouma | Lead developer LLBLGen Pro
luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 07-Mar-2008 15:43:39   

Otis wrote:

it's in your code ->

Frans, thanks for the quick reply as always. I'll take your advice on encapsulation. Regarding the referenced code, you are correct that the AuditReferenceOfRelatedEntity() method in my code calls the InsertOfNewEntity audit type. To be fair, I did so b/c I "copy-paste" from your auditor example code. wink See below:


public override void AuditReferenceOfRelatedEntity(IEntityCore entity, IEntityCore relatedEntity, string mappedFieldName)
{
    // avoid to audit into AuditInfoEntity (this would create an overflow effect). This is necessary if this auditor is injected into 
    // all entities, thus also in the AuditInfoEntity
    if(entity is AuditInfoEntity)
    {
        return;
    }

    AddAuditEntryToList(
        entity.LLBLGenProEntityName,
        AuditType.InsertOfNewEntity,
        string.Format("{0}. RelatedEntityName: {0}. MappedFieldName: {1}",
            GetPrimaryKeyInfoFromEntity(entity), relatedEntity.LLBLGenProEntityName, mappedFieldName));
}


Code/Adapter/Auditors/DatabaseAuditor/DatabaseAuditor.cs

Using your code as a reference, I assumed that the LL would call the AuditReferenceOfRelatedEntity() method upon insert. (Hince the reason for using that type) I now realize what the problem is, thanks again to you and Walaa for all the help!