tags:

views:

52

answers:

3

I fear this is going to be a big setup for a simple question. As to complexity of the answer, I fear what I might be getting into...

I am building an application that will be used to help transform data from a source database with one table structure to a target database with a different structure. The target database will contain data already, and thus the process must be able to maintain ID-based relationships from the source when inserting to the target, where the newly-inserted items will get new IDs. Assume that each source table will be transformable to a single target table.

Minimal code, with necessary class/interface structure:

public interface IDataSetStorable { }

public class InMemoryDataSet : List<IDataSetStorable>
{
    public AbstractDataEntity FindEntity(string id, Type type)
    {
        // The question will be about this method
        return new object() as AbstractDataEntity;
    }
}

public class EntityList<T> : Dictionary<string, T>, IDataSetStorable where T : AbstractDataEntity
{
    public void AddEntity(T entity)
    {
        this.Add(entity.ID, entity);
    }
}

public abstract class AbstractDataEntity
{
    public string ID { get; set; }
}

public abstract class DataItem<S, T> : AbstractDataEntity { }


// There will be a set of these three classes per source DB table
public class SourceType { }
public class TargetType { }
public class TransformationType : DataItem<SourceType, TargetType> { }

InMemoryDataSet holds the tables, represented by instances of (for example) EntityList<TransformationType>. There will be a TransformationType for each mapping of SourceType to TargetType, where each of those is likely to be a class from a DataContext. There will be one per source DB table, though many of those tables may map to a single target DB table.

The use of IDataSetStorable as a marker interface allows for the storage of EntityList<>s with many different subtypes within an instance of InMemoryDataSet.

During the transformation of any item from the source DB, it can only be inserted into the target DB if we know the appropriate target-DB IDs for its foreign keys. To do this the code will find all its dependencies from the source DB and transform them BEFORE attempting to transform the item under consideration. Recursively, this should ensure that the first things inserted into the target DB have no dependencies, get their new IDs, and can then be looked up when inserting things that depend on them.

An instance of InMemoryDataSet will provide the lookup facility, which should be passed an ID (from the source DB) and a parameter of type Type, representing the TransformationType which deals with transforming the type of item being looked up.

Example of that: Table1 has two fields, id and table2_id, the latter referencing Table2, and its field id. The lookup call would be (kinda pseudocode-y):

var entity = myDataSet.FindEntity([table1.table2_id], typeof(Table2TransformationType));

Then entity should be of type Table2TransformationType (inheriting eventually from AbstractDataEntity), and would represent the row from Table2 with ID matching that passed to the method.

And finally, to the question:

In the FindEntity() method, how can I find if there is an EntityList<whatever the passed type was> present? My thought was to use something like:

foreach (var entityList in this)
{
    // work out if entityList is an EntityList<passed-in type>;
}

Simple question! But I don't know how I can do this last part :(

A: 

Use Linq:

           Dictionary<string, Type> a = new Dictionary<string, Type>();
        var allOfMyType = a.Where(x=> (x.Value.Name == "MyType"));
Aliostad
+1  A: 

You need to check:

  1. If the Type for the current item entityList represents a generic type
  2. If that generic type represents EntityList<>
  3. If the generic argument to that type is of the passed in type

Try this:

if (entityList.GetType().IsGenericType && 
    entityList.GetType().GetGenericTypeDefinition() == typeof(EntityList<>) && 
    entityList.GetType().GetGenericArguments()[0] == type) 
{
    ...
}

Edit: Was getting the generic arguments off of the wrong type. Fixed.

Kirk Woll
Definitely getting there. The first two statements evaluate to `true`, but the third does not. It seems that `entityList.GetType().GetGenericTypeDefinition().GetGenericArguments()[0]` gives back the `T` from the original declaration of `EntityList<T>`, rather than the specific type the `EntityList` was created with. Is there some way of attempting to cast the `entityList` variable to `EntityList<passed-in type>` that would throw an exception if it's not suitable?
Matt Sach
@Matt, my apologies, I should have tested first. I updated the answer, but was getting the generic arguments off of the wrong type. Should work now.
Kirk Woll
That's nailed it, +1 and marked as accepted. My own answer works, but yours was earlier, and much neater than relying on a casting exception from an Invoke!
Matt Sach
+1  A: 

OK, managed to make this work using a bit of Reflection. Kirk Woll got me started looking in the right places, though in the end the solution hasn't used his suggestions. There is an additional method, public T RetrieveEntity(string id), in the EntityList<T> class in order to make it easier to get a single item out of the Dictionary by key when using Type.GetMethod():

public class EntityList<T> : Dictionary<string, T>, IDataSetStorable where T : AbstractDataEntity
{
    public void AddEntity(T entity)
    {
        this.Add(entity.ID, entity);
    }
    public T RetrieveEntity(string id)
    {
        return this[id];
    }
}

Then we have the guts of the FindEntity(string id, Type type) method:

public class InMemoryDataSet : List<IDataSetStorable>
{
    public AbstractDataEntity FindEntity(string id, Type type)
    {
        // Make an instance of the passed-in type so that invoking
        // TryGetValue will throw an exception if operating on an
        // EntityList which is not of the correct type.
        var sample = type.GetConstructor(new Type[]{}).Invoke(new object[]{});
        foreach (var entityList in this)
        {
            try
            {
                // This doesn't manage to set sample to the found entity...
                bool idFound = (bool)entityList.GetType().GetMethod("TryGetValue").Invoke(entityList, new object[] { id, sample });
                if (idFound)
                {
                    // So we dig it out here with the method added to EntityList<>
                    sample = entityList.GetType().GetMethod("RetrieveEntity").Invoke(entityList, new object[] { id });
                    return (AbstractDataEntity)sample;
                }
            }
            catch (Exception ex)
            {
                // Likely some kind of casting exception
            }
        }
        return null;
    }
}

At the point of calling FindEntity() we knew what the desired type was, and so casting the AbstractDataEntity that it returns is trivial.

Matt Sach