【问题标题】:EF Core: how to access DBSets programmatically for Seed purposesEF Core:如何以编程方式访问 DBSet 以用于种子目的
【发布时间】:2021-03-11 03:01:29
【问题描述】:

在 EF Core 3.1+ 中,我想使用 Seed() 扩展方法以编程方式为任何 DbContext 中的所有 DBSet 播种数据。 Seed() 方法在非动态情况下运行良好。

对于手动解决方案,我可以添加代码(可以为每个实体添加记录),例如:

   Categories.AddRange(Categories.ToList().Seed(10));
   Products.AddRange(Products.ToList().Seed(20));
   ...
   SaveChanges();

我对适用于任何 DbContext 的动态解决方案的方法是这样的代码:

var entityTypes = myContext.Model.GetEntityTypes();

foreach (var et in entityTypes)
{
    myContext.Set(et.ClrType).ToList().Seed();
}

那是引用一个扩展方法:

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

但返回错误:

System.Reflection.AmbiguousMatchException: '发现不明确的匹配。'

这是一个使用 EF 3.1+ 的通用解决方案,我想循环遍历当前上下文中的任何 DbSet 并为其播种。

我可以手动添加代码(可以为每个实体添加记录),例如:

   Categories.AddRange(Categories.ToList().Seed(10));
   Products.AddRange(Products.ToList().Seed(20));
   ...
   SaveChanges();

知道如何获得对 DBSet 的通用编程访问,以便我可以向每个 DBSet 添加数据吗?

===编辑============================================ ===============

我更新了扩展方法,因为我意识到重复是针对返回的多个方法,所以我让该部分按如下方式工作。此代码正确返回 DBSet(有点):

public static IQueryable<object> Set(this DbContext _context, Type T)
{
    return (IQueryable<object>)_context.GetType()
            .GetMethods()
            .First(p => p.Name == "Set" && p.ContainsGenericParameters)
            .MakeGenericMethod(T)
            .Invoke(_context, null);
}

现在代码运行没有抛出错误,但没有添加项目。

简化代码以便 Seed() 函数不是一个因素,我决定只添加一条记录进行测试,如下所示。我可以看到 DBSet 但仍然没有新记录。

// Does no throw an error but does not add the item
dynamic newItem = Activator.CreateInstance(et.ClrType);
var set = myContext.Set(et.ClrType);
set.ToList().Add(newItem); // Simplified from .Seed(); 

对此有什么想法吗?

【问题讨论】:

  • 尝试用typeof(DbContext)替换_context.GetType()
  • @ivan-stoev - 谢谢,但是使用 typeof(DbContext) 我们仍然会遇到同样的错误。
  • EF Core 5.0?那么typeof(DbContext).GetMethod(nameof(DbContext.Set), Type.EmptyTypes) 怎么样。

标签: c# entity-framework entity-framework-core seeding


【解决方案1】:

DbContext 有多个带有不同参数的Set&lt;T&gt; 方法。

获得正确的MethodInfo 的简单方法是创建一个匹配的委托;

var method = new Func<DbSet<object>>(_context.Set<object>).Method.GetGenericMethodDefinition();
method.MakeGenericMethod(T).Invoke( ... );

当然,如果您为每种类型定义了 DbSet 属性,则可以循环访问这些属性。

您的另一个问题是set.ToList().Add(...。调用ToList 将整个表加载到一个列表对象中,该对象不知道它来自的 DbSet。你需要打电话给DbSet&lt;T&gt;.Add,而不是List&lt;T&gt;.Add

我会将您的整个种子方法移动到一个泛型方法中,为每种类型设置该泛型,然后调用它。

private void Seed<T>(DbContext context, T obj) =>
    context.Set<T>.Add(obj);

var method = new Action<DbContext, object>(Seed<object>).Method.GetGenericMethodDefinition();
foreach (var et in entityTypes)
    method.MakeGenericMethod(et.ClrType).Invoke( ... );

【讨论】:

  • 关于 ToList() 的要点。我会看看。谢谢
【解决方案2】:

对于 EF Core 来说,这似乎是一件简单的事情,但事实并非如此,而且可能是不可能的。

link 看来,最近对 EF 的更改似乎使这更难做到。

如果您使用“DbContent.Set”通过反射跳过箍以获取 DbSet 列表,您会发现这些实际上是“InternalDbSet”类型,并带有一堆关于您不应如何使用它们的警告。无法将它们强制转换为 DbSet 或 IDbSet 等接口。

但如果有人有好的解决方案,请发布,因为我现在很想看看是否可以做到。

【讨论】:

    猜你喜欢
    • 2012-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-10
    • 2015-09-03
    • 2020-05-05
    • 1970-01-01
    • 1970-01-01
    • 2013-06-07
    相关资源
    最近更新 更多