Default value(s)

Posts   
1  /  2
 
    
magic
User
Posts: 125
Joined: 24-Nov-2008
# Posted on: 18-Feb-2009 22:19:08   

I have just been wondering how to use the information of a default value defined on database level for a given field in forms. Since I couldn't find a property in the entity/field classes, I have started looking for an answer in the forum and found this thread: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7845

Is there any new development on this topic (the thread is over 2 years old)? If not, is there a way I can for example preselect the default value from a combobox or fill a text field with a default value as specified in the database?

DvK
User
Posts: 318
Joined: 22-Mar-2006
# Posted on: 18-Feb-2009 22:36:20   

You could use a Validator (using DI or set by code) to set default values on the insert (save)event of a new entity !

Regards, Danny

magic
User
Posts: 125
Joined: 24-Nov-2008
# Posted on: 18-Feb-2009 22:42:25   

DvK wrote:

You could use a Validator (using DI or set by code) to set default values on the insert (save)event of a new entity !

thanks, but ... could you please explain what you mean a bit more dummy-proof? wink I don't quite understand how setting the value to a default upon insert/save should help with displaying default field values upon showing the form (which obviously happens before the insert/save)?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 19-Feb-2009 06:40:26   

Quoting another thread (http://llblgen.com/TinyForum/Messages.aspx?ThreadID=6971&StartAtMessage=0&#38320):

The default values aren't stored in the meta-data as they're defined in the tables in the database. Storing them in the meta-data would mean a maintenance issue when defaults change in the db and not in the code, which might be overlooked. if you want to initialize your entity with default values in memory, you should do that in code. If you want to use the default values set by the DB, leave the fields as-is and save the entity, with refetch (adapter) (selfservicing will refetch for you).

So I think you should initialize the default value you want in code, for instance in the when the entity is initialized:

protected override void OnInitialized()
{
     this.SomeField = "some default value";
     base.OnInitialized();
}

The databinding stuff (combos, textbox, etc) should work automatically.

David Elizondo | LLBLGen Support Team
DvK
User
Posts: 318
Joined: 22-Mar-2006
# Posted on: 19-Feb-2009 10:41:52   

You're right, I didn't read your message that thorough...so I was thinking about setting defaults on insert. Sorry !

magic
User
Posts: 125
Joined: 24-Nov-2008
# Posted on: 19-Feb-2009 16:09:41   

daelmo wrote:

Quoting another thread (http://llblgen.com/TinyForum/Messages.aspx?ThreadID=6971&StartAtMessage=0&#38320):

The default values aren't stored in the meta-data as they're defined in the tables in the database. Storing them in the meta-data would mean a maintenance issue when defaults change in the db and not in the code, which might be overlooked. if you want to initialize your entity with default values in memory, you should do that in code. If you want to use the default values set by the DB, leave the fields as-is and save the entity, with refetch (adapter) (selfservicing will refetch for you).

So I think you should initialize the default value you want in code, for instance in the when the entity is initialized:

protected override void OnInitialized()
{
     this.SomeField = "some default value";
     base.OnInitialized();
}

The databinding stuff (combos, textbox, etc) should work automatically.

thank you for the info. I guess I have to accept that LGP does not offer this information by default. I personally will take a look at modifying the templates ...

I must say though that I do not understand the reasoning behind this: Why should a default value of a field cause a bigger maintenance issue than any other thing (modified field name, modified constraint, etc.)? I have to sync the LGP project with the DB anyway every time when I changed the DB structure.

I sort of understand the reasoning in a special case where the default value is a (time-sensitive) function (as explained by Otis in the thread quoted by you), but in case of a "simple value type" (string, int, etc.) the solution that you suggest causes much more sync/maintenance trouble.

In my specific case the field is a foreign key to another table and it has a default value, in particular "school". Now I can of course modify my source as you suggest, but when I change the default value to "club" in the db, I have to remember that I have to change the value in the code too. Does this really come in handy?

The other thing is that the field is NOT NULL. So you can't submit the form without making a choice on the value. At the current stage, I assume that 95% of our users will select "school" in this form. Why shouldn't I make their lives easier and preselect the value?

From my point of view, the designer/developer has to understand what he is working with. I don't see any disadvantage of having a property called "DefaultValue". If you want, you can use it, e.g. to set a pre-defined value in the form. If not (because it is a time-sensitive function or for any other reason), you just don't.

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 24-Mar-2009 07:48:02   

Hi Frans & David,

I've found a problem. I'm using the OnInitialized() method to set my entity default values. This works fine if I'm creating a new entity, because InitClassFetch() is called to create the entity.

However, this does NOT work properly when I retrieve my entities using Collection.GetMulti() from the database.

This is because EntityFactory.Create(fields As IEntityFields) creates a new empty entity before assigning the values & .isNew. Thus, InitClassEmpty() & OnInitialized() are being called before the Fields are assigned to the entity - and even before .isNew is properly set. So - my code within OnInitialized() fails because .IsNew always = True, when it should really = False. I cannot detect if an entity is new or not if it is loaded from a collection! All entities from a collection appear new!

This is a really big problem for me, as I'm creating complex default child entities if the parent entity is new. Now, Parent.ChildCollection is ALWAYS filled with my default child entities - even if there are actual Child Entities in the database!

Please help me identify a fix! Much thanks!

Ryan D. Hatch

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 24-Mar-2009 11:08:23   

Use this:

        protected override void OnInitialized()
        {
            base.OnInitialized();
            if((this.Fields!=null) && (this.Fields.State!=EntityState.Fetched))
            {
                // set default values here
            }
        }

ref: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=14456

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 24-Mar-2009 14:40:34   

Thanks, Walaa! Your help is greatly appreciated. ; )

Ryan D. Hatch

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 24-Mar-2009 15:36:06   

Hi Walaa,

Actually, after further review - this still does not work. Here's why:

When I do Collection.GetMulti()... InitClassEmpty() calls OnInitalized() - where Me.Fields.State = New! So I still cannot detect if the entity is New or fetched by a Collection!

Is this not considered a bug? Please advise. Much Thanks!

Ryan

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 25-Mar-2009 07:06:37   

Frans & Walaa -

Please help! I really need a fix for this bug - or at the very least, a real workaround. When fetching Entities with Collection.GetMulti(), OnInitialized() is called prematurely. isNew = True & all Fields are incorrectly empty.

When an Entity is new, I assign default values & default child entities to it in OnInitialized(). But - Entities being fetched from the Collection are also getting assigned my default values & default child entities in memory - when they shouldn't be because they are obviously not new! Within OnInitialized() I can't tell the difference between an existing Entity being fetched from a Collection.GetMulti() or an Entity being created using New Entity()!

After retrieving my Entities using Collection.GetMulti() - I cannot lazy-load any of my related child entities! Even though child entities already exist in the database for my Entities, I cannot view them - because LLBLGen will not (and should not) lazy load from the database when it sees that the Child Entities collection (filled with default child entities) already exists in memory!

No Known Workaround My attempt to use Reflection as a workaround is failing. I attempted using the StackTrace at run-time to check whether or not EntityFactory.Create(fields As IEntityFields) was calling OnInitialized(). This is a hack, and is not good practice. Even worse the hack doesn't work because my dev environment uses a different .NET version (2.0 SP2) than my production environment (2.0 SP 2 w/ 3.5 SP 1) - and the stack traces are different.

Desired Functionality When fetching a Collection.GetMulti(), OnInitialized() should not be called prematurely. It should only be called when isNew & Fields reflect their correct values.

Your help is greatly appreciated!

Ryan D. Hatch

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 25-Mar-2009 08:37:38   

Ryan, Would you please tell us which exact runtime library version are you using?

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 25-Mar-2009 14:23:51   

You bet, Walaa -

  • v2.6 Final ( October 6th, 2008 )
  • Self-Servicing Templates
  • Platform: .NET 2.0
  • Language: VB.NET
  • Tasks: General 2005 (Partial Classes) Ryan
Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 25-Mar-2009 14:40:46   

v2.6 Final ( October 6th, 2008 )

Would you please upgrade to the latest build and try again?

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 25-Mar-2009 16:16:49   

Hi Walaa -

Here is a Screencast I created so you can better understand my problem:

http://www.screencast.com/users/rdhatch/folders/Jing/media/5cd5e99e-01f9-427d-ac53-4bf30e92d1b8

You'll notice .isNew = True, when it should = False.

Ryan

PS. I'm pretty sure I have the latest full version. I just downloaded the 25-Mar-2009 release (build October 6th) & installed it, refreshed the catalog, & regenerated the data objects. Problem is still there. Am I missing something... is there something else I should be installing to be at the newest version?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 25-Mar-2009 16:45:38   

Will look into it.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 25-Mar-2009 17:10:54   

The thing is this: When creating an entity using code, like CustomerEntity c = new CustomerEntity();, you'll call the normal constructor which will call OnInitialized etc.

With GetMulti, this same path is followed (the empty constructor is called) but after that, the Fields object of the entity is set to the filled EntityFields object with the data read from the database. This differs from adapter.

So in your case, in getmulti, you'll see a call to OnInitialized, which does work on the fields but these fields are replaced anyway with the fields object with the data from the database. This is due to the fact that the fetch method fills fieldobjects, as EntityFields is a DTO for the data to fetch. This EntityFields object is then plugged into an existing entity, created using the Create() method.

After GetMulti, your entities should have the values from the database.

This is different from adapter as in: adapter entities have a ctor which accepts a fields object, selfservicing does not. The reason is that selfservicing entities otherwise would get a myriad of ctors and we cut them out. it also would give odd code paths for direct fetching data through the ctor. Normally this isn't a problem, as only the fields are replaced by the new data. It is a problem if you do graph initialization from within an entity. the thing however is: should an entity from within its ctor initialize a graph it is part of, or should a method outside the entity do that? It's not as if the entity which initializes the graph owns the related entities.

Is this trackable, as in: is it possible to avoid the init work? No, not at the moment, as the empty ctor simply creates a new, ready to use entity.

I do agree that it would have been better if the factory template would simply call an internal ctor which created the entity class and set the fields object directly.

In your situation, I'm a little puzzled what the best way is to solve this. Creating the graph of default entities at init might sound tempting, however what happens if you fetch the entities, would you then always want to fetch the related entities or not? If not, you'd get the situation where there are related entities however not when the entity is fetched.

I see entity class instances in a graph a result of logic outside an entity, as entity X doesn't own an entity it is related to (example: does Customer own its order entities? if so, does Employee own these same order entities? If so, how can Employee own the same entities?). So IMHO, if you want to create these graphs with the new entity, why not create a factory method for that which builds these empty default entities for you in a graph?

Frans Bouma | Lead developer LLBLGen Pro
rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 25-Mar-2009 17:59:50   

Hi Frans -

Thank you very much for the reply.

I agree if I was using Adapter, I wouldn't have this problem. Nonetheless - I'm using SelfServicing, which should still be able to handle this.

In my opinion - Entities can own (or be in charge of) their related child entities. This is true whether I use a EntityManager class as a Factory to create & assign the defaults, or simply use OnInitialized() when a new Entity is created. Regardless of where I assign the default child entities - they are still being assigned - and the Entity is managing its related entities.

This is why I still consider this is a bug in the EntityFactory - From the Documentation: Entity: Initialized. Event which is raised from the protected method OnInitialized and which is raised ... [with] the fields already filled with values.

Could you please supply a fix to this? Until then, is there any way the EntityFactory can be changed so it does not call OnInitialized() prematurely? Will I need to change the Self-Servicing templates myself to fix this bug?

Thanks, Frans. Appreciate your help with this...

Ryan

PS. In this particular instance, we are dealing with health insurance. Every Insurance Plan is kept within a PlanGroup. PlanGroups is a hierarchy. Employers have many PlanGroups. When I create a new root PlanGroup - I want 3 child PlanGroups automatically created, which the user can put new Plans into.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 25-Mar-2009 22:39:41   

We can't fix it in this version, as that would break applications, and I really want to fix this for you, but I can't at the moment. I agree that it should have the same behavior as with adapter, namely that the onInitialized method is ran after the fields have been inserted, so you can test on that and go ahead.

You could alter the factory template and add additional ctor's to the entity classes (through templates, marked Friend (internal). The added ctor's accept a boolean parameter, so they look like:

Friend Sub New (flag As Boolean) _createdDuringFetch = flag End Sub

and the _createdDuringFetch flag is a boolean member variable you define in the entities (e.g. in the CommonEntityBase class).

In the factory, call this ctor while passing true instead of the empty. In OnInitialized, you then simply check the _createdDuringFetch and act accordingly.

You could also add a different CTor, which accepts an IEntityFields object and calls a new InitClassEmpty(fields, IValidator) overload of InitClassEmpty, and in your new overload you set the base.Fields object to the fields passed in. Then in OnInitialized, you can simply check the fields' State flag. Of course you've to alter the factory a bit to call this ctor instead of

The last option might be better, as it looks more compliant to what adapter offers and thus in the future this route will also be chosen by our change in v3.

Sorry for not being able to patch it for you. The extra ctor's can be added to partial classes if you like so you don't have to alter a lot of templates (only the factory if you really want to). The factories use virtual methods so you can, if you want to, add the code without using any template changes, by subclassing the factory at hand and override the create methods.

Frans Bouma | Lead developer LLBLGen Pro
rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 25-Mar-2009 22:51:09   

Great! Thanks for your help, Frans! I will get right on it...

Looking forward to v3.0 ; )

Ryan

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 02-Apr-2009 15:58:52   

Hi Frans -

I took the time this morning to modify the templates & fix this OnInitialized() bug. It seems to be working properly. In case you haven't already edited your templates for v3.0, here's what I've done:

Please see my one question below [in red]...

entityFactories.template

        ''' <summary>Creates a new <[CurrentEntityName]>Entity instance but uses a special constructor which will set the Fields object of the new
        ''' IEntity instance to the passed in fields object. Implement this method to support multi-type in single table inheritance.</summary>
        ''' <param name="fields">Populated IEntityFields object for the new IEntity to create</param>
        ''' <returns>Fully created and populated (due to the IEntityFields object) IEntity object</returns>

        Public Overrides Overloads Function Create(fields As IEntityFields) As IEntity
            'EDIT: Ryan D. Hatch, 2009.04.22 - Bug fix: http://llblgen.com/tinyforum/Messages.aspx?ThreadID=15323&HighLight=1
            'Dim toReturn As IEntity = Create()
            'toReturn.Fields = fields
            Dim toReturn As IEntity = New <[CurrentEntityName]>Entity(fields)

            <[ UserCodeRegion CurrentEntityName "CreateNew($VALUE)UsingFields" ]>
            ' __LLBLGENPRO_USER_CODE_REGION_START <[ UserCodeRegionName ]>
            ' __LLBLGENPRO_USER_CODE_REGION_END
            <[ EndUserCodeRegion ]>     
            Return toReturn
        End Function

entity.template

                ''' <summary>CTor</summary>
                ''' EDIT: by Ryan D. Hatch, 2009.04.02 - Bug fix: http://llblgen.com/tinyforum/Messages.aspx?ThreadID=15323&HighLight=1

        <[If IsAbstract]>Friend<[Else]>Public<[EndIf]> Sub New(ByVal fields As IEntityFields)
            MyBase.New()
<[If Not IsSubType]>            InitClassEmpty(fields, Nothing)<[EndIf]>
        End Sub

entityInclude.template

Question: Please check the line: MyBase.IsNew=False

How does .isNew get set properly?

In the new InitClassEmpty(field, validator) - Should we be setting .isNew = False? Might there be any other time when Entity.New(fields) could be called when the entity is not new? I think this would be possible when creating an entity in memory. Or doesn't it matter... does setting the .Fields property automatically set the .isNew property?

        ''' <summary>Initializes the class with field data.  This method is called when a Collection fetches & populates its entities.</summary>
        ''' <param name="validatorToUse">The validator Object for this <[CurrentEntityName]>Entity</param>
        ''' EDIT: Added by Ryan D. Hatch, 2009.04.02 - Bug fix: http://llblgen.com/tinyforum/Messages.aspx?ThreadID=15323&HighLight=1

        Protected <[If IsSubType]>Overrides<[Else]>Overridable<[EndIf]> Sub InitClassEmpty(ByVal fields As IEntityFields, validatorToUse As IValidator)
<[If Not IsSubType]>            OnInitializing()<[EndIf]>
<[If IsSubType]>            MyBase.InitClassEmpty(fields, validatorToUse)<[Else]>
            MyBase.IsNew=False
            MyBase.Fields = fields
            MyBase.Validator = validatorToUse<[EndIf]>
<[If IsInHierarchyType TargetPerEntityHierarchy]>           If MyBase.Fields.State=EntityState.[New] Then
                MyBase.Fields(CInt(<[CurrentEntityName]>FieldIndex.<[DiscriminatorColumnName]>)).ForcedCurrentValueWrite(<[DiscriminatorValue]>)
            End If<[EndIf]>
            InitClassMembers()
            <[ UserCodeRegion "InitClassEmpty" ]>
            ' __LLBLGENPRO_USER_CODE_REGION_START InitClassEmpty
            ' __LLBLGENPRO_USER_CODE_REGION_END
            <[ EndUserCodeRegion ]>
<# Custom_EntityInitializationTemplate #>
<[If Not IsSubType]>            OnInitialized()<[EndIf]>
        End Sub
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 03-Apr-2009 05:38:24   

Nice simple_smile Thanks for sharing this.

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 03-Apr-2009 13:49:05   

About isnew:

IsNew is set to false after a successful fetch. So it's assumed to be true. Be careful with setting IsNew to false on newly instantiated default entities: the data inside them is not a valid, existing entity, it's a new entity instance without data. So it's something to be careful about: leave IsNew to the value it has, it only should be set to false by the fetch logic.

Frans Bouma | Lead developer LLBLGen Pro
rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 03-Apr-2009 14:38:38   

Roger that, will use .isNew = True. Will let Fetch logic change to False. Thanks!

Ryan

Kodiak
User
Posts: 92
Joined: 13-Apr-2009
# Posted on: 23-Apr-2009 13:14:27   

How would I apply these changes when using SD.Presets.SelfServicing.TwoClasses2008 ?

I've tried making the changes to the template files but just come up with

Error 1 Value of type 'SD.LLBLGen.Pro.ORMSupportClasses.IEntityFields' cannot be converted to 'Integer'. in DAL\FactoryClasses\EntityFactories.vb

Should another file be updated?

Honestly the amount of hardship in using LLBLGen ....in hindsight I would just stay with LINQ.

1  /  2