1

Background

I'm using EF and I have many tables. When I insert a new entity with navigation properties' content but without the id (I'm reading the content out of a xls file) I don't want to load all the navigation properties explicitly. This was too much code. So I tried a generic way:

private void loadExistingNavigationProperties<TEntity>(TEntity entityToInsert) where TEntity : class
{
    Type type = typeof(TEntity);
    var properties = type.GetProperties().Except(type.GetProperties().Where(x => x.Name.Contains("id")));
    foreach (PropertyInfo property in properties)
    {
        if (property.PropertyType.FullName.Contains("MyNamespace"))
        {
            property.SetValue(entityToInsert, findNavigationProperty<???>(property.GetValue(entityToInsert)));
        }
    }
}

I have my entityToInsert. I check all its properties if it has a navigation property (contains("MyNamespace")). If this is true, the navigation property should be loaded (see below) and set.

private object findNavigationProperty<TNavigationProperty>(TNavigationProperty navigationPropertyValue) where TNavigationProperty : class
{
    List<TNavigationProperty> navigationProperties = GetAllEntries<TNavigationProperty>();
    foreach (var entity in navigationProperties)
    {
        if (propertiesAreEqual(entity, navigationPropertyValue))
        {
            return entity;
        }
    }
    return navigationPropertyValue;
}

The current value of the navigation property attribute is passed. It contains all information like a name or something but not the id. First I'm getting all available navigation properties having that type. Then I'm searching if there is one property which has the same properties as the current one. Then this one is returned and set as the navigation property.

Edit:

public List<TEntity> GetAllEntries<TEntity>() where TEntity : class
{
    using (var dbContext = new InventarDBEntities(MainWindow.connectionName))
    {
        return GetAllEntries<TEntity>(dbContext);
    }
}

public List<TEntity> GetAllEntries<TEntity>(InventarDBEntities dbContext) where TEntity : class
{
    return dbContext.Set<TEntity>().ToList();
}

Problem

My problem is now how can I tell the method findNavigationProperty that the generic type is the type, which the property's value has. So replacing the ??? with the type.

L3n95
  • 1,505
  • 3
  • 25
  • 49
  • 1
    You cannot retrieve a generic type at runtime. Generic types must be resolvable during compile-time. If you want to pass a runtime resolved type to a method you must do that by passing the reflected `System.Type`. Does `GetAllEntries()` have an overload that accepts a parameter of `System.Type`? – Noel Widmer May 05 '17 at 09:43
  • @NoelWidmer I added the GetAllEntries method. It does not accept a parameter of System.Type – L3n95 May 05 '17 at 09:59
  • Do you also have the Source of `InventarDBEntities`? I imagine it is a .NET type. In that case, can you tell me the name of the class name it derives from. (I want to have a look if there is an `InventarDBEntities.Set()` overload that accepts a `System.Type` instead of the generic argument) – Noel Widmer May 05 '17 at 11:13
  • Generics and reflection don't mix well. Generics is a _compile time_ construct while reflection is a _run time_ construct. You _can_ [construct a generic method call at runtime](http://stackoverflow.com/questions/232535/how-do-i-use-reflection-to-call-a-generic-method), though. – D Stanley May 05 '17 at 16:21
  • @NoelWidmer: InventarDBEntities derives from DbContext. – L3n95 May 08 '17 at 06:17
  • @DStanley: Okay thats not really nice. But I need a way to remove all that ugly code I had. I try your link. – L3n95 May 08 '17 at 06:18
  • @NoelWidmer: It looks like it has one: https://msdn.microsoft.com/de-de/library/gg679544(v=vs.113).aspx How can I use this now? – L3n95 May 08 '17 at 06:48

2 Answers2

1

As I already mentioned in my comment:
Generic type arguments are resolved during compile time.
Therefore you cannot retrieve a generic type from a System.Type.

The key to your issue is to use System.Type rather than generics.
Note that I haven't tested the code below because I haven't installed EF.
To my kowledge DbContext's System.Type overload should work just fine.

private void loadExistingNavigationProperties<TEntity>(TEntity entityToInsert) where TEntity : class
{
    Type tEntity = typeof(TEntity);
    var properties = tEntity.GetProperties().Except(tEntity.GetProperties().Where(x => x.Name.Contains("id")));
    foreach (PropertyInfo property in properties)
    {
        if (property.PropertyType.FullName.Contains("MyNamespace"))
        {
            object val = findNavigationProperty(property.GetValue(entityToInsert), tEntity);
            property.SetValue(entityToInsert, val);
        }
    }
}

private object findNavigationProperty(object navigationPropertyValue, Type tEntity)
{
    DbSet navigationProperties = GetAllEntries(tEntity);
    foreach (var entity in navigationProperties)
    {
        // You may get a type issue.
        // If so: cast to the correct type or change "propertiesAreEqual".
        if (propertiesAreEqual(entity, navigationPropertyValue))
        {
            return entity;
        }
    }
    return navigationPropertyValue;
}

public DbSet GetAllEntries(Type tEntity)
{
    using (var dbContext = new InventarDBEntities(MainWindow.connectionName))
    {
        return GetAllEntries(dbContext, tEntity);
    }
}

public DbSet GetAllEntries(InventarDBEntities dbContext, Type tEntity)
{
    return dbContext.Set(tEntity);
}
Noel Widmer
  • 4,444
  • 9
  • 45
  • 69
  • Yes it works. And it is much cleaner than invoking a method via reflection. Thank you. I just don't understand why a DbSet has less methods than a DbSet<>, but that's another question and casting solves this too. – L3n95 May 08 '17 at 14:01
  • @L3n95 Cool! I have not used the DbSet very often and can therefor not answer that question. I'd always prefer the generic types over the general object types. But in case of using reflected types you usually don't have better options. – Noel Widmer May 08 '17 at 14:11
0

You can retrieve a generic type like this:

var item = propertyInfo.GetGenericArguments()[0];

You can check whether it is of type by using "is"

you can also do:

item.BaseType == typeof(Whatever type your navigation props inherit);
Chris
  • 826
  • 10
  • 26