Transient Error Recovery - SelfService TwoClass v4.1

Posts   
 
    
Posts: 4
Joined: 16-May-2018
# Posted on: 16-May-2018 17:07:56   

I am curious what the best way to implement this would be my specific enviornment. We are using a Microsoft Sql Server Back end and generating our model in SelfService TwoClasses. We are running version 4.1 of LLBLGen.

After a brief amount of research it appears that llblgen only has a automatic version of transient error recovery for Adapter models. So my initial thought was to override the Save,Delete, GetMulti etc events within each entity / collection and write my own retry logic similar to what is below.

    
 public override bool Save(IPredicate updateRestriction, bool recurse)
        {
            bool attemptingSave = true;
            bool saveResult = false;
            int attempts = 0;
            while (attemptingSave)
            {
                try
                {
                    saveResult = base.Save(updateRestriction, recurse);
                    attemptingSave = false;
                }
                catch (System.Data.SqlClient.SqlException sqlEx)
                {

                }
                catch (Exception e)
                {
                    attempts++;
                    Console.WriteLine(attempts);
                    System.Threading.Thread.Sleep(5000);
                    if (attempts == 5)
                    {
                        attemptingSave = false;
                    }
                }
            }
            return saveResult;
        }

But after messing with the code for a bit i discovered the Save and Delete methods are the only ones i could override.

I would like to know if there is a better approach to handling this and or what would you recommend.

We really like the selfservice twoclasses model and would prefer not to have to switch the type since we just finished updating the entire application to run on this.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 17-May-2018 13:27:07   

For selfservicing there's no central object which governs the connection so there's no central place to put the strategy.

Instead of your code I'd really use the Execute method on a transient error recovery strategy object, e.g.

var q = from c in metaData.Customer
        where c.Country=="USA"
        select c;
var strategy = new SqlAzureRecoveryStrategy(); // use defaults
var l = strategy.Execute(()=>q.ToList());

or in your case:


var strategy = new SqlAzureRecoveryStrategy(); // use defaults
strategy.Execute(()=>myEntity.Save(true));

It's a bit cumbersome to cover all bases for selfservicing due to lazy loading and fetch logic in generated code, so we couldn't find a spot where we could easily obtain the set strategy without using locks, as it would require a static location to make all code be able to obtain the set strategy. A strategy has state so that would be problematic.

What you could do is use a unit of work as much as possible and commit that unit of work with a strategy using its Execute method. If you want to use the Save() methods etc. then I'd use a static method which creates a local strategy object and executes a lambda you pass in.

Be aware that a strategy has state, so you can't share it among calls!

e.g.


public static T PerformWithStrategy<T>(Func<T> toPerform)
{
    if(toPerform==null)
    {
        return;
    }
    return new SqlAzureRecoveryStrategy().Execute(toPerform);
}

where you call it like:

MyHelperMethods.PerformWithStrategy(()=>myEntity.Save(true));

It's not ideal though, but it's a way to get things more centralized and easy to perform within a strategy.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 4
Joined: 16-May-2018
# Posted on: 18-May-2018 18:55:34   

Thank you for this SqlAzureRecoveryStrategy() class i did not know this existed.

I have decided I will alter my custom template and so that LLBLGEN will add overides to each entity automatically when i generate the model like below.


     public override bool Save(IPredicate updateRestriction, bool recurse)
        {
            var strat = new SD.LLBLGen.Pro.ORMSupportClasses.SqlAzureRecoveryStrategy();
            return strat.Execute(() => { return base.Save(updateRestriction,recurse); });
        }
        public override bool Delete(IPredicate deleteRestriction)
        {
            var strat = new SD.LLBLGen.Pro.ORMSupportClasses.SqlAzureRecoveryStrategy();
            return strat.Execute(() => { return base.Delete(deleteRestriction); });
        }
        public override bool Refetch()
        {
            var strat = new SD.LLBLGen.Pro.ORMSupportClasses.SqlAzureRecoveryStrategy();
            return strat.Execute(() => { return base.Refetch(); });
        }

This will cover the basics, but i am curious about the rest of the situations specifically with collections GetMulti and the lazy loading. Single entity fetches, I might be missing them in the generated code.

I would prefer to not have to go through my entire code base to wrapped all my fetches if there is something i can override in the base methods.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 21-May-2018 11:55:48   

[quotenick="StormieTheCoder"]Thank you for this SqlAzureRecoveryStrategy() class i did not know this existed.

I have decided I will alter my custom template and so that LLBLGEN will add overides to each entity automatically when i generate the model like below.


     public override bool Save(IPredicate updateRestriction, bool recurse)
        {
            var strat = new SD.LLBLGen.Pro.ORMSupportClasses.SqlAzureRecoveryStrategy();
            return strat.Execute(() => { return base.Save(updateRestriction,recurse); });
        }
        public override bool Delete(IPredicate deleteRestriction)
        {
            var strat = new SD.LLBLGen.Pro.ORMSupportClasses.SqlAzureRecoveryStrategy();
            return strat.Execute(() => { return base.Delete(deleteRestriction); });
        }
        public override bool Refetch()
        {
            var strat = new SD.LLBLGen.Pro.ORMSupportClasses.SqlAzureRecoveryStrategy();
            return strat.Execute(() => { return base.Refetch(); });
        }

You could also add these to a partial class of CommonEntityBase, so you don't need to alter the templates wink

This will cover the basics, but i am curious about the rest of the situations specifically with collections GetMulti and the lazy loading. Single entity fetches, I might be missing them in the generated code.

I would prefer to not have to go through my entire code base to wrapped all my fetches if there is something i can override in the base methods.

yes I know what you mean, sadly we couldn't solve it in a way that made it work easily so we had to fall back on the 'execute things in a strategy if you need recovery' on selfservicing.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 4
Joined: 16-May-2018
# Posted on: 21-May-2018 16:04:55   

So if I wrap a get multi call in SqlAzureRecoveryStrategy() this method will protect the entire get multi? To include the lazy loads based on foreign keys it will perform?

Also are you saying there is a setting I can set so that my CommonEntityBase does not regenerate itself every time I build a model? Because right now I use a template to make sure that keeps my required code as well.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 21-May-2018 19:39:05   

Generated code files will always be generated and overwrite any previous version. What Otis was saying is to use another file (Partial class) for the CommonEntitybase.cs. So when the original file gets overwritten, the other file you created will not, and thus will be maintained across code generations.

Posts: 4
Joined: 16-May-2018
# Posted on: 21-May-2018 20:31:10   

ahh, I actually use partial classes for the normal Entity files I dont know why it didnt cross my mind to do it for CommonEntityBase as well.

ok that clears that up can you please comment on the getmulti part of my previous post.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 22-May-2018 10:27:55   

StormieTheCoder wrote:

So if I wrap a get multi call in SqlAzureRecoveryStrategy() this method will protect the entire get multi? To include the lazy loads based on foreign keys it will perform?

GetMulti never pulls in related elements through lazy loading, it will (if you specify a prefetch path) fetch related elements through eager loading, that's a difference simple_smile What a strategy does is really simple, it simply executes the lambda again in case of a transient error. So if you specify a prefetch path, it will fetch that prefetch path again too, as it simply re-executes the GetMulti in full, including the prefetch path.

the one thing that you need to cover is lazy loaded elements through navigator reads. E.g. if you do:

var myCustomer = myOrder.Customer; // will fetch the related Customer entity

it will issue a fetch for the customer entity, but that one is 'hidden' for you, and therefore not using a strategy.

In general we advice against using Lazy loading as it leads to less performing applications (as the risk of SELECT N+1 is always there), but if you must, I'd wrap these calls in strategy calls explicitly, which also forces you to think about when lazy loading occurs so you can decide whether you want to use lazy loading there (it can do less harm, e.g. SELECT N+1 isn't going to happen) or refactor it in an eager loading scenario instead.

Frans Bouma | Lead developer LLBLGen Pro