IConcurrencyPredicateFactory

Posts   
1  /  2
 
    
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 23-Jun-2004 17:28:52   

Any guidelines for best implementing this for timestamp comparisons? Every one of my tables has a timestamp. Just create one implementation for each class and stick into into the entity file? (for example, CustomerEntity.cs), then allocate and assign to ConcurrencyPredicateFactoryToUse for that entity in its ctor? This seems to be the thrust of the help, but I want to make sure.

Fishy avatar
Fishy
User
Posts: 392
Joined: 15-Apr-2004
# Posted on: 23-Jun-2004 18:18:13   

Stick something like this in your Bl:


    Public Class FormCPF : Implements IConcurrencyPredicateFactory

        Public Function CreatePredicate(ByVal predicateTypeToCreate As SD.LLBLGen.Pro.ORMSupportClasses.ConcurrencyPredicateType, ByVal containingEntity As Object) As SD.LLBLGen.Pro.ORMSupportClasses.IPredicateExpression Implements SD.LLBLGen.Pro.ORMSupportClasses.IConcurrencyPredicateFactory.CreatePredicate

            Dim Form As FormEntity = CType(containingEntity, FormEntity)
            Dim ReturnPredicate As IPredicateExpression = New PredicateExpression

            Select Case predicateTypeToCreate
                Case ConcurrencyPredicateType.Delete
                Case ConcurrencyPredicateType.Save
                    ReturnPredicate.Add(PredicateFactory.CompareValue(LLBL.FormFieldIndex.Concurrency, _
                    ComparisonOperator.Equal, Form.Concurrency))
                    Return ReturnPredicate
            End Select

        End Function

    End Class


Put this in the PL:

     Private Sub grdForms_AfterRowUpdate(ByVal sender As Object, ByVal e As Infragistics.Win.UltraWinGrid.RowEventArgs) Handles grdForms.AfterRowUpdate

        Dim Row As Infragistics.Win.UltraWinGrid.UltraGridRow = grdForms.ActiveRow

        CType(Row.ListObject(), FormEntity).ConcurrencyPredicateFactoryToUse = New MyApplication.FormCPF

        Try
            _MyApplication.Save()

        Catch ex As SD.LLBLGen.Pro.ORMSupportClasses.ORMConcurrencyException
            If MessageBox.Show("Someone updated that entry.  Do you want to override the record?", "Override Data", MessageBoxButtons.YesNo) = DialogResult.Yes Then
                CType(Row.ListObject(), FormEntity).ConcurrencyPredicateFactoryToUse = Nothing
                _MyApplication.Save()
            Else
                grdForms.DataSource = _MyApplication.LoadForms(_ActiveApplication)
            End If

        Catch ex As Exception
            MessageBox.Show("Entry could not be saved.  Reloading data.")
            grdForms.DataSource = _MyApplication.LoadForms(_ActiveApplication)
            Call ReloadDropDown()
        End Try


    End Sub

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Jun-2004 18:25:34   

Thanks. It seems like it would be more efficient to just create this and assign it in the entity's constructor rather than every time I save, right? I was even thinking of modifying the code generator template to do this automatically when a table has a timestamp, although that's not anything I would attempt soon. simple_smile

I was reasoning my way through this approach to managing concurrency and something obvious struck me. In order for it to work, of course I must have the original timestamp value to compare to the new one in the database. The most obvious way to do this is to cache the object, right?. But this could create some new problems, such as management of cached objects. Let's say the user is on a company page. I cache a company entitity object. On postback, I set properties to user entries and save. (Cached object has old timestamp, so if record has been modified then desired concurrency exception gets thrown). Now user navigates to another page. I need to clean up my cached object.

If this was the extent of my application, no big deal, but having to clean up lots of objects in lots of pages starts to become a high price to pay for solving the concurrency issue. (I think somewhere in the docs Frans says outright not to cache objects).

Usually I cache just an ID in the session var. But on postback, if I retrieve the data using that ID, I will also retrieve a new timestamp value (if the record was changed). Then again, caching the entire object would just have saved me a roundtrip to the database server, too. But of course maybe user would like to see the new data! Etc., etc.

I realize there may be no one best answer, but I am looking for advice on how others have handled this. Do you cache objects to keep the old timestamp? Do you cache in session or application? Do you worry about cache cleanup? What is everybody else doing about this?

My last ASP.NET application was "last one wins". If possible, I would really like to have an easy and elegant solution to concurrency and not go back to this and act surprised when the customer calls me one day to complain about his changes disappearing.

Jim

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 24-Jun-2004 19:03:30   

You can't update a timestamp value, so an existing entity in memory has the timestamp value you've read from the db, you can rely on that in your concurrency predicate. You of course need a value to compare with, otherwise it would impossible to determine if an update is possible simple_smile

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Jun-2004 22:32:55   

Ok, I think I've got this solved. I'll implement a page-level cache within my session cache, in a way that I don't have to explicitely clean up objects (and so worry about all the many ways a user can leave my page), because the next page just cleans it up. I debugged this and it looks like it will work pretty well.


private void Page_Load(object sender, System.EventArgs e)
{
      Hashtable htPage = (Hashtable)Session["PageCache"];
      if (!IsPostBack)
      {
          // clear page cache of anything from prior page
          htPage.Clear();
          // Session["SelectedAddressTypeID"] will hold needed ID unless URL called directly
          if (Session["SelectedAddressTypeID"] != null)
          {
              int AddressTypeID = (int)Session["SelectedAddressTypeID"];
              dalAddressType = new AddressTypeEntity(AddressTypeID);
              this.AddressTypeID.Text =  dalAddressType.AddressTypeID.ToString();
              this.AddressType.Text = dalAddressType.AddressType;
              // add to cache for postback
              htPage.Add("AddressType", dalAddressType);
          }
      }
      else
      {
          // get cached address object
          // it could be null if this URL called directly, but why check?
          dalAddressType = (AddressTypeEntity)htPage["AddressType"];
      }
}

then when user saves of course I use cache object retrieved in page load:


private void btnSave_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
      if (dalAddressType != null)
      {
          try
          {
              // here we could compare property first maybe to avoid unnecessary update
              // or else we use that code hook Frans gave us
              // or else we don't care
              dalAddressType.AddressType = this.AddressType.Text;
              dalAddressType.Save();
          }
          catch (Exception ex)
          {
              this.ErrText.Text = ex.Message;
              return;
          }
      }
      // go to next page or whatever...
}

For this project at least, this gives me a little peformance boost and takes care of concurrency problem (keeping old timestamp around). If it causes problems...I'll cross that bridge when I come to it. frowning

Now I have questions about exceptions, but I'll start a new thread for that.

Jim

Uh, golly gee, it would be nice if the code tag would preserve indenting!!

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Jun-2004 23:51:46   

Hey, I've got a weird one now.

I've verified that I've properly set up the predicate for the timestamp field. Now I fetch the record, cache the entitity, switch over to something else and modify the record in SQL Server and verify the timestamp has changed, and go back to the page to save the record.

Save() does not save the record. HOWEVER, it throws no exception!!! I was expecting ORMConcurrencyException but I got nothing! (It does return false, though). If the timestamp hasn't changed then it saves the record fine (and it refreshes the timestamp value, very good).

I look in the reference manual and it says Save() throws ORMQueryExecutionException. It seems a bit schizophrenic for Save() to sometimes throw an exception, but otherwise I have to also check for return value. Aren't exceptions the way to go now? In any event, how does ORMConcurrencyException get thrown? Or if I have to check for return value of Save(), how do I know concurrency was the problem? How do I know what the problem was at all?

Jim

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Jun-2004 23:57:48   

Since every one of my tables has a timestamp, I want to do this as part of my BL or just modify the entity classes. What's the easiest way to do this? I have a class called AddressTypeEntity. I put this in the constuctor I was calling for testing purposes.

        this.ConcurrencyPredicateFactoryToUse = new AddressTypeFilterFactory();

Of course, there are five constructors. And many entities. For each entity I have to derive a class from IConcurrencyPredicateFactory when actually all the timestamp columns in the database have the same name.

While I quite understand the value of IConcurrencyPredicateFactory, it seems that concurrency by timestamp is such a common issue, and LLBLGen Pro already knows my tables have timestamps...I should just be able to set a switch to off or on to get this functionality, and only go doing this stuff for special situations. disappointed

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 25-Jun-2004 09:56:00   

JimFoye wrote:

For this project at least, this gives me a little peformance boost and takes care of concurrency problem (keeping old timestamp around). If it causes problems...I'll cross that bridge when I come to it. frowning Now I have questions about exceptions, but I'll start a new thread for that. Jim

Uh, golly gee, it would be nice if the code tag would preserve indenting!!

It does, but your editor settings seem to use spaces instead of tabs simple_smile the code tag transfers every tab to 4   chars.

JimFoye wrote:

Hey, I've got a weird one now. I've verified that I've properly set up the predicate for the timestamp field. Now I fetch the record, cache the entitity, switch over to something else and modify the record in SQL Server and verify the timestamp has changed, and go back to the page to save the record. Save() does not save the record. HOWEVER, it throws no exception!!! I was expecting ORMConcurrencyException but I got nothing! (It does return false, though). If the timestamp hasn't changed then it saves the record fine (and it refreshes the timestamp value, very good).

This is because of backwards compatibility. Initially there was just Save() returning true and false. IConcurrencyPredicate was added later as well as the exception throwing. Exceptions are thrown during a recursive save though.

I look in the reference manual and it says Save() throws ORMQueryExecutionException. It seems a bit schizophrenic for Save() to sometimes throw an exception, but otherwise I have to also check for return value. Aren't exceptions the way to go now? In any event, how does ORMConcurrencyException get thrown? Or if I have to check for return value of Save(), how do I know concurrency was the problem? How do I know what the problem was at all? Jim

If a save fails because of an error, you will always get an exception. If the save fails because of a predicate restriction, the save will just fail and return false if the save wasn't a recursive save and will throw the concurrency exception if the save was a recursive save. By default selfservicing doesn't do recursive saves (again also for backwards compatibility), you have to explicitly specify 'true' for recurse.

Exceptions are not always what you want. The reason for that is that when an exception is thrown, the control moves to the catching catch clause. If you're in a sequence of events and the save failure isn't fatal (!) you can't go back, there is no resume statement. Due to its transactional behavior, recursive saves do throw an exception when a save fails, because it is an atomic action.

So in short: if you simply call Save(), you will return true or false, based on if the row was updated/saved or not. You'll get an exception on a database error (syntax error, FK violation etc. etc.).

JimFoye wrote:

Since every one of my tables has a timestamp, I want to do this as part of my BL or just modify the entity classes. What's the easiest way to do this? I have a class called AddressTypeEntity. I put this in the constuctor I was calling for testing purposes.

        this.ConcurrencyPredicateFactoryToUse = new AddressTypeFilterFactory();

Of course, there are five constructors. And many entities. For each entity I have to derive a class from IConcurrencyPredicateFactory when actually all the timestamp columns in the database have the same name.

You don't have to. simple_smile You can use the same predicate factory for all entities. You'll receive an 'object' which is the entity you have to create a predicate for. Cast that object to EntityBase:

EntityBase entity = (EntityBase)containingEntity;

'entity' will have a Fields collection. You have to create a FieldCompareValue() predicate instance in a PredicateExpression. Name your timestamp fields always the same, like ConcurrencyTimeStamp. Then do the following:

IPredicateExpression toReturn = new PredicateExpression(); toReturn.Add(new FieldCompareValue(entity.Fields["ConcurrencyTimeStamp"], ComparisonOperator.Equals));

and return toReturn from your ConcurrencyPredicateFactory. You can now use the same concurrencypredicatefactory class for all your entities with a timestamp column named ConcurrencyTimeStamp.

While I quite understand the value of IConcurrencyPredicateFactory, it seems that concurrency by timestamp is such a common issue, and LLBLGen Pro already knows my tables have timestamps...I should just be able to set a switch to off or on to get this functionality, and only go doing this stuff for special situations. disappointed

I understand your point simple_smile The problem however is that for one person it is obvious that it is always the same, and for others it isn't. Others want timestamp concurrency implemented differently perhaps. Having a 'turn key' solution inside the lib sounds great at first but it will be impractical for a lot of people. that's why it is set up flexible so you can add your own checks with a few lines of code simple_smile .

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 25-Jun-2004 15:46:10   

Thanks for the detailed response, Frans. I think it would be good for you to update your documentation to clarify about when/why Save() throws exceptions vs. returning false. wink Also would be good to put sample code to use same IConcurrencyPredicateFactory in multiple classes!

I was thinking about the concurrency problem some more last night (can never turn the brain off, unfortunately). I should be able to override Save() and then when base Save() returns false, I could create a new entitiy with the new values from the db and put that in a custom property. This would allow P/L to do something really nifty, like dump both sets of values to another page and ask user what he wants to do. But there are five Save()'s. I must really think about how much work I want to do here. Anyway...

Do you have any comments on my thread I started specifically about exceptions?

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 25-Jun-2004 15:49:38   

It does, but your editor settings seem to use spaces instead of tabs the code tag transfers every tab to 4   chars.

Ah, this threw me for a second, I thought you meant the editor in the browser! But of course I copied this code over, and yes I use spaces instead of tabs. This goes back to my early C days when I would look at someone else's file created in a different editor with different setting for tabs, and the indentation was all messed up. rage Ever since I use spaces instead of tabs in my code files. smile

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 25-Jun-2004 17:19:53   

Frans, a thousand apologies!! I just reread the description of ORMConcurrencyException and that information is in there. frowning Sorry confused

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 25-Jun-2004 18:11:49   

JimFoye wrote:

Thanks for the detailed response, Frans. I think it would be good for you to update your documentation to clarify about when/why Save() throws exceptions vs. returning false. wink Also would be good to put sample code to use same IConcurrencyPredicateFactory in multiple classes!

Well, there is always a thin line between what is useful in a documentation and what is re-explaining what is already said/obvious. As you can't document every detail, there is always something not documented. That's also why we have this forum, so people can ask questions about detailed issues in a given special context for example and an answer is given, which is then again searchable for others simple_smile It's a trade-off.

I was thinking about the concurrency problem some more last night (can never turn the brain off, unfortunately). I should be able to override Save() and then when base Save() returns false, I could create a new entitiy with the new values from the db and put that in a custom property. This would allow P/L to do something really nifty, like dump both sets of values to another page and ask user what he wants to do. But there are five Save()'s. I must really think about how much work I want to do here. Anyway...

You can override the virtual one, because execution will ALWAYS end up there. It's the .NET best practise: you have say 4 overloads, and 3 of them call the 4th with different parameters. You then make that 4th one virtual. By overriding that one, you will always make sure that all 4 methods use your code in the overriding method simple_smile

In the upcoming runtime lib upgrades, original values read from the db will be kept, so you can use these too for concurrency mechanisms, if you want 'merging' facilities on records for example.

JimFoye wrote:

It does, but your editor settings seem to use spaces instead of tabs the code tag transfers every tab to 4   chars.

Ah, this threw me for a second, I thought you meant the editor in the browser! But of course I copied this code over, and yes I use spaces instead of tabs. This goes back to my early C days when I would look at someone else's file created in a different editor with different setting for tabs, and the indentation was all messed up. rage Ever since I use spaces instead of tabs in my code files. smile

Heh, so true simple_smile

JimFoye wrote:

Frans, a thousand apologies!! I just reread the description of ORMConcurrencyException and that information is in there. frowning Sorry confused

Oh no problem simple_smile I misread stuff on a daily basis, that's human simple_smile

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 25-Jun-2004 18:45:37   

You can override the virtual one, because execution will ALWAYS end up there. It's the .NET best practise: you have say 4 overloads, and 3 of them call the 4th with different parameters. You then make that 4th one virtual. By overriding that one, you will always make sure that all 4 methods use your code in the overriding method

Good tip, thanks. I think we can consider thread closed. sunglasses

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 30-Jun-2004 01:05:33   

Whoops!

Frans, I am part way there with your suggestion. Here is my generic class derived from IConcurrencyPredicateFactory:


        public class GenericFilterFactory : IConcurrencyPredicateFactory
        {
            public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
            {
                IPredicateExpression toReturn = new PredicateExpression();
                EntityBase entity = (EntityBase)containingEntity;

                switch (predicateTypeToCreate)
                {
                    case ConcurrencyPredicateType.Delete:
                    case ConcurrencyPredicateType.Save:
                        toReturn.Add(new FieldCompareValue(entity.Fields["RV"], ComparisonOperator.Equals));
                        break;
                }
                return toReturn;
            }
        }   

So far so good, and it's very easy for you to read with everything slammed up against the left margin stuck_out_tongue_winking_eye . It may seem obvious what to do now, but what exactly is the best way to instantiate exactly one instance of this class, and then assign it to ConcurrencyPredicateFactoryToUse member of every entitity I instantiate? That's where I'm a bit lost confused I do not want to do this in my aspx pages. I want to do it as part of my DAL (which is also my BL, for this small project, anyway).

Jim

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 30-Jun-2004 09:27:18   

If you just want 1 instance, create a static member in a class and a static constructor creating the instance. The static member will then contain the instance and every read of that member will grab the same instance.

It's indeed a good question what to do to make it easy to set this instance in all new instances without hassle. The 'easiest' way would be to alter the entityFactories.template file, and grab the static instance there. So you should do something like:

instead of:


public virtual IEntity Create()
{
    return new <[CurrentEntityName]>Entity(new PropertyDescriptorFactory(), this);
}

you do:


public virtual IEntity Create()
{
    IEntity toReturn = new <[CurrentEntityName]>Entity(new PropertyDescriptorFactory(), this);
    toReturn.ConcurrencyPredicateFactoryToUse = MyStaticsClass.SingletonConcurrencyFactoryInstance;
    return toReturn;
}

I've added to the todo to make the code inside the Create() method an included template so it is very easy to alter that one, as it can be very important.

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 30-Jun-2004 23:03:12   

Otis wrote:

It's indeed a good question what to do to make it easy to set this instance in all new instances without hassle. The 'easiest' way would be to alter the entityFactories.template file, and grab the static instance there. So you should do something like:

I had poked around the generated files before and saw the Create() functions in EntityFactories.cs and wondered if this might be what I'm looking for. I will go with this. I understand that anytime I generate code after a schema change I will overwrite this file, but I'll have to live with that. If I understand correctly, if I modify a template in the SDK I will have the same dilemma.

In any event, I took a few minutes to look at the SDK and this is way, way beyond what I have time to learn in order to accomplish this task. I'm intrigued by it, but for today I'm just trying to implement concurrency via timestamps. It's one of the main reasons I bought the product!

I know you said earlier that different folks have different schemes for concurrency, but using timestamps certainly has to be one of the more common ones. This is just too much work to get this "feature" of the product to work. My two cents. simple_smile

Here's what I've got. I expected this to work, and it almost does, but something's wrong. I have a static class, and I've verified that the constructor is only called once in the program. It intializes a single instance of my IConcurrencyPredicateFactory-derived class. (BTW, the code you originally gave me didn't compile, I was echoing that code in my previous post, therefore here it's corrected. Look at it carefully, maybe I created a problem when "fixing" it).

Then I modified my AddressTypeEntityFactory constructor to assign the single instance of the GlobalConcurrencyFilterFactory class. This seems to be happening ok. (Although, just an aside, when I go to inspect things in the immediate window, I get a lot of 'xxx.ToString' does not exist messages, which I never seemed to get in VB. NET. confused So I can step through the assignment, but I can't inspect toReturn.ConcurrencyPredicateFactoryToUse in the immediate window).

When my page attempts to update the cached object, whose rowversion has since changed in the database, the update succeeds! This is in contrast to my earlier test where I created a class specifically for this entity, etc.

I must be doing something really obviously wrong here. Please help.


namespace OEL.FactoryClasses
{
    
    public class GlobalConcurrencyFilterFactory : IConcurrencyPredicateFactory
    {
        public IPredicateExpression CreatePredicate(ConcurrencyPredicateType predicateTypeToCreate, object containingEntity)
        {
            IPredicateExpression toReturn = new PredicateExpression();
            EntityBase entity = (EntityBase)containingEntity;

            switch (predicateTypeToCreate)
            {
                case ConcurrencyPredicateType.Delete:
                case ConcurrencyPredicateType.Save:
                    toReturn.Add(new FieldCompareValuePredicate(entity.Fields["RV"], ComparisonOperator.Equal));
                    break;
            }
            return toReturn;
        }
    }   
    
    class StaticConcurrencyFilterFactory
    {
        static public GlobalConcurrencyFilterFactory SingletonConcurrencyFactoryInstance;
        static StaticConcurrencyFilterFactory()
        {
            SingletonConcurrencyFactoryInstance = new GlobalConcurrencyFilterFactory();
        }
    }
    
    /// <summary>
    /// Factory to create new, empty AddressTypeEntity objects.
    /// </summary>
    [Serializable]
    public class AddressTypeEntityFactory : IEntityFactory
    {
        /// <summary>
        /// Creates a new, empty AddressTypeEntity object.
        /// </summary>
        /// <returns>A new, empty AddressTypeEntity object.</returns>
        public virtual IEntity Create()
        {
            IEntity toReturn = new AddressTypeEntity(new PropertyDescriptorFactory(), this);
            toReturn.ConcurrencyPredicateFactoryToUse = StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance;
            return toReturn;
        }

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 02-Jul-2004 14:12:09   

Frans, do you have any more suggestions? If not, I guess I will just do something simple, like override all the constructors and allocate and assign every time an entity is created (ugh).

Jim

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 03-Jul-2004 14:09:37   

Does this line: toReturn.Add(new FieldCompareValuePredicate(entity.Fields["RV"], ComparisonOperator.Equal));

ever get called?

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 04-Jul-2004 00:05:17   

Good question! That was the one place I didn't set a breakpoint. No, it's not being called.

That caused me to pay a little closer attention in the debugger. On the page calling mine an AddressTypeCollection with 4 objects is getting instantiated, and so I was seeing the AddressTypeEntityFactory::Create() being called 4 times. (That line of code is

            AddressTypeCollection dalAddressType = new AddressTypeCollection();

)

But...when I instantiate my single AddressTypeEntity like this

                     dalAddressType = new AddressTypeEntity(AddressTypeID);

then AddressTypeEntityFactory::Create() is not being called. So obviously ConcurrencyPredicateFactoryToUse is not being set. So this is not the hook we need?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 04-Jul-2004 09:50:04   

I think we now have to have a closer look at how you are creating your singleton object, and to the code which sets the actual factory objects in the entities. If you set breakpoints on that last piece of code, in the entity factories, does it have a value there?

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 05-Jul-2004 18:00:10   

Well, remember, for my single object instantiation, Create() is apparently not being called at all. Are you asking me about when I instantiate the collection?

When I instantiate the collection of 4 entitites, Create() gets called 4 times. The first time I assign StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance to ConcurrencyPredicateFactoryToUse, the static constructor of StaticConcurrencyFilterFactory fires and I construct my only GlobalConcurrencyFilterFactory object


    class StaticConcurrencyFilterFactory
    {
        static public GlobalConcurrencyFilterFactory SingletonConcurrencyFactoryInstance;
        static StaticConcurrencyFilterFactory()
        {
            SingletonConcurrencyFactoryInstance = new GlobalConcurrencyFilterFactory();
        }
    }

Now, it returns and within Create() this line finishes


            toReturn.ConcurrencyPredicateFactoryToUse = StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance;

Then, I try to inspect toReturn.ConcurrencyPredicateFactoryToUse in the immediate window (or watch window, same result). Now Frans, this is strange to me. My last ASP.NET project was in VB, and despite my years of C, some things about C# are still new to me.


?toReturn.ConcurrencyPredicateFactoryToUse
error: 'toReturn.ConcurrencyPredicateFactoryToUse' does not exist

Why would it not show either a valid object or else null? I don't understand this message, and I've looked around MSDN but with no luck.

Anyway, important thing is, above sequence is when my collection is instantiated. When I get to the next page and instantiate a single object for user to edit


dalAddressType = new AddressTypeEntity(AddressTypeID);

then AddressTypeEntityFactory::Create() is apparently not being called. I have a breakpoint there, but it's not being called. confused

Jim

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 07-Jul-2004 10:00:02   

JimFoye wrote:


            toReturn.ConcurrencyPredicateFactoryToUse = StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance;

Then, I try to inspect toReturn.ConcurrencyPredicateFactoryToUse in the immediate window (or watch window, same result). Now Frans, this is strange to me. My last ASP.NET project was in VB, and despite my years of C, some things about C# are still new to me.

Yeah, the immediate window is a fine thing in C#...


?toReturn.ConcurrencyPredicateFactoryToUse
error: 'toReturn.ConcurrencyPredicateFactoryToUse' does not exist

Do: ?((IConcurrencyPredicateFactory)toReturn).ConcurrencyPredicateFactoryToUse

Weird, I know, but it's one of the many bugs in vs.net...

Why would it not show either a valid object or else null? I don't understand this message, and I've looked around MSDN but with no luck.

Anyway, important thing is, above sequence is when my collection is instantiated. When I get to the next page and instantiate a single object for user to edit


dalAddressType = new AddressTypeEntity(AddressTypeID);

then AddressTypeEntityFactory::Create() is apparently not being called. I have a breakpoint there, but it's not being called. confused Jim

Huh? Hmm... Could you step into the constructor and see what happens? (I assume you're using self servicing, which does create an entityfactory for every entity instantiated)

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 07-Jul-2004 16:52:21   

Do: ?((IConcurrencyPredicateFactory)toReturn).ConcurrencyPredicateFactoryToUse

I did try this suggestion


?((IConcurrencyPredicateFactory)toReturn).ConcurrencyPredicateFactoryToUse
error: '(IConcurrencyPredicateFactory)toReturn.ConcurrencyPredicateFactoryToUse' does not exist

?

Huh? Hmm... Could you step into the constructor and see what happens? (I assume you're using self servicing, which does create an entityfactory for every entity instantiated)

Yes, I'm using self servicing. I stepped into the constructor. To refresh your memory, here is the line I'm stepping into:


dalAddressType = new AddressTypeEntity(AddressTypeID);

here is my hook


    public class AddressTypeEntityFactory : IEntityFactory
    {
        /// <summary>
        /// Creates a new, empty AddressTypeEntity object.
        /// </summary>
        /// <returns>A new, empty AddressTypeEntity object.</returns>
        public virtual IEntity Create()
        {
            IEntity toReturn = new AddressTypeEntity(new PropertyDescriptorFactory(), this);
            toReturn.ConcurrencyPredicateFactoryToUse = StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance;
            return toReturn;
        }

        #region Custom User Code

        #endregion
    }

Breakpoint not being fired. So when I step through the code, here's a dump of all the functions I visit.


        public AddressTypeEntity(System.Int32 addressTypeID):
            base(addressTypeID)
        {
        }


        public AddressTypeEntityBase(System.Int32 addressTypeID)
        {
            InitClassFetch(addressTypeID, new AddressTypeValidator(), new PropertyDescriptorFactory(), new AddressTypeEntityFactory());
        }


        public AddressTypeValidator()
        {
        }

        public PropertyDescriptorFactory()
        {
        }

        private void InitClassFetch(System.Int32 addressTypeID, AddressTypeValidator validator, IPropertyDescriptorFactory propertyDescriptorFactoryToUse, IEntityFactory entityFactoryToUse)
        {
            InitClassMembers(propertyDescriptorFactoryToUse, entityFactoryToUse);
            base.Fields = EntityFieldsFactory.CreateEntityFieldsObject(EntityType.AddressTypeEntity);
            bool wasSuccesful = Fetch(addressTypeID);
            base.IsNew = !wasSuccesful;
            base.Validator = validator;
            base.EntityFactoryToUse = entityFactoryToUse;
        }

        private void InitClassMembers(IPropertyDescriptorFactory propertyDescriptorFactoryToUse, IEntityFactory entityFactoryToUse)
        {
            _companyAddresses = new OEL.CollectionClasses.CompanyAddressCollection(propertyDescriptorFactoryToUse, new CompanyAddressEntityFactory());
            _companyAddresses.SetContainingEntityInfo(this, "AddressType");
            _alwaysFetchCompanyAddresses = false;
            _alreadyFetchedCompanyAddresses = false;
        }

        public CompanyAddressCollection(IPropertyDescriptorFactory propertyDescriptorFactoryToUse, IEntityFactory entityFactoryToUse)
            :base(propertyDescriptorFactoryToUse, typeof(CompanyAddressCollection), entityFactoryToUse)
        {
        }

        private bool Fetch(System.Int32 addressTypeID)
        {
            AddressTypeDAO dao = DAOFactory.CreateAddressTypeDAO();

            // Load EntityFields of AddressType
            dao.FetchAddressType(base.Fields, base.Transaction, addressTypeID);

            bool fetchResult = false;
            if(base.Fields.State == EntityState.Fetched)
            {
                base.IsNew = false;
                fetchResult = true;
            }

            return fetchResult;
        }

        public static AddressTypeDAO CreateAddressTypeDAO()
        {
            AddressTypeDAO valueToReturn = new AddressTypeDAO();
            valueToReturn.TypeDefaultValueSupplier = new TypeDefaultValue();
            return valueToReturn;
        }

        public AddressTypeDAO()
        {
        }

        public TypeDefaultValue()
        {
        }

        public IEntityFields FetchAddressType(IEntityFields fieldsToFetch, ITransaction containingTransaction, System.Int32 addressTypeID)
        {
            fieldsToFetch.State = EntityState.New;
            IPredicateExpression selectFilter = new PredicateExpression();
            selectFilter.Add(new FieldCompareValuePredicate(fieldsToFetch[(int)AddressTypeFieldIndex.AddressTypeID], ComparisonOperator.Equal, addressTypeID));
            CreateAndRunSingleInstanceRetrievalQuery(fieldsToFetch, containingTransaction, selectFilter);
            return fieldsToFetch;
        }

        private void CreateAndRunSingleInstanceRetrievalQuery(IEntityFields fieldsToReturn, ITransaction containingTransaction, IPredicate selectFilter)
        {
            IRetrievalQuery selectQuery = DynamicQueryEngine.CreateSelectDQ(fieldsToReturn, DbUtils.DetermineConnectionToUse(containingTransaction), selectFilter, 0, null, null);
            base.ExecuteSingleRowRetrievalQuery(selectQuery, containingTransaction, fieldsToReturn);
        }

        public static IDbConnection DetermineConnectionToUse(ITransaction containingTransaction)
        {
            if(containingTransaction!=null)
            {
                return containingTransaction.ConnectionToUse;
            }
            else
            {
                return CreateConnection();
            }
        }

        public static SqlConnection CreateConnection()
        {
            // read the connection string from the *.config file.
            AppSettingsReader configReader = new AppSettingsReader();
            string connectionString = configReader.GetValue(connectionKeyString, typeof(string)).ToString();

            return CreateConnection(connectionString);
        }

        public static SqlConnection CreateConnection(string connectionString)
        {
            return new SqlConnection(connectionString);
        }

(Sigh). No sign of Create() being called. EntityFactories.cs doesn't seem like a good choice to modify anyway. It gets overwritten when I regenerate.

Here's what I would like to have:


dalAddressType.ConcurrencyScheme = ConcurrencySchemes.RowVersion (or None, or whatever)
dalAddressType.ConcurrencyRowVersionField = AddressTypeFieldIndex.RV

or, better, create the hook inside the class so I can modify a function call in AddressTypeEntity.cs to set it. I really want to do this in my DAL, not any higher. I'm still a somewhat of a novice on this middle tier stuff, but even I can see that the presentation layer shouldn't be doing this.

And, make it so I can reuse a singleton, of course, across mulitple entity classes, since I'll likely name the timestamp field the same everywhere.

You're pretty brilliant (no sarcasm intended), so I know you get the idea.

Unless you've got another suggestion to my current predicament, I'm going to go ahead and override all the Save's (or constructors? not sure - expect another thread on this) and create these concurrency factories all over the place. (Second sigh).

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39848
Joined: 17-Aug-2003
# Posted on: 08-Jul-2004 12:49:28   

JimFoye wrote:

Do: ?((IConcurrencyPredicateFactory)toReturn).ConcurrencyPredicateFactoryToUse

I did try this suggestion


?((IConcurrencyPredicateFactory)toReturn).ConcurrencyPredicateFactoryToUse
error: '(IConcurrencyPredicateFactory)toReturn.ConcurrencyPredicateFactoryToUse' does not exist

?

Sorry, my mistake. I ran into the same crappyness yesterday. You have to do this: ?((_ConcurrencyPredicateFactoryClassName_)toReturn).ConcurrencyPredicateFactoryToUse

This is caused because the toReturn value is interface typed and immediate window gets confused because of that.

Huh? Hmm... Could you step into the constructor and see what happens? (I assume you're using self servicing, which does create an entityfactory for every entity instantiated)

Yes, I'm using self servicing. I stepped into the constructor. To refresh your memory, here is the line I'm stepping into:


dalAddressType = new AddressTypeEntity(AddressTypeID);

flushed ... again a mistake from my part. The factory is not used here of course, hte factory you altered is only used when a collection is created or this object is added to a collection and added to a grid.

So your specific code is not ending up in the constructor. I should have been more careful in my advice, sorry for that again.

After re-checking what you want to do, I think you have two options. Below the two options are described. The best way to do this is is via the custom include:

First option: defining a custom include This option creates a custom template to be included in the derived classes for each entity. The template overrides UpdateEntity, defined in the <entityName>EntityBase classes. This requires v1.0.2004.1 of the designer, as the template include functionality was added to that version. - create a new file in the Drivers\SqlServer\Templates\C# folder. Name this file for example jimEntityUsingEntityBaseInclude.template. - open this new file in a texteditor - add the following code to this file:


<[If HasPrimaryKey]>
    /// <summary>
    /// Performs the update action of an existing Entity to the persistent storage.
    /// </summary>
    /// <returns>true if succeeded, false otherwise</returns>
    protected override bool UpdateEntity()
    {
        // set your concurrencypredicatefactory here, like:
        // this.ConcurrencyPredicateFactoryToUse = myFactory;

        return base.UpdateEntity();
    }
    
    
    /// <summary>
    /// Performs the update action of an existing Entity to the persistent storage.
    /// </summary>
    /// <param name="updateRestriction">Predicate expression, meant for concurrency checks in an Update query</param>
    /// <returns>true if succeeded, false otherwise</returns>
    protected override bool UpdateEntity(IPredicate updateRestriction)
    {
        // set your concurrencypredicatefactory here, like:
        // this.ConcurrencyPredicateFactoryToUse = myFactory;

        return base.UpdateEntity(updateRestriction);
    }
<[EndIf]>

and adjust it to meet your requirements. - create a copy of the CSharpTemplateSet.config in the folder Drivers\SqlServer\Templates and give it a new name - open your copy of CSharpTemplateSet.config in a texteditor - change the name between the name tag so you can recognize your copy in the gui. - add a new binding: <templateBinding templateID="Custom_EntityUsingEntityBaseTemplate" templateFilename="C#\jimEntityUsingEntityBaseInclude.template" /> - save your copy of CSharpTemplateSet.config.

You're now able to generate code using this new template config and which will 'inject' the contents of jimEntityUsingEntityBaseInclude.template in every entity generated at the bottom in the Custom User Code region. Your custom template is preserved even when a new templateset is released.

Second option: altering the template - create a copy of the entityBase.template file and give it a new name (in the Drivers\SqlServer\Templates\C# folder) - create a copy of the CSharpTemplateSet.config in the folder Drivers\SqlServer\Templates and give it a new name - open your copy of entityBase.template in a texteditor and go to line 889 where InitClassMembers starts. - Add the code to set the ConcurrencyPredicate to that method. - save your copy of entityBase.template - open your copy of CSharpTemplateSet.config in a texteditor - change the name between the name tag so you can recognize your copy in the gui. - change the filename bound to ID SD_EntityBaseTemplate to the filename of your copy of entityBase.template - save your copy of CSharpTemplateSet.config

You can now generate code with your own template which will make all entities have the code which stores the concurrencypredicatefactory object internally. Forget the factory changes, you don't need them anymore.

This is a bit cumbersome, so I'll make all initialization methods contain include statements in the updated templates which will be released with the upcoming runtime libraries. This will then make sure that template updates will not overwrite your own initialization code.

You're pretty brilliant (no sarcasm intended), so I know you get the idea.

I make mistakes as well and I finally understood what you need, so far for brilliance on my side wink simple_smile (sorry again for these mistakes)

Unless you've got another suggestion to my current predicament, I'm going to go ahead and override all the Save's (or constructors? not sure - expect another thread on this) and create these concurrency factories all over the place. (Second sigh).

Adding the code to overrides of all the UpdateEntity is also an option, which I illustrated as the first option above. I think with the template include this is what you should do.

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 12-Jul-2004 23:52:45   

Rather than modify the template I thought I would start with a simple test. I overrode the 2 UpdateEntity() functions in AddressTypeEntity.cs (as if I had done the template stuff and generated). Here is the code:


        protected override bool UpdateEntity()
        {
            this.ConcurrencyPredicateFactoryToUse = StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance;
            return base.UpdateEntity();
        }

        protected override bool UpdateEntity(IPredicate updateRestriction)
        {
            this.ConcurrencyPredicateFactoryToUse = StaticConcurrencyFilterFactory.SingletonConcurrencyFactoryInstance;
            return base.UpdateEntity(updateRestriction);
        }

When I call Save(), the first UpdateEntity() gets called, and since it is the first time, the StaticConcurrencyFilterFactory constructor gets called. The debugger says this.ConcurrencyPredicateFactoryToUse is set to an instance of GlobalConcurrencyFilterFactory. However, the concurrency test fails. GlobalConcurrencyFilterFactory::CreatePredicate is not being called and so no comparision to the existing timestamp is done in the query. I must be missing something obvious here, right? Please tell me I'm close.

1  /  2