DataScope CommitChange UnitOfWork contains unchanged entites.

Posts   
 
    
yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 07-Apr-2021 22:36:26   

Hi,

LLBLGen 5.8.1 with Adapter

I want to use the DataScope functionality and have the most efficient network traffic when persisting changes from the client to the server. I have this example where I expect to just get 1 entity in the UnitOfWork, but the FastSerializer is serializing a total of 4 objects, even though only 1 is new/dirty. I could create my own unitOfWork by taking the passed in IUnitOfWorkCore and copying just those entities which are IsNew/IsDirty/IsDeleted, but is there a better, more efficient way?

This is from the AdventureWorksLT2019 database.

    class Program
    {
        static void Main(string[] args)
        {
            var meta = new LinqMetaData(new DataAccessAdapter("data source = localhost; initial catalog = AdventureWorksLT2019;Integrated Security=SSPI;"));
            var o = (from oh in meta.SalesOrderHeader
                where oh.SalesOrderId == 71774
                select oh).WithPath(op => op.Prefetch(or => or.SalesOrderDetails)).Single();


            var scope = new MyDataScope();
            scope.Attach(o);
            o.SalesOrderDetails.Add(new SalesOrderDetailEntity());
            scope.CommitChangesAsync(CommitFunc, new CancellationToken());
        }

        static Task<bool> CommitFunc(IUnitOfWorkCore arg1, CancellationToken arg2)
        {

            var uow = (UnitOfWork2) arg1;
            new FastSerializer().Serialize(uow);

            if (SalesOrderHeaderEntity.EntitiesSerialized != 0 || SalesOrderDetailEntity.EntitiesSerialized != 1)
            {
                throw new Exception("too many entities in UoW");
            }
            return Task.FromResult(false);
        }


        class MyDataScope : DataScope
        {
            internal void Attach(IEntityCore e)
            {
                base.Attach(e);
            }
        }
    }

And in the 2 entities I track the serialization with simply:


    public partial class SalesOrderHeaderEntity
    {
        public static int EntitiesSerialized;

        protected override void SerializeOwnedData(SerializationWriter writer, object context)
        {
            base.SerializeOwnedData(writer, context);
            EntitiesSerialized++;
        }
    }

And the same for :

    public partial class SalesOrderDetailEntity
    {
        public static int EntitiesSerialized;

        protected override void SerializeOwnedData(SerializationWriter writer, object context)
        {
            base.SerializeOwnedData(writer, context);
            EntitiesSerialized++;
        }
    }

Thanks,

Walaa avatar
Walaa
Support Team
Posts: 14711
Joined: 21-Aug-2005
# Posted on: 08-Apr-2021 05:17:36   

Every entity addition to the graph, as every entity added to the graph in the scope is added to the context automatically, and so they are in the UOW. And then Serialization will serialize whatever is there to serialize. So this is by design.

Your suggested solution would be ideal to serve your request.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 38431
Joined: 17-Aug-2003
# Posted on: 08-Apr-2021 10:18:55   

To elaborate a bit on what Walaa said: the UoW contains all entities, but what you're after is the persistence queues calculated from these entities. that's done when the uow is committed (or when you call the method for that: UnitOfWork2.ConstructSaveProcessQueues) So if you only want to serialize the ones which are considered in a persistence action, you have to serialize the queues in the UoW after calling UnitOfWork2.ConstructSaveProcessQueues. (i.e. UnitOfWork2.GetInsertQueue, etc.) . You then have to manually persist these in a loop on the service. Be sure to also obtain the entities to delete as these aren't in one of these 2 queues.

Frans Bouma | Lead developer LLBLGen Pro
yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 08-Apr-2021 17:58:36   

Thanks, I'll go for something like


            var uow = (UnitOfWork2) arg1;
            var uowChangesOnly = new UnitOfWork2();
            uow.ConstructSaveProcessQueues();
            foreach(var e in uow.GetEntityElementsToDelete())
            {
                uowChangesOnly.AddForDelete(e.Entity);
            }

            foreach (var e in uow.GetInsertQueue().Union(uow.GetUpdateQueue()))
            {
                uowChangesOnly.AddForSave(e.Entity, null, false, false);
            }
            new FastSerializer().Serialize(uowChangesOnly);
yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 08-Apr-2021 18:22:28   

Sorry, that doesn't actually help as the FastSerializer still picks up the associations. Maybe I can use WriteTokenizedObject directly for each entity to avoid the ReferencedEntityMap that is bringing in the unchanged entities.

But I'll have to use reflection as SerializeOwnedData is internal :-(

Otis avatar
Otis
LLBLGen Pro Team
Posts: 38431
Joined: 17-Aug-2003
# Posted on: 08-Apr-2021 19:10:20   

You can use the DetachFromGraph method to not get these related entities in the set.

but it's tricky, as you might want some associations be kept ( like pk-fk relationships)...

SerializeOwnedData is protected virtual in an entity btw:

protected internal virtual void SerializeOwnedData(SerializationWriter writer, object context)

Frans Bouma | Lead developer LLBLGen Pro
yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 08-Apr-2021 19:32:13   

SerializeOwnedData isn't really a problem, it's the FastSerializer. I think I'll just create another FasterSerializer :-) that in WriteReferencedEntities just writes the one entity, and create the SerializationWriter outside and serialize the entities one at a time. Then maybe just pass that SerializationWriter into a new FastSerializer for the deleted entities(which I can put in their own UoW).

yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 08-Apr-2021 20:24:01   

Ah yes, you are right. If there is a pk-fk, the pk entity is new and has an identity column, then the relationship is needed. And regardless of the identity, I suppose for the insert order, if both entities are new, then the relationship is used to determine the insert sequence. I wonder if in SerializeOwnedData I didn't call the base for IsNew/IsDirty == false.....

Walaa avatar
Walaa
Support Team
Posts: 14711
Joined: 21-Aug-2005
# Posted on: 09-Apr-2021 00:32:44   

I wonder if in SerializeOwnedData I didn't call the base for IsNew/IsDirty == false.....

Have you figured this out?

yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 09-Apr-2021 00:35:07   

I'm actually trying Fran's suggestion of doing a selective "DetachFromGraph". It appears to work, but I've not finished testing.

yowl
User
Posts: 213
Joined: 11-Feb-2008
# Posted on: 12-Apr-2021 23:39:10   

This is not easy as I thought. It's easy to optimize the serialization by detaching unchanged entities, but of course that breaks the graph on the client and if the save fails then you've got a problem on the client to deal with - the graph is no longer fully connected.

I don't have a solution that's as efficient as OpenRiaServices (which is what I'm migrating from), but I'm going to leave it for now and just serialize everything in the graph. Not great when there's 50 objects and only 1 is changed (typical in a LOB application), but it will have to do for now.

Thanks for the suggestions.