0

ASP .NET 5 MVC Core application.

Following method is used to find entity by key:

    class MyStudents: Students { }

    public TEntity FindNormalized<TEntity>(params object[] keyValues)
        where TEntity : class
    { 
        return Find<TEntity>(keyValues);
    }

    void FindNormalized<TEntity>(TEntity entity, params object[] keyValues)
        where TEntity : class
    {
        var res = FindNormalized<TEntity>(keyValues);
        if (res == null)
            throw new KeyNotFoundException(entity.GetType().Name + " entity key not found " + keyValues[0]);
        CopyPropertiesTo(res, entity);
    }

    static void CopyPropertiesTo<T, TU>(T source, TU dest)
    { // https://stackoverflow.com/questions/3445784/copy-the-property-values-to-another-object-with-c-sharp
        var sourceProps = typeof(T).GetProperties().Where(x => x.CanRead).ToList();
        var destProps = typeof(TU).GetProperties()
                .Where(x => x.CanWrite)
                .ToList();

        foreach (var sourceProp in sourceProps)
            if (destProps.Any(x => x.Name == sourceProp.Name))
            {
                var p = destProps.First(x => x.Name == sourceProp.Name);
                p.SetValue(dest, sourceProp.GetValue(source, null), null);
            }
    }

Using it with subclass

   FindNormalized<MyStudents>(1);

throws exception

Cannot create a DbSet for 'MyStudents' because this type is not included in the model for the context.

   FindNormalized<Students>(1);

works.

How to fix this so that it can used with subclass type also ?

For getting table attribute from subclass code from Dynamic Linq works:

    string GetTableAttrib(Type t)
    {
        foreach (Type t2 in SelfAndBaseClasses(t))
        {
            IEntityType entityType = Model.FindEntityType(t2);
            if (entityType == null)
                continue;
            return entityType.GetTableName();
        }
        throw new ApplicationException(t.FullName + " no table attribute");
    }

    /// <summary>
    /// Enumerate inheritance chain - copied from DynamicLinq
    /// </summary>
    static IEnumerable<Type> SelfAndBaseClasses(Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }

Maybe this can used to implement Find also. Unfortunately Find throws exception. Is it reasonable to wrap Find using try/catch or is there better way ?

8
  • it's fairly obvious that what you can obtain from EFCore is just the entity instances (that does make sense, EFCore does not manage non-entity types). But you want the derived entity instances instead. So that's the problem of converting from the entity instance to a derived entity instance. That can be solved by the so-called object cloning or object copying. You normalize the problem first and solve the finally analyzed problem which is simple. Commented Jan 23, 2021 at 22:56
  • BTW, your kind of design with types deriving from entity types is fairly weird. There should be some better design than that. I've never implemented something like that, I do use interfaces however. Commented Jan 23, 2021 at 22:59
  • There is one Poco document object in DBContext. It has shild objects: Invoice, Order, Waybill, Offer. Each of them have different attributes, eq. document name, acces right, subtype in database, Icon. Invoice object needs to get data from database as passes its type to Find. Commented Jan 23, 2021 at 23:13
  • what returned type do you expect from this FindNormalized<MyStudents>(1)? Students or MyStudents? If it's MyStudents then you must perform some kind of cloning/copying (as I said before), otherwise you may have it solvable with reflection. Commented Jan 23, 2021 at 23:18
  • It should return Students object. Returned property values are copied to MyStudents property values. Commented Jan 23, 2021 at 23:34

1 Answer 1

1

The EFCore Find method throws exception because the TEntity is not an entity type but a type deriving from the entity type. So to solve this, we need to first get the most derived type which is the ancestor type of the passed in TEntity type, so when passing in MyStudents you need to get type Students first to use as type argument for Find<> method. Because that type is not known at design time, so we need to use reflection to invoke that Find<> method or better use the other overload of Find that accepts the first argument of entity type (Type). Of course I understand that your Find<> method in your question is from DbContext.Find.

So the solution can be simple like this, firstly we need method to get the most derived entity type from the passed in type (which inherits from the entity type):

public static class DbContextExtensions {
     public static Type GetMostDerivedEntityType(this DbContext dbContext, Type type){
         while(type != null && dbContext.Model.FindEntityType(type) == null) {
             type = type.BaseType;
         }
         return type;
     }
}

Next just use that extension method in your FindNormalized method to find the most derived entity type first before using Find:

//I suppose that this is in the context (class) of your custom DbContext
void FindNormalized<TEntity>(TEntity entity, params object[] keyValues)
    where TEntity : class
{
    var actualEntityType = this.GetMostDerivedEntityType(typeof(TEntity)) ?? 
                           throw new InvalidOperationException($"The type {typeof(TEntity)} is not an entity type or a derive from an entity type");
    //use the actualEntityType instead
    var res = Find(actualEntityType, keyValues);
    if (res == null)
        throw new KeyNotFoundException(entity.GetType().Name + " entity key not found " + keyValues[0]);
    //copy the properties
    CopyPropertiesTo(res, entity);
}

Note that in the above code I use another overload (non-generic version) of Find which accepts the first argument of Type (entity type) directly instead of using your wrapper FindNormalized which is generic only (you can add your one overload of that wrapper which wraps the non-generic Find as used directly in my code above).

I've not tested the code at all. Just try it and let me know if there is any error.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.