Persisting a TimeStamp in web app

Posts   
1  /  2
 
    
bshuman
User
Posts: 24
Joined: 14-Dec-2004
# Posted on: 14-Dec-2004 16:59:07   

I am trying to use the ConcurrencyPredicateFactoryToUse feature. I fetch a row from the database (SQLServer 2000) and display the data. When the user clicks on Save I need to create the object again to do the save. If I persist the entire object in a Session var it works fine. I have a TimeStamp column named "Version". Here is what I am trying to do in the Save event code:

org = new InClinixV3BL.Organization(); //inherits from OrganizationEntity org.Fields["OrgID"].ForcedCurrentValueWrite(ViewState["OrgID"] as int); org.Fields["Version"].ForcedCurrentValueWrite(ViewState["Version"] as byte[]); org.IsNew = false;

org.OrgName = txtOrgName.Text; org.AutoAccrual = rblAccrualMethod.SelectedValue;

org.Save();

I get this error: An exception was caught during the execution of an action query: Prepared statement '(@OrgName varchar(50),@AutoAccrual char(1),@HideNames char(1),@O' expects parameter @Version2, which was not supplied.

How do I supply the Version column value to compare against if I have created a new entity before the save? Thanks.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 14-Dec-2004 17:24:32   

A timestamp column is not send to the server as SqlServer inserts a value itself in a timestamp column.

So you can simply avoid setting the Version column to a value, as that will be overwritten by SqlServer anyway.

Frans Bouma | Lead developer LLBLGen Pro
bshuman
User
Posts: 24
Joined: 14-Dec-2004
# Posted on: 14-Dec-2004 18:23:53   

Thanks for the quick reply. I'm not trying to set the Version (TimeStamp) column. I'm just trying to provide the value for the ConcurencyPredicateFactory to use when comparing to the database value. How do I do that without persisting the object (org in this example) in a session var?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 14-Dec-2004 18:39:30   

bshuman wrote:

Thanks for the quick reply. I'm not trying to set the Version (TimeStamp) column. I'm just trying to provide the value for the ConcurencyPredicateFactory to use when comparing to the database value. How do I do that without persisting the object (org in this example) in a session var?

Hmm, indeed, the forcedcurrentvaluewrite should not set the changed flags.

What exactly do you do in the ConcurrentPredicateFactory? It still is weird though, as the '2' suggests it's a variable added by a predicate but that should thus be present.

Frans Bouma | Lead developer LLBLGen Pro
joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 15-Dec-2004 16:10:14   

I have exactly this same problem - If I put the object in the session the concurrency control works fine with the TimeStamp, but when creating a new object with the same TimeStamp the concurrency check fails. Is there something else in the original object required for this check?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 15-Dec-2004 17:07:34   

joer wrote:

I have exactly this same problem - If I put the object in the session the concurrency control works fine with the TimeStamp, but when creating a new object with the same TimeStamp the concurrency check fails. Is there something else in the original object required for this check?

WHen you save the new object, do you plug in the ConcurrencyPredicateFactory object in the new object as well?

Frans Bouma | Lead developer LLBLGen Pro
joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 15-Dec-2004 19:39:35   

yes - it is a new instance of a business object - passed to the same save method in my business layer where I add the PredicateExpression for the concurrency.

bshuman
User
Posts: 24
Joined: 14-Dec-2004
# Posted on: 15-Dec-2004 22:05:50   

Sorry I couldn't get back to the thread right away. Here is what I am doing in my application.

I have a BL class defined as follows (partial code):


[ Serializable ]
public class Organization : OrganizationEntity
{
    public Organization() : base(new PropertyDescriptorFactory(), new OrganizationEntityFactory())
    {
      this.ConcurrencyPredicateFactoryToUse = new OrganizationConcurrencyFilterFactory();
    }

The ConcurrencyPredicateFactoryToUse is set each time it's instaniatied.

In my web form I have the following:


private void pbSave_Click(object sender, EventArgs e)
{
    try
    {
        //Create a new org object 
        org = new InClinixBL.Organization();
        
        //Set key fields and IsNew flag
        orgID = (int)ViewState["OrgID"];
        
        org.Fields["OrgID"].ForcedCurrentValueWrite(orgID);
        org.Fields["Version"].ForcedCurrentValueWrite(ViewState["Version"] as byte[]);
        org.IsNew = false;
        
        //Unload data fields into org object
        org.OrgName = txtOrgName.Text;
        org.AutoAccrual = rblAccrualMethod.SelectedValue;
        org.HideNames = rblHideNames.SelectedValue;
        
        //Save to database
        if (org.Save())
        {
            MessageBox1.Text = "Organization was saved.";
        }
        else
        {
            MessageBox1.Text = "Organization was NOT saved.";
        }
    }
    catch (Exception ex)
    {
        Util.PublishError(ex, "Error saving Organization", true, true);
    }
}

I get the following error everytime I save. I have not gotten to the point of testing when another session has modified the row.

12/15/2004 4:01:55 PM (bshuman2: ImClone1) An exception was caught during the execution of an action query: Prepared statement '(@OrgName varchar(50),@AutoAccrual char(1),@HideNames char(1),@O' expects parameter @Version2, which was not supplied.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception. Stack Trace: at SD.LLBLGen.Pro.ORMSupportClasses.ActionQuery.Execute() at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.ExecuteActionQuery(IActionQuery queryToExecute, ITransaction containingTransaction) at InClinixDAL.DaoClasses.OrganizationDAO.CreateAndRunUpdateQuery(IEntityFields fields, ITransaction containingTransaction, IPredicate updateFilter) in C:\Vault\InClinix v3\Code\Application\InClinixDAL\DaoClasses\OrganizationDAO.cs:line 768 at InClinixDAL.DaoClasses.OrganizationDAO.UpdateOrganization(IEntityFields fields, ITransaction containingTransaction, IPredicate updateRestriction) in C:\Vault\InClinix v3\Code\Application\InClinixDAL\DaoClasses\OrganizationDAO.cs:line 127 at InClinixDAL.EntityClasses.OrganizationEntityBase.UpdateEntity(IPredicate updateRestriction) in C:\Vault\InClinix v3\Code\Application\InClinixDAL\EntityBaseClasses\OrganizationEntityBase.cs:line 2162 at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.Save(IPredicate updateRestriction, Boolean recurse) at InClinixDAL.EntityClasses.OrganizationEntityBase.Save(IPredicate updateRestriction, Boolean recurse) in C:\Vault\InClinix v3\Code\Application\InClinixDAL\EntityBaseClasses\OrganizationEntityBase.cs:line 352 at InClinixBL.Organization.Save(IPredicate updateRestriction, Boolean recurse) in c:\vault\inclinix v3\code\application\inclinixbl\organization.cs:line 115 at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.Save() at InClinix.Organization.pbSave_Click(Object sender, EventArgs e) in c:\vault\inclinix v3\code\application\demo\inclinixv3demo\organization.aspx.cs:line 155

Sorry for the long post. Thanks for looking at this.

-Bill Shuman

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 16-Dec-2004 10:16:51   

joer wrote:

yes - it is a new instance of a business object - passed to the same save method in my business layer where I add the PredicateExpression for the concurrency.

Sorry for asking the same question twice, but if you don't set the ConcurrencyPredicateFactoryToUse property also in the NEW instance, you don't get the predicate at save time. That's what I was wondering, if you actually do that, because I couldn't determine that from your remark wink (and bshuman's code also shows he doesn't do that)

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 16-Dec-2004 10:22:31   

bshuman wrote:

Sorry I couldn't get back to the thread right away. Here is what I am doing in my application.

I have a BL class defined as follows (partial code):


[ Serializable ]
public class Organization : OrganizationEntity
{
    public Organization() : base(new PropertyDescriptorFactory(), new OrganizationEntityFactory())
    {
      this.ConcurrencyPredicateFactoryToUse = new OrganizationConcurrencyFilterFactory();
    }

The ConcurrencyPredicateFactoryToUse is set each time it's instaniatied.

In my web form I have the following:


private void pbSave_Click(object sender, EventArgs e)
{
    try
    {
        //Create a new org object 
        org = new InClinixBL.Organization();
        
        //Set key fields and IsNew flag
        orgID = (int)ViewState["OrgID"];
        
        org.Fields["OrgID"].ForcedCurrentValueWrite(orgID);
        org.Fields["Version"].ForcedCurrentValueWrite(ViewState["Version"] as byte[]);
        org.IsNew = false;
        
        //Unload data fields into org object
        org.OrgName = txtOrgName.Text;
        org.AutoAccrual = rblAccrualMethod.SelectedValue;
        org.HideNames = rblHideNames.SelectedValue;
        
        //Save to database
        if (org.Save())
        {
            MessageBox1.Text = "Organization was saved.";
        }
        else
        {
            MessageBox1.Text = "Organization was NOT saved.";
        }
    }
    catch (Exception ex)
    {
        Util.PublishError(ex, "Error saving Organization", true, true);
    }
}

Code looks ok, I was a bit worried if you'd set the factory again, but you do, so that should be ok. I'll assume you simply add a Predicate that compares version values, so I'll try to set up a repro case here to see what happens.

Frans Bouma | Lead developer LLBLGen Pro
joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 16-Dec-2004 11:52:44   

Hi - just to answer the question I am setting the ConcurrencyPredicateFactoryToUse in the new instance also - the problem is that in the new instance the concurrency check is throwing an exception when it shouldn't, but with the old object stored in the session no exception occurs.

joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 16-Dec-2004 12:47:59   

OK I think I may have hit on the problem, seems like a dbValue property in the original object must be used somehow in the concurrency check.

If I set both the primary key and the property I am using for my concurrency check (last mod time for me) using this method:

org.Fields["UPDATE_DT"].ForcedCurrentValueWrite(dt,dt);

then I set the other property also, and the concurrency check works OK

Obviously its a bit ugly to have to use this method, which I presume was only intended for internal use by the framework, but for now I cant see what else I can do! I suppose I can wrap this in a method in the entities. Any idea's let me know!

Joe.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 16-Dec-2004 13:07:25   

the dbvalue is used to be able to update PK values. As you're tricking the framework not to having a new entity (which means dbvalue is set) you should indeed set the dbvalue, but I don't see why dbvalue of the timestamp column should be set.

I'll check it out if there is an error in teh code in this scenario. It should properly determine which value to use for the PK filter, but that's not the issue here. (as the value for the timestamp apparently gives an exception)

Now, if anyone of you also could post the predicate construction code in the concurrentpredicatefactory class, it would be great simple_smile

Frans Bouma | Lead developer LLBLGen Pro
bshuman
User
Posts: 24
Joined: 14-Dec-2004
# Posted on: 16-Dec-2004 13:29:38   

Here's the code for my ConcurencyFilterFactory:

    public class OrganizationConcurrencyFilterFactory : IConcurrencyPredicateFactory
    {
        public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
        {
            IPredicateExpression toReturn = new PredicateExpression();
            OrganizationEntity org = (OrganizationEntity) containingEntity;

            switch (predicateTypeToCreate)
            {
                case ConcurrencyPredicateType.Delete:
                    toReturn.Add(PredicateFactory.CompareValue(OrganizationFieldIndex.Version, ComparisonOperator.Equal, org.Fields[(int) OrganizationFieldIndex.Version].DbValue));
                    break;
                case ConcurrencyPredicateType.Save:
                    // only for updates
                    toReturn.Add(PredicateFactory.CompareValue(OrganizationFieldIndex.Version, ComparisonOperator.Equal, org.Fields[(int) OrganizationFieldIndex.Version].DbValue));
                    break;
            }
            return toReturn;
        }
    }
joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 16-Dec-2004 13:36:40   

Sorry, I see the error of my ways... my factory method was this

public static IPredicateExpression getConcurrencyExpression(EntityBase2 entity){ IPredicateExpression expr= new PredicateExpression(); if(entity is ORGEntity){ expr.Add(PredicateFactory.CompareValue ORGFieldIndex.UPDATE_DT, ComparisonOperator.Equal, entity.Fields[(int)ORGFieldIndex.UPDATE_DT].DbValue)); } return expr; }

I've changed DbValue to CurrentValue and now all is well

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 16-Dec-2004 13:38:56   

Yes, in the predicate construction, don't use dbvalue, only for fields which are modified. A timestamp field is not modified, so you should use CurrentValue.

bshuman: you don't set dbvalue, as you call the single parameter ForcedCurrentValueWrite() version, so you should use CurrentValue in your ConcurrencyPredicateFactory class, instead of dbvalue.

Frans Bouma | Lead developer LLBLGen Pro
joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 16-Dec-2004 13:42:36   

disregard the last mail, but have a look at the code in my factory please. Changing DbValue to CurrentValue means the Concurrency Exception is now never thrown at all even when it should be.

So as it stands I am leaving the factory but setting the UPDATE_DT with:

org.Fields["UPDATE_DT"].ForcedCurrentValueWrite(dt,dt)

Seems like this is the only solution to me at the moment.

bshuman
User
Posts: 24
Joined: 14-Dec-2004
# Posted on: 16-Dec-2004 13:45:24   

Joer is correct. I changed my Save code where I set the PK and Version values to read as follows:

org.Fields["OrgID"].ForcedCurrentValueWrite(orgID);
org.Fields["Version"].ForcedCurrentValueWrite(ViewState["Version"] as byte[],ViewState["Version"] as byte[]);

It then worked fine.

joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 16-Dec-2004 13:48:51   

I was indeed correct - using CurrentValue works fine - disregard my last mail which was the result of inaccurate testing. Everything looks OK now!

joer
User
Posts: 26
Joined: 15-Dec-2004
# Posted on: 16-Dec-2004 13:57:21   

To recap, as I think I have confused everybody by now wink doing what you are there bshuman works fine, but a more elegant solution is to set the check in the expression produced by the factory to compare with CurrentValue instead of DbValue. This is what I'm going to stick with.

bshuman
User
Posts: 24
Joined: 14-Dec-2004
# Posted on: 16-Dec-2004 14:53:36   

Thanks Otis and joer for all your help. This is an absolutely outstanding product!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 16-Dec-2004 14:57:54   

Thanks joer for the feedback simple_smile

To explain it a little bit: First there was just CurrentValue. This was ok for timestamp concurrency, but if you want to do optimistic concurrency like "Update field Foo only if field Y still has value 3" then you had a problem if Y was changed in the entity, as you couldn't retrieve the value it had when the entity was initially read from the DB (3). DbValue was added to store that value. It means it doesn't have a value (undefined) when the entity is new, as the entity isn't read from the db. When the entity is read from the db, the value of the field in the db is stored in DbValue and is NEVER changed afterwards. So if you want to test against the value a field had in the db when the field was read from the db, test with DbValue, otherwise test with CurrentValue.

Your tests clearly target CurrentValue as you supply the value to test against yourself, so no need for DbValue. Also, with a non-new entity, CurrentValue only has a different value than DbValue when the field was NULL in the db (DbValue is null / Nothing) OR when CurrentValue was changed. With timestamp fields, they're never changed, so you can always test with currentvalue simple_smile

You're welcome bshuman simple_smile

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 17-Dec-2004 15:11:00   

Found all this very interesting, as I was previously pretty proud of myself for implementing concurrency in ASP.NET by caching the entitiy, so here apparently is a new trick of sticking PK and timestamp into viewstate and then reconstructing the entity if necessary. Very clever!

swallace
User
Posts: 648
Joined: 18-Aug-2003
# Posted on: 17-Dec-2004 19:16:42   

Clever! I like it.

simple_smile

swallace
User
Posts: 648
Joined: 18-Aug-2003
# Posted on: 17-Dec-2004 19:17:27   

...apparently I like it so much I posted it twice. Oh great webmaster, feel free to clean up my droppings...

1  /  2