Problems with FetchEntity when implementing my own adapter

Posts   
 
    
xerxesb
User
Posts: 7
Joined: 29-Aug-2012
# Posted on: 29-Aug-2012 05:32:21   

Hi there,

We've built a DAL around LLBLGEN 2.6, and this DAL is used across several projects.

The requirement has recently come up to be able to swap out the real SQL Server database with an in-memory database. This would be activated in a so-called "demo mode". In this mode, no data would be persisted to real storage.

In order to make this change, i'm implementing a custom DataAccessAdapter which just uses an IDictionary<EntityType, IEntityCollection2> as the storage mechanism, and swapping out the real DataAccessAdapter for our demo mode data adapter. Here, EntityType is the Type of my entity objects (eg typeof(CustomerEntity)) and IEntityCollection2 is just a container for all objects stored using SaveEntity

I've successfully implemented a basic subset of the functionality and for the most part this works. However, my current implementation of FetchEntity is quite dumb, and doesn't take into account related entity collections when fetching a record from the dictionary.

Because of the nature of FetchEntity being that it will populate the provided IEntity2 object, what I need is a way to be able to "deep clone" an object pulled out from the dictionary INTO the object provided to FetchEntity. The deep clone code i've found elsewhere on this forum will create a separate copy, NOT copy the values into a provided object so it's not entirely helpful

Some code, to give you an idea of what i'd like:


        //_entityStore is a thin wrapper over IDictionary<Type, IEntityCollection2>
        public bool FetchEntity(IEntity2 entityToFetch)
        {
            var found = _entityStore.FindEntityByPk(entityToFetch);

            if (found != null)
            {
                entityToFetch.Fields = found.Fields.Clone();
                _entityToFetch.Relations = found.Relations.Clone();
                return true;
            }

            return false;
        }

For clarity - i'm not specifically requesting that implementation above, but asking what ways are there to clone the found entity INTO the entityToFetch?

Cheers, Xerx

EDIT: just some clarifications

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 29-Aug-2012 06:58:28   

IMHO, the clone helper class posted is the best way to go here. What is it an issue that the resulted copy is a new entity? Anyway, it is, then you could modify the clone helper class so you can pass the target entity to it and just iterate to copy fields, setting related entities and collections.

An idea: since you want the application to go into a "demo" mode, Why don't you just create a demo DB, which could be even a SQLCE? Recreating the adapter class could be exhausting IMHO, as you should recreate all public Fetch/Save methods, since you don't know exactly what method your app would call.

David Elizondo | LLBLGen Support Team
xerxesb
User
Posts: 7
Joined: 29-Aug-2012
# Posted on: 29-Aug-2012 07:30:51   

Hi Daelmo - thanks for your response.

daelmo wrote:

IMHO, the clone helper class posted is the best way to go here. What is it an issue that the resulted copy is a new entity? Anyway, it is, then you could modify the clone helper class so you can pass the target entity to it and just iterate to copy fields, setting related entities and collections.

The thing I can't work out is specifically how do i set the related/dependent/depending entities?

Unless i've misunderstood what you mean, i'm not sure how I could change the Clone code to work with an existing entity, rather than clone into a new one?

daelmo wrote:

An idea: since you want the application to go into a "demo" mode, Why don't you just create a demo DB, which could be even a SQLCE? Recreating the adapter class could be exhausting IMHO, as you should recreate all public Fetch/Save methods, since you don't know exactly what method your app would call.

I did look into that, but we have a lot of UDFs, UDTs and we use the BINARY_CHECKSUM function. All of which aren't available in CE.

Fortunately we have a facade which sits ontop of the DataAccessAdapter, and only consumes a handful of methods to fetch, save and import. I only had to implement those.

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 29-Aug-2012 19:34:39   

I would suggest something like the following:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using SD.LLBLGen.Pro.ORMSupportClasses;

namespace xyz
{
    /// <summary>
    /// For cloning an Entity and all related entities, ie the whole graph
    /// </summary>
    internal class CloneHelper
    {
        private CloneHelper()
        {
        }

        internal static object CloneObject(object object)
        {
            MemoryStream ms = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
            bf.Serialize(ms, o);
            ms.Seek(0, SeekOrigin.Begin);
            object oOut = bf.Deserialize(ms);
            ms.Close();
            return oOut;
        }

        internal static void ResetEntityAsFetched(IEntity Entity)
        {
            Entity.State = EntityState.Fetched;
            Entity.IsNew = false;
            Entity.IsDirty = false;
            Entity.Fields.IsDirty = false;
            for (int f = 0; f < Entity.Fields.Count; f++)
            {
                Entity.Fields[f].IsChanged = false;
            }
        }

        internal static void CloneEntity(IEntity source, IEntity target)
        {
            target = (IEntity)CloneObject(Entity);
            ObjectGraphUtils ogu = new ObjectGraphUtils();
            List<IEntity> flatList = ogu.ProduceTopologyOrderedList(newEntity);

            for (int f = 0; f < flatList.Count; f++)
                ResetEntityAsFetched(flatList[f]);

            return;
        }
    }
}
xerxesb
User
Posts: 7
Joined: 29-Aug-2012
# Posted on: 30-Aug-2012 01:06:13   

Walaa wrote:

I would suggest something like the following:


        internal static void CloneEntity(IEntity source, IEntity target)
        {
            target = (IEntity)CloneObject(Entity);
            ObjectGraphUtils ogu = new ObjectGraphUtils();
            List<IEntity> flatList = ogu.ProduceTopologyOrderedList(newEntity);

            for (int f = 0; f < flatList.Count; f++)
                ResetEntityAsFetched(flatList[f]);

            return;
        }
}

Hi Walaa. In that implementation, you're re-assigning "target", but that's only overwriting the local reference, not the caller's reference, which means the original object passed into "target" still won't be updated.

This could be solved if the interface were changed to "ref", but remember that i'm calling this method from IDataAccessAdapter.FetchEntity(IEntity2), and i can't modify that signature.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 30-Aug-2012 07:37:35   

I don't see any reason why you shouldn't return a totally new cloned entity. After all, you will set any field and any related entity. Is there any reason for that?

If you really want that, you can do it yourself, here is an idea of that method:

public void CopyData(IEntityCore source, IEntityCore target)
{
    // set fields
    foreach (var field in source.Fields)
    {
        target.SetNewFieldValue(field.Name, field.CurrentValue);
    }
            
    // set related entities and collections
    var relatedData = source.GetRelatedData();
    foreach (var rd in relatedData)
    {
        if (rd.Value != null)
        {
            if (rd.Value is IEntityCore)
            {
                target.SetRelatedEntityProperty(rd.Key, (IEntityCore)rd.Value);
            }

            if (rd.Value is IEntityCollectionCore)
            {
                foreach (IEntityCore entity in ((IEntityCollectionCore)rd.Value))
                {
                    var factory = EntityFactoryFactory.GetFactory(entity.GetType());
                    var entityToSet = factory.Create(entity.Fields);
                    target.SetRelatedEntityProperty(rd.Key, entityToSet);
                }
            }
        }
    }
}

That method copy all fields, m:1 related entities and 1:n related collections. This is 1-level, you need to modify it if you want to copy all the deep graph.

David Elizondo | LLBLGen Support Team