根据实体类名动态获取DbSet<T>

5
我正在尝试使用 System.Reflections 动态获取 DbSet<T>。目前我已经拥有以下内容:
- DbSet 名称 - 存储在变量中的 DbSetType 当我尝试使用 dbcontext.Set<T>() 方法时,我遇到了问题,因为(这是我迄今为止的尝试):
- 当我尝试将我的 DbSetType 分配给 <T> 时,它抛出以下编译错误:

"XXX 是一个变量,但被用作类型"

- 如果我尝试使用我在代码中找到的两个扩展方法(我制作这些方法是为了尝试获取 IQueryable<T>),它会返回一个 IQueryable<object>,不幸的是,这不是我要找的,因为当我尝试使用进一步的反射操作它时,它缺少所有原始类具有的属性...
我做错了什么?我怎样才能得到一个 DbSet<T>
以下是我的代码,如果需要更多信息、澄清或代码片段,请告诉我。 我的控制器方法:
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);
    }
}

编辑 添加了 TypeFinder 的内部代码。
简而言之,该方法与 Type.GetType 相同,但在所有生成的程序集中搜索类型。

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;
    }
}

更新 根据评论的要求,这是具体的案例:

我的数据库中有一些非常相似的表,所以想法是创建一个动态表更新方法,对于每个表都很好用,只需将表名、要更新的行的ID和包含要更新数据的JSON传递给此方法。
因此,简而言之,我将执行一些在输入的DbSet类型表上的更新,使用输入中的ID==id更新行,并将其内容解析为类型X(与dbset相同的对象)/转换为字典。

伪代码:

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;
}

可能是如何使用反射调用泛型方法?的重复问题。 - Flater
1
@Flater 这个问题类似但并非重复。我昨天浏览了那个问题和它的答案,非常不错,但并没有涵盖我的问题(正如nvoigt在他的回答中所说,这些问题是无法用我原本打算的方式解决的,唯一要做的就是改变我的总体方法)。 - Santa Cloud
1
假设您有一个方法 public bool MyMethod<T>(int id, string jsonupdate) where T : class。您会在该方法中做什么? - Ivan Stoev
2
因为除非存在特定的用例,否则您的问题是Dynamically access table in EF Core 2.0的重复。 您可以获取IQueryableIQueryable<object>,但是除了使用Dymanic LINQ,未类型化的DbContext方法,反射,手动表达式树构建等方式外,您无法对其进行任何操作。 因此,请展示您的使用情况(伪代码),我们将为您提供最佳途径。 - Ivan Stoev
@IvanStoev 关于您的第一条评论:在我的数据库中,我有一些非常相似的表格,所以我的想法是创建一个动态的表格更新方法,适用于每个表格。因此,简而言之,我将对输入的DbSet类型的表格执行一些更新,在输入的ID == id的行中使用JSON中包含的数据进行更新,该数据将被解析为X类型(与dbset相同)的对象/字典中。 - Santa Cloud
显示剩余2条评论
3个回答

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

好的,那是行不通的。你的IQueryable不能是IQueryable<MyType>类型,因为这意味着编译器需要知道MyType是什么,而这是不可能的,因为整个练习的重点是在运行时决定它。

也许只需要知道这些对象实际上是MyType的实例吗?

如果不是,我认为你把自己画进了一个死角,正在努力想出用什么颜料才能走出去。退一步,这可能不是技术问题。你为什么需要这样做?为什么你需要在运行时只知道类型,同时又需要在编译时知道它?

你需要考虑你的需求,而不是技术细节。


嗨,感谢您的回答!针对您的问题:“为什么需要这样做?”,情况是我现在从前端接收到表名、我需要编辑的行的ID以及包含要更新该行的新数据的JSON。由于我的数据库中有很多非常相似的表,我正在尝试创建一个唯一的管理器(出于优化和可维护性的考虑)来管理不同的dbset以执行更新...那么...这是不可能的吗?我必须静态地管理每个表的不同方法吗? - Santa Cloud
1
当您将通过反射设置值时,确实有可能使用IQueryable<MyType>,但并非必须。 - nvoigt
但如果没有泛型类型而是使用Object,则无法搜索具有"XXXX"作为ID的元素,否则会出现"type object has no ID property"的错误,即使在运行时它代表一个具有ID属性的实体...我不知道我是否解释清楚了... - Santa Cloud
1
你如何知道实体是否具有Id属性?你能定义所有实体类型的公共接口吗? - nvoigt
是的,实际上在这个项目中,我可以通过接口来解决这个问题,这可能会起作用,但如果无法通过某种方式将dbset转换/声明为动态泛型类型来解决此问题,我将把它作为最后的手段。 - Santa Cloud

0
我需要动态加载已知类型列表中的每个类型中来自数据库的单个记录,以便在管理员编辑模板时打印测试电子邮件,所以我做了这个:
List<object> args = new List<object>();

//...
//other stuff happens that isn't relevant to the OP, including adding a couple fixed items to args
//...

foreach (Type type in EmailSender.GetParameterTypes())
{
    //skip anything already in the list
    if (args.Any(a => a.GetType().IsAssignableFrom(type))) continue;

    //dynamically get an item from the database for this type, safely assume that 1st column is the PK
    string sql = dbContext.Set(type).Sql.Replace("SELECT", "SELECT TOP 1") + " ORDER BY 1 DESC";
    var biff = dbContext.Set(type).SqlQuery(sql).AsNoTracking().ToListAsync().Result.First();
    args.Add(biff);
}

注意:我知道为我处理的所有实体至少存在一个记录,并且每种类型只能传递一个实例给电子邮件生成器(其具有多个Debug.Asserts来测试实现的有效性)。

如果您知道要查找的记录ID,而不是整个表,则可以使用dbContext.Set(type).Find()。 如果您想获取您已了解的任何类型的整个表,只需执行此操作即可:

string sql = dbContext.Set(type).Sql; //append a WHERE clause here if needed/feasible, use reflection?
var biff = dbContext.Set(type).SqlQuery(sql).ToListAsync().Result;

感觉有点笨重,但它能用。奇怪的是没有不带Async的ToList,但我可以在这里同步运行。在我的情况下,关闭代理创建是必要的,但你似乎想要维护一个有上下文状态的状态,以便可以写回到数据库中。我稍后会做一些反射操作,所以我并不关心这样一个结果集合的强类型(因此是List<object>)。但是一旦你有了集合(即使只是作为对象),你应该能够像你在UPDATE示例代码中所做的那样使用System.Reflection,因为你知道类型,并且可以使用已知/给定的属性名称以这种方式使用SetValue。

我正在使用.NET Framework,但希望这也适用于.NET Core。


-1

编辑:已测试并可用:

public async Task<bool> MyMethod(string _type)
{
    Type type = Type.GetType(_type);

    var tableSet = _context.Set(type);

    var list = await db.ToListAsync();

    // do something
}

// pass the full namespace of class
var result = await MyMethod("Namespace.Models.MyClass")

重要提示:你的DbContext需要声明DbSet才能正常工作!

public class MyContext : DbContext
{
    public DbSet<MyClass> MyClasses { get; set; }
}

嗨,我已经尝试过了,但不幸的是,在var dbSet = _context.Set<type>();上仍然出现了“type是变量但被用作类型”的错误。 - Santa Cloud
我已经编辑了答案,使用Type type = Type.GetType(_type);和var tableSet = _context.Set(type);进行操作。 - Charles Cavalcante
看代码,你的编辑器的.Set(type)使用了Set扩展,是吗?因为如果它使用了Set扩展,问题就在于它返回了一个IQueryable<object>。如果我需要将其返回到ToList()或其他“通用”方法中,这样做是可以的,但这与我的尝试#2和#3是相同的情况。(PS:为避免误解,我不是那个给你的答案投反对票的人) - Santa Cloud
@CharlesCavalcante 有没有一种方法可以获取 DbContext 中的 DbSet 属性的名称? - Shimmy Weitzhandler
1
很遗憾,在 .Net Core 中无法再使用 _context.Set(type)。 - MartinS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接