Not updating nullable Int column to null

Posts   
 
    
anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 21-Mar-2011 21:40:30   

Hi, I am unable to update a nullable int column to null, it updates to any value other than null.

I confirmed that my database field is nullable as well. I stepped into the code and I send the correct value to the adapter(null).

The generated sql does not include the column when the value is null.(checked with profiler) It includes the column on the sql statement when the value is not null.

I am using LLblgen 3.1 .Net 4.0 C# using Ria Services, the entity has a collection of entities with a composition attribute for RIA. I do not implement the integrated crud operations on the LLBLGenProDomainService2 base class, I call my own bol(all works ok except for this).

Version 3.1.0.0 SD.LLBLGen.Pro.ORMSupportClasses.NET20, v2.0.50727 SD.LLBLGen.Pro.RiaSupportClasses.dll, v4.0.30319

MS SQL 2008

Thanks Andres (customer number 17303)

This is what I see on the server side for that entity field(field value is null)

        _actAsDerivedTableField false   bool
        _alias  null    string
        _beginEditIsChangedState    false   bool
        _currentValue   null    object
        _dbValue    null    object
        _derivedTableTargetName null    string
        _dynamicUsageInfo   null    SD.LLBLGen.Pro.ORMSupportClasses.EntityFieldCore.DynamicFieldInfo
+       _fieldInfo  {SD.LLBLGen.Pro.ORMSupportClasses.FieldInfo}    
SD.LLBLGen.Pro.ORMSupportClasses.IFieldInfo {SD.LLBLGen.Pro.ORMSupportClasses.FieldInfo}
+       _inheritanceFieldInfo   {SD.LLBLGen.Pro.ORMSupportClasses.EntityFieldCore.InheritanceFieldInfo} 
SD.LLBLGen.Pro.ORMSupportClasses.EntityFieldCore.InheritanceFieldInfo
        _isChanged  false   bool
        _isNull false   bool
        _objectAlias    ""  string
        _originalValue  null    object
        ActAsDerivedTableField  false   bool
        ActualContainingObjectName  "DocTypeAttEntity"  string
        AggregateFunctionToApply    None    SD.LLBLGen.Pro.ORMSupportClasses.AggregateFunction
        Alias   "GridDisplayPosition"   string
        ContainingObjectName    "DocTypeAttEntity"  string
        CurrentValue    null    object
        CurrentValueInternal    null    object
+       DataType    {Name = "Nullable`1" FullName = "System.Nullable`1[ 
[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}   
 System.Type {System.RuntimeType}
        DbValue null    object
        DbValueInternal null    object
        ExpressionToApply   null    SD.LLBLGen.Pro.ORMSupportClasses.IExpression
        FieldIndex  3   int
        [colorvalue="FF0000"][b]IsChanged   false[/b][/color]   bool
        IsForeignKey    false   bool
        IsInMultiTargetEntity   false   bool
        IsNull  false   bool
        [colorvalue="FF0000"][b]IsNullable  true[/b][/color]    bool
        IsOfEnumDataType    false   bool
        IsPrimaryKey    false   bool
        IsReadOnly  false   bool
+       LinkedSubTypeFields Count = 0   
System.Collections.Generic.List<SD.LLBLGen.Pro.ORMSupportClasses.IEntityFieldCore>
        LinkedSuperTypeField    null    SD.LLBLGen.Pro.ORMSupportClasses.IEntityFieldCore
        MaxLength   0   int
        Name    "GridDisplayPosition"   string
        ObjectAlias ""  string
        Precision   10  byte
+       RealDataType    {Name = "Int32" FullName = "System.Int32"}  System.Type {System.RuntimeType}
        Scale   0   byte
        SD.LLBLGen.Pro.ORMSupportClasses.IEntityFieldCoreInternal.DerivedTableTargetingFieldName    null    string
        SD.LLBLGen.Pro.ORMSupportClasses.IEntityFieldCoreInternal.IsDerivedTableTargetingField  false   bool
+       Static members      

I was able to see the original value of the entity using ChangeSet and it is clearly the original value. DocTypeAttEntity original = this.ChangeSet.GetOriginal<DocTypeAttEntity>(docTypeAttEntity);

the field GridDisplayPosition is an int = 3; so there must be a problem deserializing the entity. I will implement a fix on my side but that should be fixed soon. Mark fields changed using the LLBLGenProDomainService2 class(in this case the nullable of int).

anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 22-Mar-2011 00:59:28   

This is the generic workaround in case some one is looking for a hack while this gets fixed. Needs testing.

This will mark all the entities in the change set(includes related entities).

    public void UpdateEntity(DocTypeEntity entity)
    {
        ChangeOperation opMain = ChangeSet.GetChangeOperation(entity);
        if (opMain == ChangeOperation.Update)
            entity.IsNew = false;

        if (entity.DocTypeAtts != null)
            entity.DocTypeAtts.RemovedEntitiesTracker = new EntityCollection<DocTypeAttEntity>();

        foreach (DocTypeAttEntity docTypeAttEntity in this.ChangeSet.GetAssociatedChanges<DocTypeEntity, EntityCollection<DocTypeAttEntity>>(entity, p => p.DocTypeAtts))
        {
            ChangeOperation op = this.ChangeSet.GetChangeOperation(docTypeAttEntity);

            switch (op)
            {
                case ChangeOperation.Delete:
                    docTypeAttEntity.IsDirty = false;
                    docTypeAttEntity.IsNew = false;
                    entity.DocTypeAtts.RemovedEntitiesTracker.Add(docTypeAttEntity);
                    //entity.DocTypeAtts.Remove(docTypeAttEntity);//ria removes the entity so i can not remove it again
                    break;
                case ChangeOperation.Insert:
                    docTypeAttEntity.IsNew = true;
                    break;
                case ChangeOperation.None:
                    docTypeAttEntity.IsNew = false;
                    docTypeAttEntity.IsDirty = false;
                    break;
                case ChangeOperation.Update:
                    MarkFieldsChanged<DocTypeAttEntity>(docTypeAttEntity, this.ChangeSet);
                    docTypeAttEntity.IsNew = false;
                    break;
                default:
                    docTypeAttEntity.IsDirty = false;
                    docTypeAttEntity.IsNew = false;
                    break;
            }

        }

warning //Hack, entities are not propertly marked as Changed, this should be removed after a better fix is available

        MarkFieldsChangedOnAllEntities(this.ChangeSet);

        CallContext cc = new CallContext(Membership.GetUser().ProviderUserKey.ToString());
        BusinessObjectResult<DocTypeEntity> result = bol.SaveAndGetEntity(cc, entity);
        if (result.ResultType == BusinessObjectResultType.Success)
            entity = result.ResultObject;

        //reserach why this is serialized back by ria services, I am currently taking care of the work on my bol, so this is out of sync
        foreach (ChangeSetEntry item in this.ChangeSet.ChangeSetEntries)
        {
            ((EntityBase2)item.Entity).Fields.State = SD.LLBLGen.Pro.ORMSupportClasses.EntityState.Fetched;
        }

    }

    public void MarkFieldsChangedOnAllEntities(ChangeSet changeSet)
    {
        foreach (ChangeSetEntry item in changeSet.ChangeSetEntries)
        {
            Type entityType = item.Entity.GetType();
            MethodInfo method = GetType().GetMethod("MarkFieldsChanged"); 
            MethodInfo genericMethod = method.MakeGenericMethod(new Type[] { entityType });
            object[] parameters = new object[2];
            parameters[0] = item.Entity;
            parameters[1] = changeSet;
            genericMethod.Invoke(this, parameters);
        }
    }

    public void MarkFieldsChanged<EntityType>(object entity, ChangeSet changeSet)
        where EntityType : EntityBase2
    {

        EntityType original = changeSet.GetOriginal<EntityType>((EntityType)entity);
        if (original != null)
            foreach (IEntityField2 item in ((EntityType)entity).Fields)
            {
                object originalPropertyVal = original.GetCurrentFieldValue(((SD.LLBLGen.Pro.ORMSupportClasses.EntityFieldCore)(item)).FieldIndex);
                object newPropertyVal = ((SD.LLBLGen.Pro.ORMSupportClasses.EntityFieldCore)(item)).CurrentValue;

                if (!item.IsChanged && ((originalPropertyVal != null && newPropertyVal == null) || (originalPropertyVal == null && newPropertyVal != null)
                    || (originalPropertyVal != null && !originalPropertyVal.Equals(newPropertyVal))
                    || (newPropertyVal != null && !newPropertyVal.Equals(originalPropertyVal)))
                    )
                {
                    item.IsChanged = true;
                }

            }
    }
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 22-Mar-2011 07:38:21   

Hi anlebu, Sorry but I don't understand your trace in the first post neither your workaround. I don't think there is anything to fix IMHO. If you could show us a simple code snippet that reproduce what you think is a bug, we can look into it to fix it. Normally if a null value is not set is because the original value (DBValue) is null as well, so no change is detected. Please double check this.

David Elizondo | LLBLGen Support Team
anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 22-Mar-2011 14:35:47   

Hi Daelmo, please disregard that trace and just read the rest of the email.

these are the steps to reproduce it (no code to make it easier to understand)

-Create a DomainService class using the Base Class provided by llblgen 3.1, it provides crud operations for an Entity with a nullable int column.

-on Silverlight 4 client, get an entity with the nullable int field set to 3(using the generated DomainContext)

-on Silverlight 4 client, update the entity nullable field to null and then call the update operation on the generated DomainContext

-at this point the DomainService gets the entity wich has the correct nullable field value(null) but the field is not marked as changed.

please let me know if you still don't understand so I can send you more details.

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 23-Mar-2011 09:46:56   

David wrote:

Normally if a null value is not set is because the original value (DBValue) is null as well, so no change is detected. Please double check this.

Would the value be set to null, if the database field has another value but null?

anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 23-Mar-2011 16:13:36   

No, the field value is 3 on the database and is not set to null. My previous threads explain that in detail. my work around has access to the property values before and after the changed are done on the generated Ria Entities using the ChangeSet on the DomainService, this is how i marked the fields as changed.

It seems that the logic to mark the Fields as changed is natively on the llblgen entities, since Ria generates the client side entities, this logic is removed from here, therefore it needs to be implemented after the deserialization of the entity.

I am not sure at what level the two entities need to be sync, but my guess is that it should happen between the deserialization and before the entity is passed to the domain service, and I don't think this should be done by the llblgen customer since Ria Services are supported now, thats why I got 3.1.

I appreciate your time, and as you know I have a Hack work around so this is not stopping me.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 24-Mar-2011 10:00:35   

The version number you gave is the .net version number. Please right-click the assembly dll in windows explorer -> properties -> version tab.

Anyway, on August 12th, we fixed a bug in the RIA services dll, as nullable fields didn't get the roundtriporiginal attribute. Please check whether you're using a build after that date (e.g. get the latest build from the website). When you update the dll, you have to rebuild the solution to make it take effect, as wcf ria services generates code when building.

It might be you're using an early beta build of the wcf ria services which we shipped for 3.0?. If you're using vanilla v3.1 RTM, you should have the right dll and the right code.

Frans Bouma | Lead developer LLBLGen Pro
anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 24-Mar-2011 15:26:53   

I am using SD.LLBLGen.Pro.RiaSupportClasses.dll (file version 3.1.11.207, Date Modified 2/10/2011 10:32 AM)

It is both in my dll source folder and on the webapp/bin

As I described previously I am getting the original value using the ChangeSet, meaning that the column does have the roundtriporiginal, so that seems to be unrelated to this issue.

Thanks

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 24-Mar-2011 17:18:25   

Ok, we'll setup a repro and see what's the problem. If we have difficulty reproducing it, we'll ask for a simple repro case.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 25-Mar-2011 10:52:16   

I can't reproduce it.



[TestMethod]
[Asynchronous]
[Tag("UpdateAddress")]
public void UpdateAddressTestRunIdAfterFetch()
{
    var svc = new UnitTesterSvc();
    Exception error = null;
    int addressId = -1;
    svc.InsertAddressSimple(
        x =>
        {
            error = x.Error;
            addressId = x.Value;
        }, null);

    this.EnqueueConditional(() => addressId > 0);
    this.EnqueueCallback
    (
        () => { if(error != null) { throw error; } },
        () => Assert.IsTrue(addressId > 0, "AddressId should be set to a value > 0")
    );
    IEnumerable<AddressEntity> results = null;
    this.EnqueueCallback
    (
        () =>
        {
            var addressQ = svc.GetAddressesQuery(addressId, false);
            svc.Load(addressQ, 
                o =>
                {
                    results = o.Entities;
                    error = o.Error;
                }, null);
        }
    );
    this.EnqueueConditional(() => results != null);
    bool saveCompleted = false;
    this.EnqueueCallback
    (
        () =>
        {
            Assert.AreEqual(1, results.Count(), "result count should be 1");
            var address = results.First();
            Assert.AreEqual(addressId, address.AddressId, "addressid doesn't match");
            address.TestRunId = null;
        },
        () =>
        {

            svc.SubmitChanges(x => { saveCompleted = true; }, null);
        }
    );
    this.EnqueueConditional(() => saveCompleted);
    results = null;
    this.EnqueueCallback
    (
        () =>
        {
            var addressQ = svc.GetAddressesQuery(addressId, false);
            svc.Load(addressQ,
                o =>
                {
                    results = o.Entities;
                    error = o.Error;
                }, null);
        }
    );
    this.EnqueueConditional(() => results != null);
    this.EnqueueCallback
    (
        () =>
        {
            Assert.AreEqual(1, results.Count(), "result count should be 1. After save");
            var address = results.First();
            Assert.AreEqual(addressId, address.AddressId, "addressid doesn't match. After save");
            Assert.IsNull(address.TestRunId, "TestRunId should be null");
        },
        () =>
        {

            svc.SubmitChanges(x => { saveCompleted = true; }, null);
        }
    );
    this.EnqueueTestComplete();
}

I tested it with a nullable Guid, (TestRunId in this example), and it is inserted with a value, then it's updated with 'null' and persisted again and then checked whether it is indeed null.

I.o.w.: could you provide us with a ready to run SIMPLE (!) reprocase on adventureworks or northwind? thanks

Frans Bouma | Lead developer LLBLGen Pro
anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 25-Mar-2011 14:32:06   

Thanks Otis, I will do it as soon as I get a break(this may take a while). I appreciate your time and effort.

By the way, I did not see any non null value assignment to the field TestRunId, the test should have saved a non null value on TestRunId then fetch then update TestRunId to null.

Is this "var svc = new UnitTesterSvc();" a new instance of the DomainContext or is it a new DomainService, because the problem needs to be tested going across to the DomainContext and coming back to the DomainService, I know the DomainService correctly updates an entity field that is marked as changed.

Thanks.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39888
Joined: 17-Aug-2003
# Posted on: 25-Mar-2011 21:23:03   

anlebu wrote:

Thanks Otis, I will do it as soon as I get a break(this may take a while). I appreciate your time and effort.

By the way, I did not see any non null value assignment to the field TestRunId, the test should have saved a non null value on TestRunId then fetch then update TestRunId to null.

the InsertAddressSimple method does that, it's on the service, it inserts a valid TestRunId (I checked wink )

Is this "var svc = new UnitTesterSvc();" a new instance of the DomainContext or is it a new DomainService, because the problem needs to be tested going across to the DomainContext and coming back to the DomainService, I know the DomainService correctly updates an entity field that is marked as changed. Thanks.

It's a new instance of the DomainContext. So you mean: the domain context has to be destroyed after initial save, then create a new one, then update the entity for testing?

Frans Bouma | Lead developer LLBLGen Pro
anlebu
User
Posts: 10
Joined: 19-Jan-2011
# Posted on: 25-Mar-2011 22:53:30   

It's a new instance of the DomainContext. So you mean: the domain context has to be destroyed after initial save, then create a new one, then update the entity for testing?

No, that test is correct then. I will look into the test case later. Thanks