Extracting instances from a list based on a given Type variable
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 :(
You need to check:
- If the
Type
for the current itementityList
represents a generic type - If that generic type represents
EntityList<>
- 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.
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.
Use Linq:
Dictionary<string, Type> a = new Dictionary<string, Type>();
var allOfMyType = a.Where(x=> (x.Value.Name == "MyType"));
精彩评论