Serialization looses EntityRefs between requests

Jan 23, 2009 at 3:09 AM
Hello Matt! Nice work and congrats for the stable release

Well to start I'm using LinqEntityBase.cs on Asp .Net 3.5 and Webforms on a data centric application (lots of master-detail... catalogs) where most of the Aspx.cs web partial class stores the reference of an Entity using serialization for allowing of editing mode, creating mode, delete and read (CRUD) over that entity and its children or references, I've implemented it and tested working but then I discovered some caveats inherent to the serialization and I'd like to know if there are any know workarounds:

First is that Collections don't get serialized, I know that is because of possible circular references, a workaround I used was to persist PK's arrays (int's) on the viewstate as ArrayList types, so when I read the original collection I store each PK on the request-persistent array to work with it between requests, I would like to know if this is a good solution, anyway it is working but when I need to work with a collection member instance I have to query and instantiate the entity and cannot depend on the original collection of the parent entity, also saving a collection is a pain because I have to check if the collection has items that my request persistent array doesn't have and delete'em, also for new entities that are not on the collection have to be checked and added, then finally saved.

The second issue  is that of the discussion name, the EntityRefs are not "rehydrated" when Deserialization takes place, it only restores the values on the table that creates the Entity, but not the actual entities from the foreign key reference values, this is the big one I have dificulties since its the primary entity I'm serializing-deserializing for the data operations on the controls of the form, I dont know if making the EntityRef's fields also Request persistent by serializing-deserializing to  overcome that they are not loaded by the primary entity (foreingn key referenced).

People, I would like to hear your aproaches too on the common data scenarios (CRUD) using LinqEntityBase

Thanks for your comments

Code below
//////////////////////////////////////////////

        /// <summary>
        ///  private entity field not persisted between requests
        ///  Used when the entity has already been retrieved from the DB (or is new) and is ready for consumption on this control
        /// </summary>
        private OrdenServicio ordenServ;

        /// <summary>
        /// Request persistent entity property
        /// </summary>
        public OrdenServicio OrdenServicio
        {
                get {
                        if (IsNew)
                        {
                                if (ordenServ != null) //save db query if entity is available
                                        return ordenServ;
                                else if (Session["entityOrdenServicio" + this.ClientID] != null)
                                {
                                        ordenServ = LINQEntityBase.DeserializeEntity(Session["entityOrdenServicio" + this.ClientID].ToString(), typeof(BusinessAndData.OrdenServicio), null) as OrdenServicio;
                                        return ordenServ;
                                }
                                else
                                        return null;
                        }
                        
                        if (ordenServ != null) //save db query if entity is available
                                return ordenServ;
                        else if (Session["entityOrdenServicio" + this.ClientID] != null)
                        {
                                ordenServ = LINQEntityBase.DeserializeEntity(Session["entityOrdenServicio" + this.ClientID].ToString(), typeof(BusinessAndData.OrdenServicio), null) as OrdenServicio;
                                return ordenServ;
                        }
                        else
                        {
                                OrdenServicio = OrdenServicio.GetByID(OrdenId, true);
                                return ordenServ;
                        }

                }
                set
                {
                        if (value != null)
                        {
                                OrdenId = value.idOrdenServicio;
                                ordenServ = value;
                                Session["entityOrdenServicio" + this.ClientID] = LINQEntityBase.SerializeEntity<OrdenServicio>(value, null);
                        }
                        else{
                                OrdenId = 0;
                                ordenServ = null;
                                Session["entityOrdenServicio" + this.ClientID] = null;
                        }
                }
        }
 
        /// <summary>
        /// Persisted property used to set the internal managed entity of this Control, acts as a 2nd setter for the Entity of this control
        /// This is used by the Entity Property primarily for already DB persisted entities
        /// It is also useful to set an entity to this control just by assigning this ID (in cases where just the ID is available, on some bound controls for example.
        /// Can be set by this control or by external controls (public)
        /// </summary>
        public int OrdenId
        {
                get
                {
                        if (ViewState["OrdenId" + this.ClientID] == null || string.IsNullOrEmpty(ViewState["OrdenId" + this.ClientID].ToString()))
                        //first time that tries to get and its yet unitilialized by the setter
                        {
                                ViewState["OrdenId" + this.ClientID] = 0;
                                return 0;
                        }
                        else
                                return int.Parse(ViewState["OrdenId" + this.ClientID].ToString());
                }
                set
                {
                        ViewState["OrdenId" + this.ClientID] = value;
                }
        }


///////// Note: I have a solution based on one library where de dbml and partial classes for Logic and DataAccess stand and a Website project that controls the CRUD operations by consuming the Data access methods on the library (partial classes)
Jan 23, 2009 at 10:22 AM
Hi there,

thanks for the praise!

In answer to your questions

First Question: I assume your talking about using XMLSerializer? Sorry I haven't used collections with XMLSerializer before, regardless, is there a reason your using a XMLSerializer rather than DataContract... I realise Data Contract is for use by WCF, but works pretty well and it's pretty easy to use and doesn't required WCF.  If you have a look at my LINQ 2 SQL EB, you'll see that there's a few generic collections that get serialized in there (such as LINQEntityDetachedEntities).

Second Question: Basically when using the data contract serializer, the serialization using uni-directional serialization (i.e. one way) from parent to child.  Microsoft did it this way to avoid circular references.  I have no solution to this issue directly, but what I usually do is have my entities that I want to work with (the "model") in one list and the entities that these entities reference (possible FK entities) in another list(s) - both the model and possible FK entity lists are reference by a parent DTO (data transfer object).  When I'm passing my data round between processes (via WCF) or logical layers in the same process, I pass the DTO instead, that way I keep all the relevant information together, then my screen picklists use the FK reference list(s) information to update the model information - i never assume that these FK references have entity object attached to them, i use the values only for getting and assign the FK entities from the posible FK entities list(s) for setting the value FK value.

Hope I've helped.

Cheers

Matt.
Feb 19, 2009 at 12:28 AM
Matt and muttkid,

Regarding the Second Question above... I have successfully bidirectionally-serialized a LINQEntityBase Entity to and from Viewstate in an ASP.Net  3.5 SP1 application using Damien Guard's LINQ to SQL Templates for T4.

Here are some links to get you going with this:
  • http://damieng.com/blog/2009/01/19/linq-to-sql-templates-updated-now-on-codeplex
    • Be sure to watch Damien's brief video
  • http://l2st4.codeplex.com/
  • http://oakleafblog.blogspot.com/2008/07/bidirectional-serialization-of-linq-to.html
After you download and install the template, make the following modification to enable the bidirectional capability:
  • Open up the .tt file and look for the var option = new{ line near the top of the file
  • Find the SerializeDataContractSP1 = false option and change this to true. This basically outputs [DataContract(IsReference=true)] instead of simply [DataContract] for each entity. The IsReference argument is apparently new with .Net 3.5 SP1.
For some reason  code generated by the T4 template didn't pick up on the EntityBase attribute that Matt recommended adding manually to the dbml file, although there may be a way to modify the template to get it to do that.
In any case, here's what I did to get the LINQ to SQL T4 Template to grok the LINQEntityBase:
  • Look for this line in the .tt file:
    • <#=code.Format(class1.TypeAttributes)#>partial class <#=class1.Name#> : INotifyPropertyChanging, INotifyPropertyChanged
      • This was line 206 in the version I downloaded
    • Change it to:
      • <#=code.Format(class1.TypeAttributes)#>partial class <#=class1.Name#> : [your namespace here].LINQEntityBase, INotifyPropertyChanging, INotifyPropertyChanged

Feb 19, 2009 at 6:35 PM
Edited Feb 19, 2009 at 6:35 PM
Interesting... I just realized that Matt added IsReference=true to the [DataContract] attribute for the LINQEntityBase class in the 17306 change set. I guess you still need to apply that attribute directly to each entity class to get the bidirectional serialization to work.
Feb 19, 2009 at 6:44 PM
Edited Feb 19, 2009 at 6:46 PM
PS - Clarius has a T4 Editor that plugs into Visual Studio. Makes editing the *.tt files more palatable.

http://www.visualt4.com/downloads.html
Feb 19, 2009 at 8:48 PM
Hi there,

This sounds great!  I'll be checking this out soon (when I have some time!)

I'm not sure why DataContract(IsReference=true) works in this situation, all it does is when you have two references to the same object the first reference that gets picked up by the serializer gets serialized normally, then the second reference just includes a reference id to the first.  What used to happen before I included this is that after deserialization you would end up with two totally different copies of the entities rather than two or more references to the same entity - that's why I added it in - to stop this situation.  This is definately a good thing to have, but I don't know why it's the trigger for making it work with bi-directional, although it certainly should be better with it.

In regards to the Damien Guards LINQ to SQL T4 Template and it missing the entity base option, just send a message to Damien G on codeplex - he'll probably add it in - he probably just missed it.

Cheers

Matt.

Feb 19, 2009 at 9:22 PM
Thanks Matt.

I'll post an issue on the LINQ to SQL T4 codeplex site re: the EntityBase thing.

Let us know what brilliant deductions you come with after analyzing this new info!
May 5, 2009 at 6:09 PM
FYI -

Damien G has fixed the issue referenced above as of Change Set 22443 of the LINQ to SQL T4 Templates. You no longer need to modify the template to add the reference to LINQEntityBase.
May 27, 2009 at 11:53 AM
Edited May 27, 2009 at 11:58 AM

A couple of things: What is your real use case here? '&foo' is not a valid entity without the semicolon; do you expect the browser to treat it like one? If so, then adding the semicolon is harmless. If not, then you are asking that we either tolerate un escaped ampersands, which are *evil* in HTML like http://www.gamblingcoverage.com/ , or else raise an exception. - I could live with the exception, because the "warning" in HTML would / should be an absolute failure in XML mode: in XML, no unescaped ampersands are allowed, period - Because you are the person with the "itch", your chance of getting it "scratched" would be greatly improved if you supplied a patch with tests.