简体   繁体   English

如何在实体框架中将字符串作为类型DbSet的类型参数传递?

[英]How to pass an string as a type parameter for a generic DbSet in Entity Framework?

Consider the following sample code, What's the most concise way to pass C# generic type as a parameter? 考虑以下示例代码,将C#泛型类型作为参数传递的最简洁方法是什么?

public dynamic MyDbSet(string typeName)
{
    var typeofDbSet = typeof(DbSet<>);
    Type[] typeArgs = { Type.GetType(typeName) };
    var type = typeofDbSet.MakeGenericType(typeArgs);
    dynamic dbsetOfT = Activator.CreateInstance(type);

    return dbsetOfT;

    //Call it
    //var notificationTypes = MyDbSet("NotificationType");
    //var list = notificationTypes.ToList();
}

Or something like this: 或类似这样的东西:

public dynamic MyDbSet2(string typeName)
{
    var keyValuePairs = new Dictionary<string, dynamic>
    {
        {nameof(NotificationType), Set<NotificationType>().AsQueryable()}
    };

    return keyValuePairs[typeName];
    //Call it
    //var notificationTypes = MyDbSet2("NotificationType");
    //var list = notificationTypes.ToList();
}

Activator.CreateInstance(type) would fail since DbSet has no public parameterless constructor. Activator.CreateInstance(type)将失败,因为DbSet没有公共的无参数构造函数。
Unless you really need to pass the type name as string, the best way to do this is creating a generic function, getting the constructor and invoking it. 除非您确实需要将类型名称作为字符串传递,否则执行此操作的最佳方法是创建泛型函数,获取构造函数并调用它。 It would something like this: 会是这样的:

public DbSet<T> MyDbSet<T>() where T : class
{
    return (DbSet<T>)typeof(DbSet<T>).GetConstructor(BindingFlags.NonPublic |
       BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(null);
}

Then, you would call it by MyDbSet<NotificationType>() 然后,您可以通过MyDbSet<NotificationType>()调用

Update: 更新:
Since passing the name is required, you can do this: 由于需要传递名称,因此您可以执行以下操作:

public dynamic MyDbSet(string typeName)
{
    return typeof(DbSet<>).MakeGenericType(Type.GetType(typeName)).
        GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
        null, Type.EmptyTypes, null).Invoke(null);
}

Then, you would call it by MyDbSet("<namespace>.NotificationType") . 然后,可以通过MyDbSet("<namespace>.NotificationType")调用。 Specifying the namespace is required since otherwise Type.GetType wouldn't find the type and return null 指定名称空间是必需的,因为否则Type.GetType将找不到类型并返回null

Try this extension code: 尝试以下扩展代码:

//Use var list = _context.MyDbSet("ConsoleAppEF.Student").ToList();
public static IQueryable MySet(this SchoolContext context, string typeName)
{
    var T = Type.GetType(typeName);
    // Get the generic type definition
    MethodInfo method = typeof(SchoolContext)
        .GetMethod("MySet", BindingFlags.Public | BindingFlags.Instance);

    // Build a method with the specific type argument you're interested in
    method = method.MakeGenericMethod(T);

    return method.Invoke(context, null) as IQueryable;
}

public static IQueryable<T> Set<T>(this SchoolContext context)
{
    // Get the generic type definition 
    MethodInfo method = typeof(SchoolContext)
        .GetMethod(nameof(SchoolContext.Set), BindingFlags.Public | BindingFlags.Instance);

    // Build a method with the specific type argument you're interested in 
    method = method.MakeGenericMethod(typeof(T));

    return method.Invoke(context, null) as IQueryable<T>;
}

public static IList ToList1(this IQueryable query)
{
    var genericToList = typeof(Enumerable).GetMethod("ToList")
        .MakeGenericMethod(new Type[] { query.ElementType });
    return (IList)genericToList.Invoke(null, new[] { query });
}

Sample DbContext: 样本DbContext:

public class SchoolContext : DbContext
{

    public SchoolContext() : base("SchoolContext")
    {

    }

    public DbSet<Student> Students { get; set; }

    public virtual new DbSet<TEntity> MySet<TEntity>() where TEntity : BaseEntity
    {
        return base.Set<TEntity>();
    }
}

Sample Entity: 样本实体:

public class Student : BaseEntity
{
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
}

public class BaseEntity
{
    public int Id { get; set; }
}

Use it: 用它:

class Program
{
    static void Main(string[] args)
    {
        Seed();
        var _context = new SchoolContext();
        var list = _context.MySet("ConsoleAppEF.Student").ToList1();
    }

    private static void Seed()
    {
        var _context = new SchoolContext();
        var students = new List<Student>
        {
            new Student{FirstMidName="Carson",LastName="Alexander"},
        };

        students.ForEach(s => _context.Students.Add(s));
        _context.SaveChanges();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM