Serializing and Deserializing

Posts   
 
    
SteelRat
User
Posts: 12
Joined: 11-Nov-2007
# Posted on: 11-Nov-2007 21:12:37   

**Version : **LLBLGen Pro Version 2.5 Final (October 25th, 2007) Runtime : 2.5.7.1019 **Template : **Adapter + SQL Specific Database : SQL Server 2000

If I fetch an entity from the database, serialize it using its WriteXML method and then deserialize it using its ReadXML method it causes the DBValue for nullable fields in the database which contain an empty string to show as null when the aren't actually null. As a result the update statement when I save the entity will needlessly update these fields from empty string to empty string.

E.g.


PropertyEntity p = new PropertyEntity(3);
DataAccessAdapter daa = new DataAccessAdapter([connection_string]);
daa.FetchEntity(p);
string xml;
p.WriteXml(out xml);

bool dbvIsNull = (p.Fields[(int)PropertyFieldIndex.DependantThoroughfare].DbValue == null);
Console.WriteLine(dbvIsNull);

p = new PropertyEntity();
p.ReadXml(xml);

dbvIsNull = (p.Fields[(int)PropertyFieldIndex.DependantThoroughfare].DbValue == null);
Console.WriteLine(dbvIsNull);

If the field DepandantThoroughfare in the database which is a nullable varchar(60) field contains an empty string the above code will output :

false true

Is this by design and is there any way I can control or modify this behaviour (other than getting rid off all the empty strings in nullable fields in my database) ?

Thanks.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 12-Nov-2007 10:04:00   

Does the field's IsDirty flag changes before and after deserialization?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 12-Nov-2007 12:02:52   

The problem occurs because the XML's representation of a value that's null and a value that's an empty string is the same: <DbValue Type="System.String"/>

(i.o.w.: there's no data, so it's null). There's no other option for strings, as there's no 'magic' value to use here, as XML is text based.

However, there is indeed something to improve on our side: In Compact and Compact25 formats, there IS a way to determine if the element was just empty because the value was an empty string, or because the value was null (undefined).

In Compact format, the element is <DbValue/>, and if it's set to null, the element is: <DbValue type="System.String"/>.

In Compact25 format, it's even easier: if the element is there, it's not null, as null valued fields aren't emitted into the xml.

I'll make sure the code obeys these rules. It might be that code might fall over due to this, but I think it's justified to implement this fix, as it's a bug.

Frans Bouma | Lead developer LLBLGen Pro
SteelRat
User
Posts: 12
Joined: 11-Nov-2007
# Posted on: 12-Nov-2007 12:16:13   

Walaa, no the IsNew and IsDirty properties of the entity are both false which is exactly what I would expect.

Otis, the Compact Format sounds like an excellent improvement. Any idea of the timescale for its incorporation into a release ?

I am currently evaluating LLBLGen for use in a large project and so far it looks like a winner. I've been very impressed with it in terms of how quickly I could get up and running and how unintrusive it is. I also like the fact that it doesn't try to abstract me too completely from the database. The (adapter-based) entities will mostly be used via Enterprise Services but will also need to be exposed via WCF.

Do you have any guidelines, best-practices or advice to offer when using LLBLGen with WCF ?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 12-Nov-2007 13:18:30   

SteelRat wrote:

Otis, the Compact Format sounds like an excellent improvement. Any idea of the timescale for its incorporation into a release ?

Within an hour or so.

I am currently evaluating LLBLGen for use in a large project and so far it looks like a winner. I've been very impressed with it in terms of how quickly I could get up and running and how unintrusive it is. I also like the fact that it doesn't try to abstract me too completely from the database. The (adapter-based) entities will mostly be used via Enterprise Services but will also need to be exposed via WCF.

cool! simple_smile

Do you have any guidelines, best-practices or advice to offer when using LLBLGen with WCF ?

Don't use DataContracts if you want to pass entities across the wire, but use ServiceContracts, as described in the manual. If you want to have clients which aren't aware of LLBLGen Pro, you should pass DTO's (data transfer objects) and then you can use datacontracts. To fill them, use projection fetches, which are projected onto classes.

LLBLGen Pro v2.5 has very compact XML serialization, so throughput should be optimal with entities (and you keep change tracking! simple_smile ). If you can use remoting, you can opt for that too, our remoting code is very fast and also gives very small datablocks.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 12-Nov-2007 13:39:02   

Analysing it a bit more, the bug's fix will be a breaking change for Normal (Verbose) and Compact formats. Therefore it's not fixed for these formats. The reason is that 'CurrentValue' also is affected, and this can have serious consequences for existing projects, hence we're not going to fix that now. The fix for this is postponed till v2.6, which we'll use to fix issues which cause a breaking change.

Webservices and WCF use Compact25 format, which is much more compact. It will be fixed in that format only.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 12-Nov-2007 13:54:06   

FIxed in next build. I'll attach a new build to this post within 15 minutes or so. New build has been attached to this post (click paperclip)



[Test]
public void SerializeDeserializeSingleEntityXmlDbValueWithEmptyString()
{
    ShipperEntity toTest = new ShipperEntity(15);
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        adapter.FetchEntity(toTest);
    }

    Assert.AreEqual(string.Empty, toTest.Fields[(int)ShipperFieldIndex.Phone].DbValue);
    Assert.AreEqual(string.Empty, toTest.Fields[(int)ShipperFieldIndex.Phone].CurrentValue);
    Assert.IsFalse(toTest.Fields[(int)ShipperFieldIndex.Phone].IsChanged);
    Assert.IsFalse(toTest.IsDirty);

    string xml = string.Empty;
    ShipperEntity clone = null;

    toTest.WriteXml(XmlFormatAspect.Compact25, out xml);
    StreamWriter writer = new StreamWriter("Shipper.xml");
    writer.Write(xml);
    writer.Close();

    clone = new ShipperEntity();
    clone.ReadXml(xml);
    Assert.AreEqual(string.Empty, clone.Fields[(int)ShipperFieldIndex.Phone].CurrentValue);
    Assert.AreEqual(string.Empty, clone.Fields[(int)ShipperFieldIndex.Phone].DbValue);
    Assert.IsFalse(clone.Fields[(int)ShipperFieldIndex.Phone].IsChanged);
    Assert.IsFalse(clone.IsDirty);

    toTest.Phone = null;
    toTest.WriteXml(XmlFormatAspect.Compact25, out xml);
    clone = new ShipperEntity();
    clone.ReadXml(xml);
    Assert.AreEqual(null, clone.Fields[(int)ShipperFieldIndex.Phone].CurrentValue);
    Assert.AreEqual(string.Empty, clone.Fields[(int)ShipperFieldIndex.Phone].DbValue);
    Assert.IsTrue(clone.Fields[(int)ShipperFieldIndex.Phone].IsChanged);
    Assert.IsTrue(clone.IsDirty);
}

Frans Bouma | Lead developer LLBLGen Pro
SteelRat
User
Posts: 12
Joined: 11-Nov-2007
# Posted on: 12-Nov-2007 14:57:48   

Wow. That was a quick fix. Thanks. It works just fine.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 12-Nov-2007 15:07:34   

SteelRat wrote:

Wow. That was a quick fix. Thanks. It works just fine.

simple_smile We're quick simple_smile , and 1 line of code shouldn't take that long wink

Glad it's solved

Frans Bouma | Lead developer LLBLGen Pro
SteelRat
User
Posts: 12
Joined: 11-Nov-2007
# Posted on: 12-Nov-2007 15:21:55   

For WCF I am not passing any entities across the wire.

For each entity I create a wrapper class that represents the data contract for that entity and it exposes only those properties of the entity that I want in that particular contract.

This also helps keep my entity code completely free of .NET 3.0 references as we still have a large installed base of Windows 2000 workstations which cannot use .NET 3.0. ( I know, scary isn't it frowning ).

Here is a very cut down version of my Property Entity

public partial class PropertyEntity : CommonEntityBase, ISerializable
{

    // ...other stuff here...

    public virtual System.Int32 PropertyId
    {
        get { return (System.Int32)GetValue((int)PropertyFieldIndex.PropertyId, true); }
        set { SetValue((int)PropertyFieldIndex.PropertyId, value); }
    }
    
    public virtual System.String AddressLine1
    {
        get { return (System.String)GetValue((int)PropertyFieldIndex.AddressLine1, true); }
        set { SetValue((int)PropertyFieldIndex.AddressLine1, value); }
    }
    
    public virtual System.Byte Bedrooms
    {
        get { return (Nullable<System.Byte>)GetValue((int)PropertyFieldIndex.Bedrooms, false); }
        set { SetValue((int)PropertyFieldIndex.Bedrooms, value); }
    }
    
    // ...other stuff here...
}

In my WCF Service Library I have a Data Contract wrapper like this :

[DataContract]
public class XaverProperty : IExtensibleDataObject
{
    private PropertyEntity _Entity;
    public PropertyEntity Entity
    {
        get { return _Entity; }
        set 
        { 
            this._Entity = value;
            this._Entity.WriteXml(XmlFormatAspect.Compact25, out this._OriginalState);
        }
    }

    public XaverProperty()
    {
        this.Initialize();
    }

    public XaverProperty(PropertyEntity p)
    {
        this._Entity = p;
        this._Entity.WriteXml(XmlFormatAspect.Compact25, out this._OriginalState);
    }

    private void Initialize()
    {
        this._Entity = new PropertyEntity();
        this._Entity.WriteXml(XmlFormatAspect.Compact25, out this._OriginalState);
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext sc)
    {           
        this.Initialize();
    }

    private string _OriginalState = string.Empty;
    [DataMember(Order=1)]
    public string OriginalState
    {
        get { return this._OriginalState; }
        set 
        {
            this._OriginalState = value;
            this._Entity = new PropertyEntity();
            this._Entity.ReadXml(this._OriginalState);
        }
    }
    
    [DataMember(Order=2)]
    public string AddressLine1
    {
        get { return this._Entity.AddressLine1; }
        set { this._Entity.AddressLine1 = value; }
    }
    
    [DataMember(Order=2)]
    public int PropertyId
    {
        get { return this._Entity.PropertyId; }
        set { this._Entity.PropertyId = value; }
    }
    
    
    [DataMember(Order=2)]
    public byte Bedrooms
    {
        get { return this._Entity.Bedrooms; }
        set { this._Entity.Bedrooms = value; }
    }
    
    // ...other stuff here...   
}

It exposes some of the properties of PropertyEntity as part of the DataContract and also a string field called OriginalState which will always be Serialized and Deserialized first.

In my service contract I have GetProperty(int propertyID) and SaveProperty(Property p).

When GetProperty is called it returns a Property data contract which also contains the original entity's complete state. When SaveProperty is called the original state is deserialized into the Entity class first by the DataContractSerializer and then each of the remaining DataMember fields is set. This means that the Property object I receive contains a PropertyEntity that reflects the exact changes the user has made as if they had made them to a local or MarshallByRef object I can use all the right IAuditors, IAuthorizers and IValidators to manage the changes as the DataContractSerializer makes them.

I may look at encrypting the OriginalState plus some other features but this is my general approach at the moment.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39908
Joined: 17-Aug-2003
# Posted on: 13-Nov-2007 18:24:12   

Sounds good. It's more work due to the change tracking that's not there. You can fill these objects with projections simple_smile

Frans Bouma | Lead developer LLBLGen Pro
SteelRat
User
Posts: 12
Joined: 11-Nov-2007
# Posted on: 16-Nov-2007 23:01:13   

Thanks for the tip. I need to read up on projections and how they work. There's so much "stuff" in LLBLGen but it's all GOOD !

BTW, I am doing a second prototype using Enterprise Services. I am creating a server using a serviced component that stores and retreives entities.

It all works great as a Library but as a Server I get a lot of errors about assemblies not being found etc. It's probably due to me not having the right assemblies signed and in the GAC but are you aware of any particular bear-traps, pitfalls or gotchas when passing entities via Enterprise Services or with configuring LLBLGen to work with ES ?

SteelRat
User
Posts: 12
Joined: 11-Nov-2007
# Posted on: 16-Nov-2007 23:23:55   

Problem solved. It was, as I suspected, missing assemblies from the GAC. It wanted the LLBLGen assemblies in there too.