Upgrading to LLBLGen Pro 4.0

Posts   
 
    
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 04-Jul-2013 16:32:46   

I've got to say, credit where it is due, the update from v3.5 to v4.0 went quite smoothly... mostly - I have one outstanding minor issue.

  • I really like the fact that the runtime libraries were combined and the names were simplified to remove .NET version and year information.
  • Queries seem faster in my app but I also upgraded to VS2012 and SqlServer2012 at the same time so its not clear cut, but we'll give LLBLGen the major credit.
  • I'm glad you fixed the issue where BatchActionQuery was always being generated. I can now simplify my Promote<TEntity> code.
  • The documentation for breaking changes was helpful when I need to fixup my code. There were a couple of things omitted though, so I've listed them below.
  • Love the Cascade Delete options (who came up with that great idea sunglasses ). Code writer for triggers next.... stuck_out_tongue_winking_eye

Other Breaking Changes (for those of use who have 'fiddled'):- IEntityFieldCore:- AcceptChange/RejectChange are documented but these are not - BeginEdit/EndEdit/CancelEdit - AddLinkedSubTypeField - ForcedChangedWrite - SetDbValue - ForcedIsNullWrite - LinkedSubTypeFields - LinkedSuperTypeField.set - These are new: public void WriteXml(XmlWriter writer) { realField.WriteXml(writer); } public void ReadXml(XmlReader reader) { realField.ReadXml(reader); } public void WriteDefinitionAsXml(XmlWriter writer) { realField.WriteDefinitionAsXml(writer); } public void ReadDefinitionFromXml(XmlReader reader) { realField.ReadDefinitionFromXml(reader); }

  • QueryExpression:
    • MaxNumberOfElementsToReturn replaced with RowsToTake?
    • PageNumber/PageSize replaced with RowsToSkip?

Outstanding Issue:- I had some code to reset certain fields back to their original values like this:- Fields[(int) QuestionnaireResponseAnswerFieldIndex.AnswerID].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.ResponderPriority].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.AdviserPriority].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.NotApplicable].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.Notes].RejectChange();

I love how the breaking changes said "...and it's highly unlikely these methods are used in user code."! Well I'm one smile

Fields.RejectChanges() doesn't quite do the same thing, well it does but only once. The suggestion of using SaveFields/RollbackFields might work but its not as convenient as it requires a preparation step to do the SaveFields bit on all the entities that need it. The bit that would need to do the Rollback doesn't necessarily know about the source (loose coupling and all that). Plus I don't necessarily want to reject changes on all the fields, only a subset. I think I might have to reintroduce the same functionality somehow - maybe an extension method or something on my CommonEntityBase but I'll have to work out what's changed first. I'll get back to you if I have a problem.

Documentation gremlins:- 1) In Designer Docs in Update / Delete Cascade rules. The last sentence in the first paragraph under Model First contains the same text as the following sentence. 2) That following paragraph also has "emited" rather than "emitted" and "an Foreign" which should be "a Foreign" 3) Last line has spelling mistake for "TagetPerEntity" 4) First sentence under "Supported update/delete rules per database" has "mentioend"

Questions (not criticisms really - just interested) 1) Why is Cascade Delete on by default? (Other than for entity hierarchies which is fine). SQL Server doesn't do that by default, you have to manually add it so I would have thought it would also be off in LLBLGen for safety. Sometimes you want to automatically delete child related entities and sometimes you would want to abort a delete because that entity 'is in use' and only the developer can know which it is. 2) Why are the XXXFieldIndex templated created as public enums rather than public consts? That way, you wouldn't need casts wherever they are used. There also seems to be scope to use int16 instead since I doubt any entity would have 16k fields. 3) What tool do you use to create documentation? I prefer the output of your docs to that Help2 stuff that Microsoft use now.

Suggestions 1) There seems to be scope to reduce the size of the footprint of an entity: EntityState is a int enum which I assume will take 4 bytes when 1 would do. Also it and the 11 bools could be Stored in 2 bit vectors - one of the serializable bools and one for the EntityState and non-serializable bools. That would save 12 bytes per entity in memory - a 117K saving for 10,000 entities. I think there may be some other savings there too depending on usage - in the same way you now lazy-create Fields. Some Events if only rarely used could create an EntityEvents object on the fly - maybe another 12 bytes per entity. 2) If there is one thing I could change in your documentation, its the spacing between bulleted-lists. I don't mean a blank line, that would be too large a gap but a few extra pixels would help these ageing eyes.

Cheers Simon

simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 04-Jul-2013 17:24:54   

This extension method seems to do the trick for me now but is it robust enough for general use?

    public static void RejectChange(this IEntityFieldCore field)
    {
        if (!field.IsChanged) return;

        var fields = ReflectionHelper.GetNonPublicFieldValue<IEntityFieldsCore>(field, "_containingEntityFieldsCore");
        if (fields == null) return;

        var volatileData = ReflectionHelper.GetNonPublicFieldValue<VolatileEntityFieldsDataContainer>(fields, "_volatileData");
        if (volatileData == null) return;

        var originalValue = ReflectionHelper.GetNonPublicFieldValue<object[]>(volatileData, "_originalValues")[field.FieldIndex];

        field.CurrentValue = originalValue;
        field.IsChanged = false;
    }
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Jul-2013 10:59:31   

simmotech wrote:

I've got to say, credit where it is due, the update from v3.5 to v4.0 went quite smoothly... mostly - I have one outstanding minor issue.

simple_smile

  • Queries seem faster in my app but I also upgraded to VS2012 and SqlServer2012 at the same time so its not clear cut, but we'll give LLBLGen the major credit.

Queries are 30+% faster (e.g. from 1200ms in v3.5 to 750ms in v4), as the materialization is much faster than before. Of course, if the query execution is 10% of your total app's execution time, it won't be that significant.

Other Breaking Changes (for those of use who have 'fiddled'):- IEntityFieldCore:- AcceptChange/RejectChange are documented but these are not - BeginEdit/EndEdit/CancelEdit - AddLinkedSubTypeField - ForcedChangedWrite - SetDbValue - ForcedIsNullWrite - LinkedSubTypeFields - LinkedSuperTypeField.set - These are new: public void WriteXml(XmlWriter writer) { realField.WriteXml(writer); } public void ReadXml(XmlReader reader) { realField.ReadXml(reader); } public void WriteDefinitionAsXml(XmlWriter writer) { realField.WriteDefinitionAsXml(writer); } public void ReadDefinitionFromXml(XmlReader reader) { realField.ReadDefinitionFromXml(reader); }

We didn't enlist all, as there were too many and most, if not all, were framework only methods, unless they were public.

  • QueryExpression:
    • MaxNumberOfElementsToReturn replaced with RowsToTake?
    • PageNumber/PageSize replaced with RowsToSkip?

You meant QueryParameters I presume? yes. We didn't document this as QueryParameters is mostly used internally by linq/queryspec: lowlevel api calls use the normal api.

Outstanding Issue:- I had some code to reset certain fields back to their original values like this:- Fields[(int) QuestionnaireResponseAnswerFieldIndex.AnswerID].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.ResponderPriority].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.AdviserPriority].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.NotApplicable].RejectChange(); Fields[(int) QuestionnaireResponseAnswerFieldIndex.Notes].RejectChange();

I love how the breaking changes said "...and it's highly unlikely these methods are used in user code."! Well I'm one smile

The thing is, RejectChanges is a left-over from v1 and it mimics datatable behavior, but in fact, there are better ways to do this (not the exact tiny thing, but the underlying feature: roll back changes). The main point of not adding RejectChange on the field is that the field doesn't hold the value anymore.

Documentation gremlins:- 1) In Designer Docs in Update / Delete Cascade rules. The last sentence in the first paragraph under Model First contains the same text as the following sentence. 2) That following paragraph also has "emited" rather than "emitted" and "an Foreign" which should be "a Foreign" 3) Last line has spelling mistake for "TagetPerEntity" 4) First sentence under "Supported update/delete rules per database" has "mentioend"

We'll correct those simple_smile

Questions (not criticisms really - just interested) 1) Why is Cascade Delete on by default? (Other than for entity hierarchies which is fine). SQL Server doesn't do that by default, you have to manually add it so I would have thought it would also be off in LLBLGen for safety. Sometimes you want to automatically delete child related entities and sometimes you would want to abort a delete because that entity 'is in use' and only the developer can know which it is.

The setting is on by default as we think most people want cascade deletes on entity graphs. The thing with settings is that if you leave the default to 'none', a lot of people will never discover the feature to begin with as many people simply go with the defaults and hardly change anything.

2) Why are the XXXFieldIndex templated created as public enums rather than public consts? That way, you wouldn't need casts wherever they are used. There also seems to be scope to use int16 instead since I doubt any entity would have 16k fields.

We started with enums in v1, and as these types are used a lot in user code (especially in the beginning), we can't change now. Good points, but we have to stick with enums due to backwards compatibility.

3) What tool do you use to create documentation? I prefer the output of your docs to that Help2 stuff that Microsoft use now.

The main docs are written by hand in a HTML editor simple_smile I used blend for web mostly and for v4 I used vs.net's wysiwyg editor. It's not that great, but it gets the job done simple_smile

Suggestions 1) There seems to be scope to reduce the size of the footprint of an entity: EntityState is a int enum which I assume will take 4 bytes when 1 would do. Also it and the 11 bools could be Stored in 2 bit vectors - one of the serializable bools and one for the EntityState and non-serializable bools. That would save 12 bytes per entity in memory - a 117K saving for 10,000 entities. I think there may be some other savings there too depending on usage - in the same way you now lazy-create Fields. Some Events if only rarely used could create an EntityEvents object on the fly - maybe another 12 bytes per entity.

The changes we made were mainly for fetch performance and we achieved that by storing all entity data together. This made it possible to pack together flags and the like and it led to a lower memory footprint, but we weren't actively hunting for bytes as I think that's a bit meaningless in .NET. A bool takes 4 bytes unless it's in a bitarray for example, and the clr/bcl allocate a lot of memory for objects anyway so finding a couple of bytes here and there won't make that much of a difference and isn't worth the trouble.

2) If there is one thing I could change in your documentation, its the spacing between bulleted-lists. I don't mean a blank line, that would be too large a gap but a few extra pixels would help these ageing eyes.

Hmm, never had a problem with them, but we'll see what we can do simple_smile

Thanks for the feedback!

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Jul-2013 11:00:55   

simmotech wrote:

This extension method seems to do the trick for me now but is it robust enough for general use?

    public static void RejectChange(this IEntityFieldCore field)
    {
        if (!field.IsChanged) return;

        var fields = ReflectionHelper.GetNonPublicFieldValue<IEntityFieldsCore>(field, "_containingEntityFieldsCore");
        if (fields == null) return;

        var volatileData = ReflectionHelper.GetNonPublicFieldValue<VolatileEntityFieldsDataContainer>(fields, "_volatileData");
        if (volatileData == null) return;

        var originalValue = ReflectionHelper.GetNonPublicFieldValue<object[]>(volatileData, "_originalValues")[field.FieldIndex];

        field.CurrentValue = originalValue;
        field.IsChanged = false;
    }

don't focus on field objects, define it on IEntityFieldsCore and pass in the field index. You now create Field objects somewhere which we don't do by default, so you're creating unnecessary objects.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 05-Jul-2013 12:57:11   

OK, I understand. How about this:-

        public static void RejectChange(this IEntityFieldsCore fields, int fieldIndex)
        {
            // Quick return if not a 'normal' set of entity fields
            if (!ReflectionHelper.GetNonPublicFieldValue<bool>(fields, "_inEntityDataMode")) return;

            // Quick return if not changed
            if (fields.GetIsChanged(fieldIndex) == false) return;

            // Get the volatile data manager
            var volatileData = ReflectionHelper.GetNonPublicFieldValue<VolatileEntityFieldsDataContainer>(fields, "_volatileData");
            if (volatileData == null) return;

            // Get the original value
            var originalValue = ReflectionHelper.GetNonPublicFieldValue<object[]>(volatileData, "_originalValues")[fieldIndex];

            // Use it to set the current value and reset ischanged
            volatileData.SetCurrentValue(fieldIndex, originalValue, false);
            volatileData.SetIsChanged(fieldIndex, false);
        }

This appears to work too though I'm not sure about whether the indirect call to SetDbValue from SetCurrentValue may be a problem.

The thing is, RejectChanges is a left-over from v1 and it mimics datatable behavior, but in fact, there are better ways to do this (not the exact tiny thing, but the underlying feature: roll back changes). The main point of not adding RejectChange on the field is that the field doesn't hold the value anymore.

I'm fine understanding that Field doesn't hold the value any more and I like the optimization to let Fields control it all.

But whilst Roll back changes is obviously a must-have feature, rolling back specific fields has a use also.

For example. the sample calling code I gave a clue as to my current usage. It is a questionnaire form with checkboxes mapped to AnswerID and NotApplicable and comboboxes for the two priority fields. I plan to remove the two lines that refer to AdviserPriority and Notes though.

This allows a Customer to reset their answers but preserve any Notes that have been modified (Adviser priority will become readonly so becomes a non-issue). If I had to rollback everything then the notes have gone as well.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 06-Jul-2013 10:45:56   

Code is better indeed.

You do have a point indeed. So adding a Fields.RejectChange(index) would suffice and make this breaking change go away for you?

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 08-Jul-2013 11:11:43   

It would indeed though I'm in no hurry - the code above is working fine.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 08-Jul-2013 11:39:04   

We'll look into adding a convenience method for this in v4.1

Frans Bouma | Lead developer LLBLGen Pro
chand
User
Posts: 72
Joined: 05-Aug-2007
# Posted on: 28-Dec-2013 06:58:24   

Frans,

Any news on adding this RejectChange method? This is a breaking change for us also.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 28-Dec-2013 07:17:11   

Hi chand,

AFAIK, this is not included in v4.1. Could you please explain your breaking change here?, just to ensure we are talking about the same thing.

David Elizondo | LLBLGen Support Team
chand
User
Posts: 72
Joined: 05-Aug-2007
# Posted on: 28-Dec-2013 07:24:12   

Calls such as the following are not compiling

if(somecodition) { ((EntityField2)item.Fields[MyFieldIndex]).RejectChange(); }

Now RejectChange() is method is not there any more.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 28-Dec-2013 11:02:12   

We implemented RejectChanges on the fields object. So

((IEntityFieldsCore)entity).RejectChanges();

which rejects all changes. You want to reject a change on one field or all? RejectChange only worked when the entity is in an IEditableObject state (so edited by a control through binding).

In v4.x there's no equivalent per field, to do per field undo, as most of the time the bound control does this anyway and reject changes was mostly used in 1 go, i.e. reject changes made through editing in the IEditableObject scenario.

So my question to you is: what's the use case of your code that requires per-field reject change? (so we have a picture for which scenario the feature is).

Frans Bouma | Lead developer LLBLGen Pro
chand
User
Posts: 72
Joined: 05-Aug-2007
# Posted on: 28-Dec-2013 15:16:58   

It is being used in data binding scenario.

Here is one case:

A child collection is being bound to windows forms datagrid. All properties of all rows are updatable, except a single property of one special row (identified based on a property of that row)

Every time user changes that property of that special row, it simply rejects those changes.

I am sure we could solve this issue other ways. But wondering if we don't have to go through QA cycles for this change.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 29-Dec-2013 12:22:19   

you can simply do that with an authorizer: the authorizer can be injected either manually, or through the built-in dependency injection system. The authorizer then validates whether a change on a given field is acceptible. If not, it will reject the change, so the change won't happen on the field. See http://www.llblgen.com/documentation/4.1/LLBLGen%20Pro%20RTF/hh_goto.htm#Using%20the%20generated%20code/gencode_authorization.htm

Frans Bouma | Lead developer LLBLGen Pro