Change empty string to null

Posts   
 
    
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 22-Oct-2007 00:41:26   

Can I use OnSetValue to change an empty string to null? Currently I do this in CommonEntityBase, overriding OnValidateEntityBeforeSave():


protected override void OnValidateEntityBeforeSave()
{
   base.OnValidateEntityBeforeSave();

   // set any modified string fields that are "" to null
   for (int i = 0; i < Fields.Count; i++)
   {
       IEntityField2 field = Fields[i];
       if (field.IsChanged)
       {
           string s = field.CurrentValue as string;
           if (s != null && s.Length == 0)
               SetNewFieldValue(i, null);
       }
   }
}

Which is a little inefficient, to say the least, but works well.

I tried


protected override void OnSetValue(int fieldIndex, object valueToSet, out bool cancel)
{
   string s = valueToSet as string;
   if (s != null && s.Length == 0)
       valueToSet = null;
   base.OnSetValue(fieldIndex, valueToSet, out cancel);
}

and then


protected override void OnSetValue(int fieldIndex, object valueToSet, out bool cancel)
{
   string s = valueToSet as string;
   if (s != null && s.Length == 0)
   {
       SetNewFieldValue(fieldIndex, null);
       cancel = false;
   }
   else
       base.OnSetValue(fieldIndex, valueToSet, out cancel);
}


but in both cases my current test in OnValidateEntityBeforeSave() still evaluates to true and I have to set it again.

Jim

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 22-Oct-2007 06:59:15   

Hi Jim.

**OnSetValue **is called before the value is already set. So, after your code is performed the value ("") continues the process of be set.

A better place is OnSetValueComplete...

namespace Northwind.EntityClasses
{
    public abstract partial class CommonEntityBase
    {
        protected override void OnSetValueComplete(int fieldIndex)
        {
            if (this.Fields[fieldIndex].CurrentValue is string
                && this.Fields[fieldIndex].CurrentValue != null
                && this.Fields[fieldIndex].CurrentValue.ToString().Length == 0)
            {
                this.SetNewFieldValue(fieldIndex, null);
            }
            
            base.OnSetValueComplete(fieldIndex);        
        }
    }
}

Hope helpful wink

David Elizondo | LLBLGen Support Team
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 22-Oct-2007 20:42:57   

Indeed, OnSetValueComplete() is exactly what I needed, thanks.

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Oct-2007 00:33:41   

Whoops. That appeared to work on until I pasted in a large quantity of data from the clipboard. Then I got StackOverflowException in SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 24-Oct-2007 10:36:49   

huh? The method either works or not, it doesn't depend on size if it dives in a loop or not, it shouldn't.

Is it possible for you to obtain some parts of the stack so I have an idea where it loops around?

Frans Bouma | Lead developer LLBLGen Pro
JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Oct-2007 22:21:07   

Well, I thought this was pretty weird. I can import a bunch of data from a file and it has no problem, but I was testing some functionality to paste the same data from the clipboard, and that's when it blew up. The debugger cannot evaluate anything at that point.

Let me play with this a bit more and make sure I'm not doing something really stupid, and I will post with more info.

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Oct-2007 22:46:43   

Well, it appears the obvious is happening, which is that calling SetNewFieldValue from within OnSetValueComplete results in another call to OnSetValueComplete, etc. At least, that is what is happening in this case. For some reason, seems like when I tried it the first time, it worked.

Does calling SetNewFieldValue() from within OnSetValueComplete() result in recursion? And if so, only sometimes or always?

Currently my code looks like this:


protected override void OnSetValueComplete(int fieldIndex)
{
   IEntityField2 field = Fields[fieldIndex];
   string s = field.CurrentValue as string;
   if (s != null && s.Length == 0)
       SetNewFieldValue(fieldIndex, null);

   base.OnSetValueComplete(fieldIndex);
}

BTW overriding ToString() for field object to return .Name might be cool.

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 24-Oct-2007 23:03:06   

In addition to the override in CommonEntityBase, I also override OnSetValueComplete in my entity class:


protected override void OnSetValueComplete(int fieldIndex)
{
   base.OnSetValueComplete(fieldIndex);

   if (fieldIndex == (int)TraceFieldIndex.Name)
   {
       dataColumn.ColumnName = this.Name;
       dataColumn.Caption = this.Name;
   }
}

When I call SetNewFieldValue() from within CommonEntityBase.OnSetValueComplete(), then OnSetValueComplete gets called in the subclass, and it in turn calls base.OnSetValueComplete(), resulting in recursion.

How can I safely set the string field so it emits NULL to the db in OnSetValueComplete()? Or should I go back to what I was doing before?

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 25-Oct-2007 10:55:45   

I think OnValidateFieldValue is the best place to do so.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 25-Oct-2007 11:52:50   

Actually, there's a special method for this. (You realize there is too much functionality in a framework when you overlook a feature you've added yourself flushed )

It's called PreProcessValueToSet, and it's in EntityBase(2). You simply override it in the commonentitybase and you get the field to set + the value to set passed in. You then simply set the parameter valueToSet to the value it has to become and THAT value is then used for further processing, e.g. validation, auditing etc. simple_smile

Calling SetNewFieldValue() from OnSetValueComplete causes recursion indeed, as OnSetValueComplete is called from SetValue, which is called from SetNewFieldValue simple_smile

Frans Bouma | Lead developer LLBLGen Pro
luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 23-Dec-2007 03:10:33   

This is a very helpful thread. I just edited my CommonEntityBase.cs file adding the following and it worked perfectly!


#region Custom Entity code
// __LLBLGENPRO_USER_CODE_REGION_START CustomEntityCode

// LKK: Override method to convert empty strings to null
protected override void PreProcessValueToSet(IEntityField2 fieldToSet, ref object valueToSet)
{
    if (valueToSet is string && valueToSet != null && valueToSet.ToString().Length == 0)
    {
        valueToSet = null;
    }
    base.PreProcessValueToSet(fieldToSet, ref valueToSet);
}

// __LLBLGENPRO_USER_CODE_REGION_END
#endregion

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 23-Dec-2007 18:24:37   

luciusism, since you posted this, I hope you won't mind a couple of helpful suggestions on your code. If valueToSet is null, then the 'is' operator will return false when comparing to string (or anything else). Also, once you have determined valueToSet is a string, calling ToString() on it is not needed. So you could make your code a little more efficient. (See my code below).

It occurred to me since I decided to stick with OnValidateEntityBeforeSave(), maybe I should put my code up here with an explanation, in case it's helpful to anybody else in the future. I noticed that PreProcessValueToSet was getting called on everything when fetching from the database. So my final solution was to stick with OnValidateEntityBeforeSave(). I keep it in a partial class in my DAL project, along with other partial classes containing code I used to insert directly into the generated files, or else use custom templates for. Using partial classes is much cleaner, if you have a choice.

Here's the entire file, (with a couple of cosmetic name changes):


using System.ComponentModel;
using SD.LLBLGen.Pro.ORMSupportClasses;

namespace XXXXXX.XXX.DAL.EntityClasses
{
    public abstract partial class CommonEntityBase
    {
        protected override void OnValidateEntityBeforeSave()
        {
            // Genpro considers empty string to be semantically equivalent to null. In the db, 
            // query results can be very different depending on whether a column's value is 
            // NULL or set a zero-length string. To fix this, if a changed field currently has
            // an empty string, we "correct" that so that NULL in sent to db. I looked also at
            // PreProcessValueToSet(), but it gets called for every entity/field when a XXXX is
            // loaded from the db, so this seems the best place to do this.

            base.OnValidateEntityBeforeSave();

            foreach (IEntityField2 field in Fields)
            {
                if (field.IsChanged)
                {
                    string s = field.CurrentValue as string;
                    if (s != null && s.Length == 0)
                        SetNewFieldValue(field.FieldIndex, null);
                }
            }
        }
    }
}

This certainly may not be the final word on the subject!

I do think Frans should consider building this option into the designer somehow.

Jim

P.S. Not sure, but I believe the whole "" vs. NULL issue may be moot in Oracle. I'm using SQL Server.

luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 13-Feb-2008 02:05:30   

luciusism, since you posted this, I hope you won't mind a couple of helpful suggestions on your code. If valueToSet is null, then the 'is' operator will return false when comparing to string (or anything else). Also, once you have determined valueToSet is a string, calling ToString() on it is not needed. So you could make your code a little more efficient. (See my code below).

Thanks for the input, don't mind at all. I was wondering why I did that, then I noticed that I had copied and pasted from daelmo's code snippet. flushed

Thanks for the code reference, great input for the llblgen community.

luciusism
User
Posts: 119
Joined: 02-Jun-2007
# Posted on: 22-Nov-2016 17:44:18   

With breaking changes to PreProcessValueToSet, updated code for CommonEntityBase is below:


    protected override void PreProcessValueToSet(IFieldInfo fieldInfoOfFieldToSet, ref object valueToSet)
    {
            if (valueToSet != null && valueToSet is string && valueToSet.ToString().Length == 0) { valueToSet = null; }
            base.PreProcessValueToSet(fieldInfoOfFieldToSet, ref valueToSet);
    }