Thursday, 27 March 2008

[important: this post belongs to a whole host of other posts. Basically it's about my efforts of building a light-weight n-tier disconnected solution for Entity Framework]

Another status update. I've worked on connecting up a clean graph to a EntityFramework context and found it far from straight-forward! I ended up with very little code though, which is a good sign.

The approach of not serializing the object context, makes it harder to figure out when an object was added or removed. On the client, I keep copies of the 'original' collection and I compare the new collection with the old collection. Problem there is that it's quite possible that an object was just removed from a collection and added to another collection. How do we know we have to do a real 'delete' on it?
I have not yet figured this out ;-)

Here is some code for you to look at:

  0    Customer c = new Customer { Name = "Ruurd Boeke" };
  1    Car car1 = new Car { Make = "Saab", Customer = c };
  2    Order order1 = new Order { Amount = 2, Customer = c };
  3
  4
  5    IEditableBusinessObject eC = c as IEditableBusinessObject;
  6    string MsgOnWire = "";
  7
  8    // at this point, we are not at all attached
  9    using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
  10    {
  11     context.AddToCustomer(c);
  12     context.SaveChanges();  // this instructs context to start changetracking
  13
  14     ((IContextAware)c).CreateSerializableState(context);
  15
  16     DataContractSerializer s = new DataContractSerializer(c.GetType(), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  17
  18     MsgOnWire = s.GetWellFormedToContract(c);
  19    }
  20

The message on the wire looks like this:

<CustomerSurrogate xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="myNamespace">
    <Cars>
        <Car>
            <CarID>244</CarID>
            <Customer>
                <SerializationID>0</SerializationID>
            </Customer>
            <Make>Saab</Make>
            <SerializationID>1</SerializationID>
            <OriginalValue_CarID>244</OriginalValue_CarID>
            <OriginalValue_Customer>
                <SerializationID>0</SerializationID>
            </OriginalValue_Customer>
            <OriginalValue_Make>Saab</OriginalValue_Make>
        </Car>
    </Cars>
    <CustomerID>152</CustomerID>
    <Name>Ruurd Boeke</Name>
    <Orders>
        <Order>
            <Amount>2</Amount>
            <Customer>
                <SerializationID>0</SerializationID>
            </Customer>
            <OrderID>159</OrderID>
            <SerializationID>2</SerializationID>
            <OriginalValue_Amount>2</OriginalValue_Amount>
            <OriginalValue_Customer>
                <SerializationID>0</SerializationID>
            </OriginalValue_Customer>
            <OriginalValue_OrderID>159</OriginalValue_OrderID>
        </Order>
    </Orders>
    <SerializationID>0</SerializationID>
    <OriginalValue_Cars>
        <Car>
            <SerializationID>1</SerializationID>
        </Car>
    </OriginalValue_Cars>
    <OriginalValue_CustomerID>152</OriginalValue_CustomerID>
    <OriginalValue_Name>Ruurd Boeke</OriginalValue_Name>
    <OriginalValue_Orders>
        <Order>
            <SerializationID>2</SerializationID>
        </Order>
    </OriginalValue_Orders>
</CustomerSurrogate>

Next step is to receive this xml on the client, deserialize it and change some stuff:

  0    DataContractSerializer s2 = new DataContractSerializer(typeof(CustomerClient), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  1    CustomerClient c2 = (CustomerClient)s2.UnwrapFromString(MsgOnWire);
  2
  3    // delete
  4    c2.Cars.First().Customer = null;
  5    c2.Cars.Clear();
  6
  7    // add
  8    OrderClient order2 = new OrderClient { Amount = 100, Customer = c2 };
  9    c2.Orders.Add(order2);
  10
  11    // change
  12    c2.Name = "Changed";
  13
  14    // we have edited everything, let's get back to the server
  15    DataContractSerializer s3 = new DataContractSerializer(typeof(CustomerClient), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  16    MsgOnWire = s3.GetWellFormedToContract(c2);

This client is removing the cars. Note that we do not get the automatic hookup of relations that we used to get on the server: there is no EF Relationship manager on the client!

<CustomerSurrogate xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="myNamespace">
    <Cars />
    <CustomerID>152</CustomerID>
    <Name>Changed</Name>
    <Orders>
        <Order>
            <Amount>2</Amount>
            <Customer>
                <SerializationID>0</SerializationID>
            </Customer>
            <OrderID>159</OrderID>
            <SerializationID>1</SerializationID>
            <OriginalValue_Amount>2</OriginalValue_Amount>
            <OriginalValue_Customer>
                <SerializationID>0</SerializationID>
            </OriginalValue_Customer>
            <OriginalValue_OrderID>159</OriginalValue_OrderID>
        </Order>
        <Order>
            <Amount>100</Amount>
            <Customer>
                <SerializationID>0</SerializationID>
            </Customer>
            <SerializationID>2</SerializationID>
        </Order>
    </Orders>
    <SerializationID>0</SerializationID>
    <OriginalValue_Cars>
        <Car>
            <CarID>244</CarID>
            <Make>Saab</Make>
            <SerializationID>3</SerializationID>
            <OriginalValue_CarID>244</OriginalValue_CarID>
            <OriginalValue_Customer>
                <SerializationID>0</SerializationID>
            </OriginalValue_Customer>
            <OriginalValue_Make>Saab</OriginalValue_Make>
        </Car>
    </OriginalValue_Cars>
    <OriginalValue_CustomerID>152</OriginalValue_CustomerID>
    <OriginalValue_Name>Ruurd Boeke</OriginalValue_Name>
    <OriginalValue_Orders>
        <Order>
            <SerializationID>1</SerializationID>
        </Order>
    </OriginalValue_Orders>
</CustomerSurrogate>

We see that there are no current cars, but the original value of cars still has them. That is how I figure out to delete the cars on the server.
Did you also notice how the new order does not have a ID assigned? The server will take care of it.

  0    // we're at the server
  1    using (SimpleRelationshipTestEntities context = new SimpleRelationshipTestEntities())
  2    {
  3     DataContractSerializer s4 = new DataContractSerializer(typeof(Customer), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  4     Customer c3 = (Customer)s4.UnwrapFromString(MsgOnWire);
  5
  6     ((IContextAware)c3).AttachGraphToContext(context,
  7      delegate(object source)
  8      {
  9       return source.GetType().Name;   // our setnames correspond to our classname.
  10      });
  11
  12
  13     context.SaveChanges();
  14    }

Now, we're deserializing and attaching the graph to the context. The delete on line 7 is a way of returning entitysetnames. EF needs to know the entitysetname to be able to attach an object to a context, but there is no way for me to know it. So at this point I let you make the decision yourself.

The database is correctly filled and we are happy.

Now, I really want to check in this source, but there is a lot to be done still. The API I have created is monstrous.
Also, I think there are quite a few situations where this will break horribly.

Once I get just a little happier about it, I will do a screencast building a server using Entity Framework and a silverlight client that is editing.

Thursday, 27 March 2008 17:20:28 (Romance Standard Time, UTC+01:00)  #    Comments [10]  |  Trackback
 Monday, 24 March 2008

Found through Julie's blog: a new tool was released on Code Gallery from the EF Team, called EF Mapping Helper.

It will help you play around with mappings and see what the result would be in the corresponding CSDL, SSDL and MSL generated xml.

Read more on Julie's blog and quickly download the tool!

Monday, 24 March 2008 17:08:14 (Romance Standard Time, UTC+01:00)  #    Comments [12]  |  Trackback
 Friday, 21 March 2008

I'm working hard on bringing a couple of my projects together that will enable domainobjects on the client without Entity Framework references. I thought I'd give a small update on how that's working out for me.

The endresult

What I want is one visual studio project: 'Domain' that holds business objects for me that I can use on both the client and the server. What I do not want, is to have to think about EntityFramework contexts on the client. Since I do not persist my objects on the client, I see no reason to actually create a context on the client, just to be able to do changetracking. Nor do I want to see any EF specific plumbing in the messages between that client and server: I lose interoperability if I do that. Also, if my client is Silverlight (for instance) I might not want to bring the EF assemblies over (if that's even possible?).

So, the endresult should be an easy way to serialize and deserialize objects on the client and server, and a way to build up the object correctly on the server so it can be attached to the context again.

Please check Daniel's EntityBag sample for a very sophisticated way of serializing your context. He does go down the route of serializing the objectcontext, which does mean you need the entityframework on the client. Read about his motivations for doing this here.

Let's see it

I'll first show some code and then walk you through it.

  0    using (OneSimpleType.OneSimpleTypeConnection context = new OneSimpleType.OneSimpleTypeConnection())
  1    {
  2     // clear out database
  3     foreach (OneSimpleType.Person old in context.Person)
  4     {
  5      context.DeleteObject(old);
  6     }
  7     context.SaveChanges();
  8    }
  9
  10    OneSimpleType.Person p = new OneSimpleType.Person { Firstname = "Ruurd", Lastname = "Boeke" };
  11    IEditableBusinessObject ep = p as IEditableBusinessObject;
  12    string MsgOnWire = "";
  13
  14    using (OneSimpleType.OneSimpleTypeConnection context = new OneSimpleType.OneSimpleTypeConnection())
  15    {
  16     context.AddToPerson(p);
  17     context.SaveChanges();  // at this point, there should be 'original values'
  18
  19     // the server is changing the object without yet saving
  20     p.Firstname = "ServerChanged";
  21
  22     // now we need to create a version of the object that can be serialized well
  23     p = (OneSimpleType.Person)context.CreateSerializableVersion(p);
  24
  25     DataContractSerializer s = new DataContractSerializer(p.GetType(), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  26
  27     MsgOnWire = GetWellFormedToContract(p, s);
  28
  29    }
  30
  31    // imagine we are the client getting a message and deserializing it
  32    DataContractSerializer s2 = new DataContractSerializer(typeof(OneSimpleType.Person), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  33    StringReader reader = new StringReader(MsgOnWire);
  34    XmlReader xreader = XmlReader.Create(reader);
  35    OneSimpleType.Person p2 = (OneSimpleType.Person)s2.ReadObject(xreader);
  36
  37    // edit something on the client
  38    p2.Lastname = "ClientChanged";
  39
  40
  41    // we have edited everything, let's get back to the server
  42    s2 = new DataContractSerializer(typeof(OneSimpleType.Person), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  43    MsgOnWire = GetWellFormedToContract(p2, s2);
  44
  45    // we're at the server
  46    using (OneSimpleType.OneSimpleTypeConnection context = new OneSimpleType.OneSimpleTypeConnection())
  47    {
  48     DataContractSerializer s3 = new DataContractSerializer(typeof(OneSimpleType.Person), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  49     StringReader reader3 = new StringReader(MsgOnWire);
  50     XmlReader xreader3 = XmlReader.Create(reader3);
  51     OneSimpleType.Person p3 = (OneSimpleType.Person)s3.ReadObject(xreader3);
  52
  53     context.AttachedDeserializedVersion(p3, "Person");
  54
  55     context.SaveChanges();
  56    }
  57

We start out on the serverside.
On line 17, we have just saved an object to the database. 
We continue using that (attached, and thus changetracked) object and change a property (just for the fun of it).
On line 27 we serialize it like you've seen me do in previous posts.

On line 32, we pretend to be on the client, which uses the message to deserialize to a compatible type. Note, that this type does not need to be 'IPoco' enhanced. It does need to be enhanced for serialization and editing.

We change a property 'on the client' on line 38. Notice that this object does not have a context attached at all.
At line 43 we have once again serialized this new object and on line 46 we pretend to be on the server again.

There we create a new context and deserialize our message to an object. Then we call an extension method of context to attach this version to the context. Note that I have to pass the setname (working on somehow getting to this information differently).
Finally we save.

Only the changed properties are flagged as dirty, so efficient SQL can be generated by EF.

The extension method is shown here:

  0   public static void AttachedDeserializedVersion(this ObjectContext context, object source, string setname)
  1   {
  2    // TODO: how to get setname from metadata or something
  3
  4    Debug.Assert(source is IEntityWithKey);
  5    Debug.Assert(source is IEditableBusinessObject);
  6
  7    IEditableBusinessObject eS = (IEditableBusinessObject)source;
  8
  9    // we go to the loaded state
  10    eS.SetReadMode(ReadMode.Loaded);
  11
  14    string fullEntitySetName = context.DefaultContainerName + "." + setname;
  15
  16    EntityKey createdKey = context.GetEntityKey(fullEntitySetName, source);
  17    ((IEntityWithKey)source).EntityKey = createdKey;
  18
  19    // attach this version
  20    context.Attach((IEntityWithKey)source);
  21
  22    eS.ReplayLoadedToNormal();
  23
  24    eS.SetReadMode(ReadMode.Normal);
  25
  26
  27   }

You see I have quite a bit of functionality in the IEditableBusinessObject interface.

The deserialized version of the object has a 'loaded' state (or better: original state) and a 'current' state. Before attaching at line 20, I first set my object to be in 'original' mode. Then I have a replay method that will compare the current values to the loaded versions and touch the setters of the properties of the changed versions. This notifies the changetracker of EF to flag a property as dirty.

This all works fine, but both code and api are pretty crude. I have no complextype or relationshiptype support, but that will come, if I don't run into major problems along the way.

I hope you are as excited about this as I am. Let me know!

Friday, 21 March 2008 18:21:28 (Romance Standard Time, UTC+01:00)  #    Comments [2]  |  Trackback
 Monday, 17 March 2008

I'll probably dedicate a bigger post to this soon, but I wanted to show you a domainmodel, some code and the xml it generates.
What you see here is two attributes that take on quite a bit of work.
EditableBusinessObject implements IEditableObject for you and also allows you to copy the currentvalues to a loadedvalues state.
CreateSerializeSurrogate generates a surrogate class that knows how to deal with loadedvalues and with circular references.
Together, they form the backbone of the client-side of your domainmodel.

Here is an example.

DomainModel:
(notice this is the full class, no other properties are here)

  0 [EditableBusinessObject]
  1 [CreateSerializeSurrogate]
  2 [Serializable]
  3 [DataContract(Namespace = "myNamespace", Name = "Person")]
  4  public class Person
  5 {
  6   [DataMember]
  7   public int IntProperty { get; set; }
  8
  9   [DataMember]
  10   public string StringProperty { get; set; }
  11
  12   [DataMember]
  13   public string StringProperty2 { get; set; }
  14
  15   int neverSetInt;
  16   [DataMember]
  17   public int NeverSetInt
  18   {
  19    get
  20    {
  21     return neverSetInt;
  22    }
  23    set { neverSetInt = value; }
  24   }
  25
  26   [DataMember]
  27   public List<string> StringLijst { get; set; }
  28
  29   [DataMember]
  30   public List<int> IntLijst { get; set; }
  31
  32   public Person()
  33   {
  34    StringLijst = new List<string>();
  35    IntLijst = new List<int>();
  36   }
  37
  38 }
  39

Some testcode:
(notice line 16 where we do a endEdit. If we had cancelled, we would've had a proper rollback)

  0    Person p = new Person();
  1    p.IntProperty = 1;
  2    p.StringProperty = "Ruurd";
  3    p.StringProperty2 = "Boeke";
  4
  5    p.StringLijst.Add("a");
  6    p.IntLijst.Add(1);
  7
  8    (p as IEditableBusinessObject).CopyCurrentToLoaded();
  9
  10    (p as IEditableBusinessObject).BeginEdit();
  11
  12    p.IntLijst.Add(2);
  13    p.StringLijst.Add("b");
  14    p.StringProperty = "Ruurd Boeke";
  15
  16    (p as IEditableBusinessObject).EndEdit();
  17
  18    DataContractSerializer s = new DataContractSerializer(p.GetType(), null, int.MaxValue, false, false, new SubstituteDomainDataContractSurrogate());
  19
  20    string outMessage = GetWellFormedToContract(p, s);

And the generated xml:
(notice how the lists and 'StringProperty' are changed, and see the original value).

<PersonSurrogate xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="myNamespace">
    <IntLijst xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <d2p1:int>1</d2p1:int>
        <d2p1:int>2</d2p1:int>
    </IntLijst>
    <IntProperty>1</IntProperty>
    <SerializationID>0</SerializationID>
    <StringLijst xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <d2p1:string>a</d2p1:string>
        <d2p1:string>b</d2p1:string>
    </StringLijst>
    <StringProperty>Ruurd Boeke</StringProperty>
    <StringProperty2>Boeke</StringProperty2>
    <OriginalValue_IntLijst xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <d2p1:int>1</d2p1:int>
    </OriginalValue_IntLijst>
    <OriginalValue_IntProperty>1</OriginalValue_IntProperty>
    <OriginalValue_StringLijst xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <d2p1:string>a</d2p1:string>
    </OriginalValue_StringLijst>
    <OriginalValue_StringProperty>Ruurd</OriginalValue_StringProperty>
    <OriginalValue_StringProperty2>Boeke</OriginalValue_StringProperty2>
</PersonSurrogate>

What is left, is deserializing, maybe propagating the beginedit commands to all children and then create the serverside EF variant.
Oh, and I don't like the originalValue representation. Maybe I'll generate a OriginalValue class to keep it tidy.

Monday, 17 March 2008 22:28:31 (Romance Standard Time, UTC+01:00)  #    Comments [11]  |  Trackback