DbSet.Cast<TEntity>() Error: Cannot create a DbSet<IEntity> from a non-generic DbSet for objects of type 'Entity'
Asked Answered
N

4

9

Version Info:

I am using C# 4.5, Entity Framework 6.0, and MEF.

Code and Unit Test

I created a Test Project to explain the problem: https://skydrive.live.com/redir?resid=E3C97EC293A34048!2234

Please Open the UnitTest project and try to run TestIfItWorks() unit test.

Problem

I want to convert a non-generic DbSet to its generic version but I am getting the following exception: InvalidCastException: Cannot create a DbSet<IUser> from a non-generic DbSet for objects of type 'User':

var nonGeneric = context.Set(typeof(User));
var generic = nonGeneric.Cast<IUser>(); //Exception in here

The User class is implementing IUser so you would think the cast shouldn't be a problem unless DbSet code is restricted to concrete classes (I hope not otherwise I need to either create a wrapper around non-generic DbSet to convert it to a generic DbSet or find an alternative to current DbSet implementation).

If you are wondering why I am using interfaces even though they are not currently supported by Microsoft I give you a little explanation (hopefully this would filter out responses that say "Don't Do That" instead of providing a solution) :

I am using MEF and EntityFramework to create a loosely coupled data layer engine through which I can provide Entities (and their corresponding configurations) per project basis. I have been using Interfaces extensively to define the engine. The meta data and concrete implementation of entities in context are discovered in run time using MEF.

Excerpt from code

[TestMethod]
public void TestIfItWorks()
{
    //TODO: Please open the App.Config and change the PluginsPath to match the Plugins folder in your machine.

    using (var dbContext = new MyContext()) //Please ignore this line for now. This was UnitOfWork which I replaced with Context to create a simple unit test
    {
        dbContext.Setup(); //Please ignore this line for now. This was part of UnitOfWork which I moved to here to create a simple unit test

        //The purpose of all these is to be able to read and write user info from/to database while User class is defined in an external assembly
        //but we can import it by MEF using IUser interface.

        //Failed Attempt# 1: Use User class directly! This doesnt work because User is in an external class which we dont have reference to
        //var failedAttempt1 = dbContext.Set<User>(); 

        //Failed Attempt# 2: But the good thing is that we have access to IUser and its exports
        //then lets get a DbSet<IUser> instead
        var failedAttempt2 = dbContext.Set<IUser>();
        try
        {
            var throwsException2 = failedAttempt2.FirstOrDefault();
        }
        catch (InvalidOperationException ex)
        {
            //InvalidOperationException: 
            // The entity type IUser is not part of the model for the current context.
            // It also didnt work when I tried to define a class that inherits from EntityTypeConfiguration<IUser>at TestImplementation
        }




        //Ok then lets do it differently this time. Lets get User type (that we know we have good configuration for)
        //from our Container and ask Context to give us the nonGeneric version
        var userImplementationType = Logic.Instance.GetExportedTypes<IUser>().FirstOrDefault();
        Assert.IsNotNull(userImplementationType, "We havn't been able to load TestImplementation into catalog. Please ensure the PluginsPath is set correctly at App.Config");
        var nonGeneric = dbContext.Set(userImplementationType);
        //
        // This is working so far, we can add and remove records from database using
        // the nonGeneric version of DbSet. You can uncomment the following code block provide a unique ID
        // and test it yourself.
        //
        var newUser = Logic.Instance.New<IUser>();
        newUser.Id = "99";
        newUser.UserName = "Aidin Sadighi";
        nonGeneric.Add(newUser);
        try
        {
            dbContext.SaveChanges();
        }
        catch (DbUpdateException ex)
        {
            //This is OK because most probably this is a duplicate user. Just increase the Id to make it unique.
        }



        //Failed Attempt#3: Cast non generic DbSet to generic
        try
        {
            //TODO: I need to fix this. Help me please 
            var genericSet = nonGeneric.Cast<IUser>();
        }
        catch (InvalidCastException ex)
        {
            //Cannot create a DbSet<IUser> from a non-generic DbSet for objects of type 'User'.
            throw;
        }
    }
}
Nadbus answered 26/10, 2013 at 17:56 Comment(3)
How about IQueryable<IUser> generic = nonGeneric.Cast<User>();?Breeden
I know jack about Entity Framework, but looking at the code i'm pretty skeptic about context.Set(). does this work: User nonGeneric = context.Set(typeof(User)); ..if not, then its normal that the cast doesn't either, and if it does, just use IUser generic = context.Set(typeof(User));Dextrorse
Thanks for response but this doesn't work for me. I edited my question to provide a sample code and more details. ThanksNadbus
L
10

For this, I would actually suggest using reflection. In the constructor of your DbContext, you can set a property to the function pointer:

method = this.GetType().GetMethod("Set", new Type[0]).MakeGenericMethod(typeof(UserImplementation));

You can then invoke this using:

method.Invoke(this, new object[0]);

And this should return an object of type DbSet<UserImplementation> which the .Cast<>() method can then be invoked on.

Lineage answered 31/10, 2013 at 1:59 Comment(7)
Thanks for response but this doesn't work for me. I edited my question to provide a sample code and more details. ThanksNadbus
I really like your approach. That is very genius. However I am getting AmbiguousMatchFound exception when calling the following line: var method = typeof(MyContext).GetMethod("Set").MakeGenericMethod(userImplementationType);Nadbus
Awesome, fixed it. I had to change that code a bit but your approach was the key. Thank you so much you just made my day. Correct code is: var method = typeof(MyContext).GetMethod("Set", new Type[0]).MakeGenericMethod(userImplementationType); var genericItem = method.Invoke(dbContext, new object[0]);Nadbus
I dont understand how to use this. I have: var set = context.Set(ImpType); and need to return IQueryable<T>. T is IMyType and ImpType is a Type object from a class MyType : IMyType. I tried set.Cast<T>(), but got the error that it cant create generic from non generic.Teaspoon
@pksorensen: sorry for the extremely late reply, but if you know the implementation type at compile-time, you can simply use: var iMyQueryable = this.Set<MyType>().Cast<IMyType>();. If you want it to be at run-time, then you would have to use: var myQueryable = this.GetType().GetMethod("Set", new Type[0]).MakeGenericMethod(typeof(MyType)).Invoke(this, new [0]); Then you would have an IQueryable<MyType>, which you could then call myQueryable.Cast<IMyType>() to get an IQueryable<IMyType>.Lineage
This returns a object of type object, which leaves me to cast the object into DbSet which in turn can't be casted... Aidin, what did you do with the object to convert it to a DbSet<Interface>?Marje
I also don't know how they managed to get the object to DbSet<Interface> cast done. But I got it working by doing a cast to IQueryable<Interface> which allows you to do pretty much the same operations as the DbSet. Code: context.GetType().GetMethod("Set", new Type[0]).MakeGenericMethod(type).Invoke(context, new object[0]) as IQueryable<Interface>;Romalda
S
2

replace

nonGeneric.Cast<IUser>();

by

Enumerable.Cast<IUser>(nonGeneric);
Since answered 27/8, 2014 at 22:45 Comment(3)
Not sure why it was downvoted, this advice helped me.Wilheminawilhide
I don't know why nonGeneric.Cast<IUser>(); doesn't work but this is such an elegant solution. Thanks.Phycomycete
Do not use this. With this method all entities from db will be loaded to memory.Extractor
D
0

Ok i know nothing about Entity framework but from looking at the docs

http://msdn.microsoft.com/en-us/library/gg696521%28v=vs.103%29.aspx

DbSet<TEntity> item = DbContext.Set<TEntity>;

so actually your code would be the same as this:

DbSet<User> nonGeneric = context.Set<User>();

and to get a IUser

DbSet<IUser> nonGeneric = context.Set<User>();

or maybe

var generic = nonGeneric.Cast<DbSet<IUser>>();
Dextrorse answered 31/10, 2013 at 2:12 Comment(1)
Thanks for response but this doesn't work for me. I edited my question to provide a sample code and more details. ThanksNadbus
R
0

As I wasn't able to cast a generic DbSet to a typed DbSet I used instead a typed IQueryable which can do the same things I needed from the DbSet.

Here is a extension that can get you that:

    public static IQueryable<T> GetIQueryableByTableName<T>(this DbContext context, string tableName)
    {
        var type = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t => t.Name == tableName);
        if (type == null)
        {
            throw new Exception("GetIQueryableByTableName received an invalid table name.");
        }
        return context.GetType().GetMethod("Set", new Type[0]).MakeGenericMethod(type).Invoke(context, new object[0]) as IQueryable<T>;
    }
Romalda answered 24/11, 2020 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.