1

I need to make all my entities serializable. So I was thinking in a BaseEntity with a Backup and a Restore method. But in the restore I can't override the object with the saved one because this is read-only.

Any solution or some other way to get the serializable entities?

My code:

internal class BaseEntity
{
    private MemoryStream ms = new MemoryStream();
    private BinaryFormatter bf = new BinaryFormatter();

    public void Backup()
    {
        bf.Serialize(ms, this);
    }

    public void Restore()
    {
        this = (BaseEntity)bf.Deserialize(ms);
    }
}
Diego
  • 16,436
  • 26
  • 84
  • 136
  • 3
    small point I would rename your class, it will be confusing in the future to not only you but others that come to work on the code. I immediately assumed this was an interface. – Mike Miller Jun 14 '11 at 21:04
  • @Mike Miller: you are right, I'm sorry, I started with an interface and then changed it. forgot to rename it. Thanks!! – Diego Jun 14 '11 at 21:05
  • (should I even bother with my usual anti-`BinaryFormatter` rant?) – Marc Gravell Jun 14 '11 at 21:22
  • @Marc Gravell: could you please provide some links of why not using BinaryFormatter and how to replace it? – Diego Jun 15 '11 at 10:34
  • @Diego sure, [see this answer](http://stackoverflow.com/questions/703073/what-are-the-deficiencies-of-the-built-in-binaryformatter-based-net-serializatio). I'm probably biased, but I use protobuf-net (ok, I also wrote it), which addresses ***all*** of these issues. – Marc Gravell Jun 15 '11 at 10:58
  • @Marc Gravell: Thanks!! I'll use your answer as a reference on this subject – Diego Jun 15 '11 at 14:21

4 Answers4

2

The more common pattern is to not make it the responsibility of your objects to serialize/deserialize themselves; rather, use an external serializer:

var serializer = new DataContractJsonSerializer(typeof(YourClass));
var stream = ...;
YourClass yourObj = ...;

serializer.WriteObject(stream, yourObj);

var restoredObj = serializer.ReadObject(stream);
Jacob
  • 77,566
  • 24
  • 149
  • 228
  • +1 - this approach makes more sense to me. although not directly answering the question it adds value by offering an alternative (which was asked for). – Mike Miller Jun 14 '11 at 21:11
  • This solution may be better in some ways, but its not what I needed. Thanks anyway! – Diego Jun 15 '11 at 17:08
1
public IEntidadBase Restore()
{
    return (IEntidadBase)bf.Deserialize(ms);
}

@jacklondon how would you do EntitySerializer methods?

You can do serialization process with http://www.servicestack.net/ StackService.Text module for clean entities. You don't need any attribute (serializable/datacontract) in ms way.

 public class EntityFoo
    {
        public string Bar { get; set; }

        public EntityFoo (string bar)
        {
            Bar = bar;
        }
    }

    public class EntityDumper //and the EntitySerializer
    {
        public static string Dump<T> (T entity)
        {
            return new TypeSerializer<T> ().SerializeToString (entity);
        }

        public static T LoadBack<T> (string dump)
        {
            return new TypeSerializer<T> ().DeserializeFromString (dump);
        }
    }



    public class dump_usage
    {
        public void start ()
        {
            string dump = EntityDumper.Dump (new EntityFoo ("Space"));

            EntityFoo loaded = EntityDumper.LoadBack<EntityFoo> (dump);
            Debug.Assert (loaded.Bar == "Space");
        }
    }
jack-london
  • 1,599
  • 3
  • 21
  • 42
  • 1
    Wouldn't it need to be static to be useful? – Jouke van der Maas Jun 14 '11 at 21:03
  • @jacklondon: Yes, that could be a solution, but I'd prefer (if possible) that the serialization be contained in the entity. This way, from outside the entity class I'd have to do: `myEntity = myEntity.Restore();` and that is not really nice. – Diego Jun 14 '11 at 21:04
  • If each entity serializes itself that method could not be static, Am i wrong? – Diego Jun 14 '11 at 21:07
  • I think creating an EntitySerializer would be a better solution. You're giving too many responsibility to your entity. It's illegal for srp. – jack-london Jun 14 '11 at 21:08
  • EntitySerializer? got an msdn link? – Mike Miller Jun 14 '11 at 21:12
  • microsoft has two serializer xmlserializer (and new one datacontractserializer), binaryserializer. http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx, http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx. EntitySerializer; i mentioned, is a custom class that encapsulates the serialization process. – jack-london Jun 14 '11 at 21:22
  • @jacklondon how would you do EntitySerializer methods? – Diego Jun 15 '11 at 10:37
1

Edit: One way serialization can work is to use the System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (or other implementation of IFormatter). To serialize an object you pass the object and a stream. To Deserialize the object, you pass a stream (positioned at the begining of your serialized data), and it returns the serialized object and all its depenedencies.

public static class EntityBackupServices
{
   public static MemoryStream Backup (BaseEntity entity)
   {
      var ms = new MemoryStream();
      Serialize (ms, entity);
      ms.Position = 0;
      return ms;
   }
   public static void Serialize (Stream stream, BaseEntity entity)
   {
      var binaryFormatter = new BinaryFormatter();
      binaryFormatter.Serialize (stream, entity);
   }
   public static BaseEntity Restore (Stream stream)
   {
      var binaryFormatter = new BinaryFormatter();
      var entity = (BaseEntity) binaryFormatter.Deserialize (stream);
      return entity;
   }
}

One thing a formatter don't do (though the FormatterServices class makes it possible) is modify existing objects. So you probably don't want to have an instance method called Deserialize. You can't really do this: new LionEntity().Deserialize () where it replaces the fields of an existing instance.

Note: You'll need to put Serializable over all your types. Any fields that can't be serialized (because it's either not a struct, or it's not marked as [Serializable] will need to be marked with NonSerialized.

// A test object that needs to be serialized.
[Serializable()]        
public class BaseEntity
{
    public int member1;
    public string member2;
    public string member3;
    public double member4;

    // A field that is not serialized.
    [NonSerialized()] public MyRuntimeType memberThatIsNotSerializable; 

    public TestSimpleObject()
    {
        member1 = 11;
        member2 = "hello";
        member3 = "hello";
        member4 = 3.14159265;
        memberThatIsNotSerializable = new Form ();
    }

    public MemoryStream Backup ()
    {
       return EntityBackupServices.Backup (this);
    }
}

Edit: The way I've mentioned is a rather standard and accepted way. If you want to venture into hackdom, you can deserialize the object the way I've mentioned, then use reflection to set each field on your existing object to the value of the deserialized object.

public class BaseEntity
{
   void Restore(Stream stream)
   {
      object deserialized = EntityBackupServices.RestoreDeserialize(stream);//As listed above
      if (deserialized.GetType () != this.GetType ())
         throw new Exception();
      foreach (FieldInfo fi in GetType().GetFields())
      {
         fi.SetValue(this, fi.GetValue (deserialized));
      }
   }
}
Tiago Gouvêa
  • 15,036
  • 4
  • 75
  • 81
agent-j
  • 27,335
  • 5
  • 52
  • 79
  • And still, how would you do the serialization without understanding the properties of the class? (So that it can be moved to a super class). – Diego Jun 15 '11 at 10:41
  • Your example shows the BinaryFormatter (from System.Runtime.Serialization.Formatters.Binary, I assume). The BinaryFormatter is capable of deserializing any struct and any class marked as [Serializable]. To do this, you call binaryFormatter.Deserialize(myStream). I'll update my answer to show how the base class should /not/ do this. – agent-j Jun 15 '11 at 12:54
  • In your example the methods are static and return / receive some entity. I can also do that in my BaseEntity class, the problem starts when I want to do it as simple as: `entity.Backup()` and `entity.Restore()` – Diego Jun 15 '11 at 13:44
  • I edited my post to provide a different solution more in line with your desires. – agent-j Jun 15 '11 at 15:52
0

I don't necessarily recommend this, but here is one pattern for an object that can persist and restore its own state using serialization that creates new instances:

public sealed class MyClass
{
    private Data _data = new Data();

    //Properties go here (access the public fields on _data)

    public void Backup()
    {
        //Serialize Data
    }

    public void Restore()
    {
        //Deserialize Data and set new instance
    }

    private sealed class Data
    {
        //Public fields go here (they're private externally [because Data is private], but public to MyClass.)
    }
}

Note that this only works if your serializer supports non-public classes. Worst-case, you have to make the nested class public, which is ugly, but doesn't hurt encapsulation (since the instance is private).

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • Please correct me if I'm wrong but I think your model suggest that I save property by property into data. I'd like to have a super class which gives all its "son-classes" the methods used to serialize, so this super class needs to know nothing about the entity it is serializing. – Diego Jun 15 '11 at 10:40
  • @Diego, that's correct, this is strictly one way of allowing deserialization to a particular existing instance, but using serialization systems that create new instances. I don't actually recommend doing this (I think it makes much more sense for serialization to be the responsibility of another class), but this is a pattern that accomplishes part of what you're after. – Dan Bryant Jun 15 '11 at 13:27