UnitOfWork2 and .NET Remoting

Posts   
 
    
Aion
User
Posts: 8
Joined: 09-Nov-2007
# Posted on: 25-Nov-2007 15:26:41   

HI

After reading all the remoting threads I could find in here I desided to use UnitOfWork for sending changes back to the server.


public class Service : MarshalByRefObject
{
  public void CommitChanges(UnitOfWork2 uow)
  {
    uow.Commit(dda);
  }
}

When I send the changes to my entities to the server I'm not done with the entities on the client side. So I need them to be updated. Or at least marked as unchanged. If I was not using remoting I could get this easily since the UnitOfWork could do the refresh for me. But using remoting I can not see how this can be done easily.

Just to get the "unchanged" behavior I have tried to set IsNew and IsDirty to false for all entities on client side after an update but this is apperently not enough.

Of cause I could just refetch everything but it seems silly to waste the bandwith when I only have changed a few entries in the entire object graph. Also fetching everything ruins my guistate since many different entities are bound to different gui elements so a refetch will force me to manually go trugh all these an rebind them manually.

Thanks in advance,

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 25-Nov-2007 18:59:43   

Hi Aion, I think the flag **MarkSavedEntitiesAsFetched **would be useful in this case. This was added in v2.5.

LLBLGenPro wrote:

It's now possible to mark entities which are saved directly as 'Fetched' when they're not re-fetched. By default entities which aren't refetched and which are saved successfully will get the state 'OutOfSync'. To mark any successfully saved entity as 'Fetched', add an appSettings element which adds the key "markSavedEntitiesAsFetched" with a value 'true', or set the EntityBase(2).MarkSavedEntitiesAsFetched static property to true.

LLBLGenPro wrote:

However setting this setting to true can also cause getting the entity out of sync with the database because another thread has updated the same entity data. Use with care. It's recommended to leave it on its default value: false and only set this to true if you're sure the data in-memory reflects the entity data in the database.

David Elizondo | LLBLGen Support Team
Aion
User
Posts: 8
Joined: 09-Nov-2007
# Posted on: 25-Nov-2007 20:01:04   

Hi David

I'm not sure this flag can help me.

The problem is that when I remote the UnitOfWork the entities are marked as fetched on the server but not on the client.

E.g. when the client do the following

UnitOfWork2 uow = new UnitOfWork2();
uow.AddForSave(myChangedEntity, null, true, true);
service.CommitChanges(uow); // remoted call

the UnitOfWork will be serilized and sent to the server by value. Now the server commits the UnitOfWork which automatically refreshes the entities (or just mark them as fetched depending on flags). But since the UnitOfWork is call by vaue the entities on the client are not changed at all and will still have the same status as before the commit.

Currently I'm using a hack where the server returns the updated entities as a IEntityCollection2. On the client I then use this information to update the copy on the client



  IEntityCollection2 refreshed = service.CommitChanges(uow);

  ObjectGraphUtils ogu = new ObjectGraphUtils();
  List<IEntity2> list = ogu.ProduceTopologyOrderedList(myRootEntity);
  foreach(IEntity2 entity in list)
  {
    foreach(IEntity2 refreshedEntity in refreshed)
    {
      if(refreshedEntity.ObjectID == entity.ObjectID)
      {
        entity.Fields = refreshedEntity.Fields;
      }
    }
    entity.IsDirty = false;
    entity.IsNew = false;
  }
}


I guess my problem is a general issue when using remoting. So I was hoping there would be a more clean way of doing it than my hack.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 26-Nov-2007 11:26:44   
Aion
User
Posts: 8
Joined: 09-Nov-2007
# Posted on: 26-Nov-2007 11:42:34   

Thanks I have seen that thread. This was where I got the idea to my hack.

The reason I dont like the hack is that it might break in later editions of LLBLGEN. E.g. if more stuff is added to the entity not directly under Fields.

I understand that it can be solved by regetting all data and starting from the beginning. But this is not always the best solution.

Since remoting and webservices probably will be used more and more in database applications it would be nice if LBLGEN would support refetching over remoting in a general way.

Anyway, thanks for a great product

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39903
Joined: 17-Aug-2003
# Posted on: 26-Nov-2007 12:15:03   

You're sending copies over the wire to the service. So the entities at the client aren't affected in any way what happens at the service. So if you want to work with them after that, you either have to manually mark them as unchanged or fetch the data again.

It's often not required to mark the entities as sending changes to the service is often the last step in a process which then either ends or starts again with fresh data.

The reason that refetching might be preferable is that you otherwise run the risk of having data on the client which isn't reflecting the state in the db.

Frans Bouma | Lead developer LLBLGen Pro
Aion
User
Posts: 8
Joined: 09-Nov-2007
# Posted on: 01-Dec-2007 15:47:27   

How do you manually mark the rows as fetched. Acording to

http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=5676&HighLight=1

there is a bunch of stuff to do. Is there not an easier way?

What about a AcceptChanges call as in DataTable?

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 03-Dec-2007 09:15:39   

It depends on how are you going to use the entities afterwards, in many cases you may only need to set entity.Fields.State = EntityState.Fetched

Also since the service methods returns a collection of the refreshed/modified entities, then you may use a Context at the client side and update the entities within with the newly refreshed ones.

Aion
User
Posts: 8
Joined: 09-Nov-2007
# Posted on: 03-Dec-2007 22:58:51   

I need the entries to be in a state excactly as they where just fetched. I don't understand why you do not supply a AcceptChanges() function that will do excactly that. Espcecially since it is difficult to get the entries refreshed when one is using remoting.

I do not see how the context will work. As far as I understand the context works by prim. key. But if the pri. key is an Identity a newly created entity will not be matched on refetch.

I really think it is a shame it is so difficult to make the following basic senario work with LLBLGEN:

1) Client asks server (via remoting) for a specific customor and all his orders. 2) The server sends a graph with the customer entity as a root and the orders in the Order collection of the customer entity. 3) Client modifies a few orders and send the changes back to the server (e.g. as a UnitOfWork). 4) The server commits the changes and sends just a refetched version of the orders back to the client. 5) The client merges the changes back into his graph.

or if one knows that the commiting does not change the entitys

4) The server just commits the changes. 5) Client marks changed orders as fetched and unchanged.

I know the argument is that one should reget all in the end of an edit cycle. But this is not always the best solution.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39903
Joined: 17-Aug-2003
# Posted on: 04-Dec-2007 11:27:32   

Aion wrote:

I need the entries to be in a state excactly as they where just fetched. I don't understand why you do not supply a AcceptChanges() function that will do excactly that. Espcecially since it is difficult to get the entries refreshed when one is using remoting.

It's not that simple. We're not dealing with a single dataset with a couple of tables. We're dealing with complex object graphs with potentially a lot of levels and a lot of branches. So to deal with that, you can't simply tell a single object to accept the changes and go, as there's no such thing as that object, simply because there's no object which references ALL objects in the graph.

I really think it is a shame it is so difficult to make the following basic senario work with LLBLGEN:

1) Client asks server (via remoting) for a specific customor and all his orders. 2) The server sends a graph with the customer entity as a root and the orders in the Order collection of the customer entity. 3) Client modifies a few orders and send the changes back to the server (e.g. as a UnitOfWork). 4) The server commits the changes and sends just a refetched version of the orders back to the client. 5) The client merges the changes back into his graph.

Remoting means that you send COPIES of the data over the wire which are inserted into a NEW instance on the other side. If you then refetch the entities on the server, when they were send to the server by the client, you are refetching entity instances (== data!) into entity objects which are DIFFERENT from the ones on the client, as these are two separate appdomains

So when the service sends back the objects which were refetched, in this case orders, it sends DATA back to the client, which is then inserted into NEW Instances (that's how distributed systems work, you can't send 'objects' across, only data. The perception of sending objects is created but it's really not there, you never send objects across. THis is the case with every remoting/WCF/webservices system, be it with llblgen pro or other system).

So on the client you now have for example TWO order objects with data which represents the SAME entity: one you already had on the client, the one with the changed data. The other one is the one you received from the service.

To insert the data in the NEW object into the OLD object, you use a context. (myContext.Get(newInstance)). This inserts the data of the new one into the old instance.

This is unavoidable. With whatever object technology you're using, you have to do this somewhere. LLBLGen Pro offers this feature via the Context object so you have control over what is happening when. I.o.w.: you can also toss away what you had and use what you receive from the service, that's up to you.

or if one knows that the commiting does not change the entitys

4) The server just commits the changes. 5) Client marks changed orders as fetched and unchanged.

I know the argument is that one should reget all in the end of an edit cycle. But this is not always the best solution.

In THIS case, you don't send entities back so you have entities on the client which have flags set and they need to be updated to a state where they look as if they're fetched.

There's a problem though: NEW entities which have an identity PK need to be refetched. What's also the case: entities which have an FK (e.g. orders with an fk to customer) to that identity PK also need to be refetched.

If this is NOT the case in your model, you can proceed with updating entities on the client to the state where they seem to look as if they were fetched from the DB.

The way to obtain the entities which were saved in a unitofwork is to call its ConstructSaveProcessQueues() method. After that you can call GetInsertQueue and GetUpdateQueue for the entities which were saved. Then traverse these as follows:


public void ResetEntitiesToInitialState(UnitOfWork2 uow)
{
    uow.ConstructSaveProcessQueues();
    List<ActionQueueElement<IEntity2>> toProcess = new List<ActionQueueElement<IEntity2>>();
    toProcess.AddRange(uow.GetInsertQueue());
    toProcess.AddRange(uow.GetUpdateQueue());
    
    foreach(ActionQueueElement<IEntity2> element in toProcess)
    {
        IEntity2 entity = element.Entity;
        foreach(IEntityField2 field in entity.Fields)
        {
            field.ForcedCurrentValueWrite(field.CurrentValue, field.CurrentValue);
            field.AcceptChanges();
        }
        entity.Fields.IsDirty = false;
        entity.IsNew = false;
    }
}

(not tested but should work)

The ForcedCurrentValueWrite is necessary at the moment for setting the DbValue to the CurrentValue. AcceptChanges() is a method which marks the field as not changed and throws away an optional intermediate value used to do rollbacks in databinding scenarios.

You can add the code inside the outer foreach to the CommonEntityBase class. This will then change your loop into simply calling that method.

I admit this is a bit of work, i.e. you should pass the UoW to the method and if you don't your entities aren't marked properly.

I'll look into adding a utility method to the entity to make this easier, probably in v2.6

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39903
Joined: 17-Aug-2003
# Posted on: 17-Apr-2008 10:41:43   

We postponed this to v3, as the best solution would be to produce some sort of diffgram like structure: per ObjectID (i.e. per entity) a list of pk/fk values would be produced and which is then usable to send back to the client. There, this list is then passed to a method to the unitofwork2 instance, which is then merging the values into the entities it send to the server.

However, this isn't usable in WCF scenarios and it's best if we use one solution for that architecture as well. We think it's better served with a graph governing class which is planned for v3.

Frans Bouma | Lead developer LLBLGen Pro