Cannot loop through list A(icollection) of generic type B where A and B both implement same interface

42 views Asked by At

The intro is a bit tedious, but is just for clearity! Some might even learn something from it or get some handy insights. My question is found at the bottom. I really hope someone can help me!

I have this interface

public interface IEFEntity
{
    object GetPKValue();
    bool PKHasNoValue();
}

All my automatic genereted EF classes implement this interface and implement the 2 methods. This is done via a separate file using partial classes so the implementation is not gone when regenerating the EF classes. This is not important for the question but I have put an example of the setup of two EF classes, debiteur and schakeling below. All are in the same namespace offcourse:

public partial class debiteur
    {
        public debiteur()
        {
            this.schakeling = new HashSet<schakeling>();
        }

        public int id { get; set; }
        public string naam { get; set; }

        public virtual ICollection<schakeling> schakeling { get; set; }
    }
public partial class schakeling
    {
        public schakeling()
        {
            this.debiteur = new HashSet<debiteur>();
        }

        public int id { get; set; }
        public string omschrijving { get; set; }

        public virtual ICollection<debiteur> debiteur { get; set; }
    }

And I have this 2 extra partials that implement the interface. Nothing special here.

public partial class debiteur : IEFEntity
    {
        public object GetPKValue() {
            return this.id;
        }

        public bool PKHasNoValue() {
            return this.id == 0;
        }
    }
public partial class schakeling : IEFEntity
    {
        public object GetPKValue() {
            return this.id;
        }

        public bool PKHasNoValue() {
            return this.id == 0;
        }
    }

(Just to clearify: Other EF generated classes might have a PK property who's name is not 'id' or even have a PK of type string and a name of username, but this is not important for the question! And also not the only reason I use these methods, keep reading...)


I have a GenManager class which uses two generic type parameters. Here's the class definition without implementation. (And I know, this only works for entities/tables with a single PK not with composite keys, as debated here)

public class GenManager<Entity, EntityKey> where Entity : class, IEFEntity {}

This means that the Entity type parameter must be a class which implement interface IEFEntity. So anywhere in my application, lets say a controller I can now create an instance by using:

private GenManager<debiteur, int> DebiteurManager;
private GenManager<schakeling, int> SchakelingManager;

But for example:

private GenManager<anotherClass, int> AnotherClassManager;

will fail because anotherClass does not implement IEFEntity. But back to the GenManager class. Here's the first outlining without all the implementation

public class GenManager<Entity, EntityKey> where Entity : class, IEFEntity
    {
        public IQueryable<Entity> Items { get { return repo.Items; } }
        private IGenRepo<Entity, EntityKey> repo;

        public GenManager(IGenRepo<Entity, EntityKey> repo) { this.repo = repo; }

        public Entity find(EntityKey pk) { return repo.find(pk); }
        public RepoResult delete(EntityKey pk) { return repo.delete(pk); }
        public RepoResult create(Entity item) { return repo.create(item); }
        public RepoResult update(Entity item) { return repo.update(item); }

        public bool isInFKEntity<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey fk) where FKEntity : class, IEFEntity { }

        public List<FKEntity> GetFKEntities<FKEntity, FKEntityKey>(EntityKey pk) where FKEntity : IEFEntity { }

        public RepoResult removeFKEntities<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey[] fkList) where FKEntity : IEFEntity { }

        public RepoResult addFKEntities<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey[] fkList) where FKEntity : IEFEntity { }

        public RepoResult resetFKEntities<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey[] fkList) where FKEntity : IEFEntity { }

        public RepoResult resetFKEntities2<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey[] fkList) where FKEntity : IEFEntity { }

        public RepoResult resetFKEntities3<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey[] fkList) where FKEntity : IEFEntity { }

        public RepoResult clearFKEntities<FKEntity>(EntityKey pk) where FKEntity : IEFEntity { }
    }

Take a second to look at this. Just as the first generic type Entity of the GenManager class must be a class that implements IEFEntity, this must also be the case with generic type FKEntity used in the methods. So in my controller for example I can now use this statement:

bool b = DebiteurManager.isInFKEntity<schakeling, int>(debiteur_id, schakeling_id);

But again an example:

bool b = DebiteurManager.isInFKEntity<anotherEntity, int>(debiteur_id, anotherEntity_id);

will fail because anotherEntity does not implement IEFEntity.


And it is in these methods where the magic must happen using c# reflection and I face a problem. I give you the implementation of the unfinished method isInFKEntity with some test code statements including comments:

public bool isInFKEntity<FKEntity, FKEntityKey>(EntityKey pk, FKEntityKey fk) where FKEntity : class, IEFEntity
    {
        Entity dbEntry = find(pk);
        if (dbEntry == null) throw new InvalidOperationException(RepoMessages.dbEntryIsNull(new List<object> { pk }));

        //----------Not important for question! Just to clearify things----------\\
        var v1 = typeof(Entity).GetProperty("naam").GetValue(dbEntry);//Sets v1 to a string value
        var v1Copy = new debiteur().GetType().GetProperty("naam").GetValue(dbEntry);//Just to test and clearify what the above statement does, vars have the same value!
        //----------End----------\\

        var FKEntityList = typeof(Entity).GetProperty(typeof(FKEntity).Name).GetValue(dbEntry);//Gives a list back at runtime, but I can't loop through them
        var FKEntityListCopy = new debiteur().GetType().GetProperty("schakeling").GetValue(dbEntry);//Just to test and clearify what the above statement does, vars have the same value!

        var FKEntityListWithCast = typeof(Entity).GetProperty(typeof(FKEntity).Name).GetValue(dbEntry) as List<FKEntity>;//Gives a NULL value back at runtime, but intellisense!
        var FKEntityListWithCastCopy = new debiteur().GetType().GetProperty("schakeling").GetValue(dbEntry) as List<FKEntity>;//Just to test and clearify what the above statement does, vars have the same value!(also NULL)

        return false;
    }

I want to loop through FKEntityList but if I try to do that using a foreach I get a message: foreach statement cannot operate on variables of type 'object' because 'object' does not contain a public definition for 'GetEnumerator'. Also, using intellisense only shows the 4 standard methods Equals(object obj), GetHashCode(), GetType() and ToString(). See image

So I try to cast it succesfully using as List<FKEntity> and assign the result to FKEntityListWithCast and now I get no error message using a foreach.

So I thought, very nice I just use this return statement and it works:

return FKEntityListWithCast.Any(item => item.GetPKValue().ToString() == fk.ToString());    

The above return statment doesn't give an error and everything compiles, but the problem is that at runtime the FKEntityListWithCast doesn't get a value. It stays NULL. So the problem is:

GETS VALUE at runtime but CANNOT loop through:

var FKEntityList = typeof(Entity).GetProperty(typeof(FKEntity).Name).GetValue(dbEntry);

GETS NO VALUE(NULL) at runtime but CAN loop through:

var FKEntityListWithCast = typeof(Entity).GetProperty(typeof(FKEntity).Name).GetValue(dbEntry) as List<FKEntity>;

Any help would be greatly appiciated!

1

There are 1 answers

0
Kip ei On BEST ANSWER
ICollection<FKEntity> FKEntityList = typeof(Entity).GetProperty(typeof(FKEntity).Name).GetValue(dbEntry) as ICollection<FKEntity>;

Still a bummer nobody could answer this