Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically get a DbSet<T> by Entity class name

I'm trying to use System.Reflections to get a DbSet<T> dynamically from its name.

What I've got right now is:

  • The DbSet name
  • The DbSet's Type stored on a variable

The issue I'm facing comes out when trying to use the dbcontext.Set<T>() method, since (these are my tries so far):

  • When I try to assign to <T> my DbSet Type, it throws me the following compilation error:

    "XXX is a variable but is used like a type"

  • If I try with using both the Extension methods that you will find below in my code (which I made in order to try to get an IQueryable<T>), it returns a IQueryable<object>, which unfortunately is not what I am looking for, since of course when I try to manipulate it with further Reflections, it lacks of all the properties that the original class has…

What am I doing wrong? How can I get a DbSet<T>?

My code is the following, but of course, let me know if you need more infos, clarifications or code snippets.

My Controller's Method:

public bool MyMethod (string t, int id, string jsonupdate)
{
    string _tableName = t;
    Type _type = TypeFinder.FindType(_tableName); //returns the correct type

    //FIRST TRY
    //throws error: "_type is a variable but is used like a type"
    var tableSet = _context.Set<_type>();  

    //SECOND TRY
    //returns me an IQueryable<object>, I need an IQueryable<MyType>
    var tableSet2 = _context.Set(_type);  

    //THIRD TRY
    //always returns me am IQueryable<object>, I need an IQueryable<MyType>
    var calcInstance = Activator.CreateInstance(_type);
    var _tableSet3 = _context.Set2(calcInstance);

    //...
}

Class ContextSetExtension

public static class ContextSetExtension
{ 
    public static IQueryable<object> Set(this DbContext _context, Type t)
    {
        var res= _context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
        return (IQueryable<object>)res;
    }


    public static IQueryable<T>Set2<T>(this DbContext _context, T t) 
    {
        var typo = t.GetType();
        return (IQueryable<T>)_context.GetType().GetMethod("Set").MakeGenericMethod(typo).Invoke(_context, null);
    }
}

EDIT Added TypeFinder's inner code.
In brief, this method does the same of Type.GetType, but searches Type on ALL the generated assemblies

public class TypeFinder
{

    public TypeFinder()
    {
    }
    public static Type FindType(string name)
    {


        Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
        var result = (from elem in (from app in assemblies
                                        select (from tip in app.GetTypes()
                                                where tip.Name == name.Trim()
                                                select tip).FirstOrDefault())
                      where elem != null
                      select elem).FirstOrDefault();

        return result;
    }
}

UPDATE as requested in the comments, here's the specific case:

In my DB i've got some tables which are really similar each other, so the idea was to create a dynamic table-update method which would be good for every table, just passing to this method the table name, the ID of the row to update and the JSON containing data to update.
So, in brief, I would perform some updates on the table given in input as DbSet type, updating the row with ID==id in input with the data contained inside the JSON, which will be parsed inside an object of type X(the same of dbset)/into a dictionary.

In pseudo-code:

public bool MyMethod (string t, int id, string jsonupdate)
{
    string _tableName = t;
    Type _type = TypeFinder.FindType(_tableName); //returns the correct type

    //THIS DOESN'T WORKS, of course, since as said above:
    //<<throws error: "_type is a variable but is used like a type">>
    var tableSet = _context.Set<_type>();  

    //parsing the JSON
    var newObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonupdate, _type);

    //THIS OF COURSE DOESN'T WORKS TOO
    //selecting the row to update:
    var toUpdate = tableSet.Where(x => x.Id == id).FirstOrDefault();

    if(toUpdate!=null)
    {

       var newProperties = newObj.GetType().GetProperties();
       var toUpdateProperties = toUpdate.GetType().GetProperties();

       foreach(var item in properties)
       {
           var temp = toUpdateProperties.Where(p => p.Name==item.Name)
           {
              //I write it really in briefand fast, without lots of checks.
              //I think this is enough, I hope
              temp.SetValue(toUpdate, item.GetValue());
           }
       }

       _context.SaveChanges();
    }

    return false;
}
like image 329
Santa Cloud Avatar asked Nov 17 '22 00:11

Santa Cloud


1 Answers

returns me an IQueryable<object>, I need an IQueryable<MyType>

Well, that will never work. Your IQueryable cannot be of type IQueryable<MyType>because that would mean the compiler would need to know what MyType is and that is not possible, because the whole point of this exercise is to decide that on runtime.

Maybe it's enough to know that those objects are in fact instances of MyType?

If not, I think you have painted yourself into a corner here and you are trying to figure out what paint to use to get out of there. Take a step back, it's probably not a technical problem. Why do you need to do this? Why do you have the conflicting needs of knowing the type at runtime only and knowing it at compile time?

You need to think about your requirements, not about the technical details.

like image 132
nvoigt Avatar answered Jan 06 '23 22:01

nvoigt