【问题标题】:How to loop through dbcontext all dbset in Entity Framework Core to get count?如何遍历 Entity Framework Core 中的 dbcontext 所有 dbset 以获取计数?
【发布时间】:2021-05-12 16:13:05
【问题描述】:

我的上下文中有 20 个 Dbset,我想获取每个 dbset 的行数,以确保所有 dbset 行数为 0。要获取一个 dbset 的计数,这是我的代码:

var person = context.Persons.Count();

有没有办法循环上下文,动态获取每个 dbset 的计数?

【问题讨论】:

  • 简单。但我认为仅仅得到计数是不够的。您打算如何使用这些信息?
  • 只是在继续进行一些数据导入过程之前验证以确保所有表计数为0。 @SvyatoslavDanyliv

标签: entity-framework-core


【解决方案1】:

有解决办法。用法很简单:

var tablesinfo = ctx.GetTablesInfo();
if (tablesinfo != null)
{
    var withRecords = tablesinfo
        .IgnoreQueryFilters()
        .Where(ti => ti.RecordCount > 0)
        .ToArray();
}

扩展返回IQueryable<TableInfo>,您可以稍后重用此查询。可能您需要过滤掉视图,但我认为您可以处理。请注意,如果您定义了全局查询过滤器,IgnoreQueryFilters 可能很重要。

扩展的作用:

它扫描模型以查找为特定 DbContext 注册的实体类型,并生成 Concat 的大 Count 查询。在这里,我们必须通过按常量值分组来做到这一点。

从原理上讲,它将生成以下 LINQ 查询:

var tablesinfo = 
            ctx.Set<Entity1>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity1", RecordCount = g.Count()})
    .Concat(ctx.Set<Entity2>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity2", RecordCount = g.Count()}))
    .Concat(ctx.Set<Entity3>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity3", RecordCount = g.Count()}))
    ...

这将被转换为以下SQL:


SELECT "Entity1" AS TableName, COUNT(*) AS RecordCount FROM Entity1
UNION ALL
SELECT "Entity2" AS TableName, COUNT(*) AS RecordCount FROM Entity2
UNION ALL
SELECT "Entity3" AS TableName, COUNT(*) AS RecordCount FROM Entity3
...

实施:

public static class QueryableExtensions
{
    public class TableInfo
    {
        public string TableName { get; set; } = null!;
        public int RecordCount { get; set; }
    }

    public static IQueryable<TableInfo> GetTablesInfo(this DbContext ctx)
    {
        Expression query = null;
        IQueryProvider provider = null;

        var ctxConst = Expression.Constant(ctx);
        var groupingKey = Expression.Constant(1);

        // gathering information for MemberInit creation 
        var newExpression = Expression.New(typeof(TableInfo).GetConstructor(Type.EmptyTypes));
        var tableNameProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.TableName));
        var recordCountProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.RecordCount));

        foreach (var entityType in ctx.Model.GetEntityTypes())
        {
            var entityParam = Expression.Parameter(entityType.ClrType, "e");
            var tableName = entityType.GetTableName();

            // ctx.Set<entityType>()
            var setQuery = Expression.Call(ctxConst, nameof(DbContext.Set), new[] {entityType.ClrType});

            // here we initialize IQueryProvider, which is needed for creating final query
            provider ??= ((IQueryable) Expression.Lambda(setQuery).Compile().DynamicInvoke()).Provider;

            // grouping paraneter has generic type, we have to specify it
            var groupingParameter = Expression.Parameter(typeof(IGrouping<,>).MakeGenericType(typeof(int), entityParam.Type), "g");

            // g => new TableInfo { TableName = "tableName", RecordCount = g.Count() }
            var selector = Expression.MemberInit(newExpression, 
                Expression.Bind(tableNameProperty, Expression.Constant(tableName)),
                Expression.Bind(recordCountProperty,
                    Expression.Call(typeof(Enumerable), nameof(Enumerable.Count), new[] {entityParam.Type}, groupingParameter)));

            // ctx.Set<entityType>.GroupBy(e => 1)
            var groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy), new[]
                {
                    entityParam.Type,
                    typeof(int)
                },
                setQuery,
                Expression.Lambda(groupingKey, entityParam)
            );

            // ctx.Set<entityType>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "tableName",  RecordCount = g.Count()}))
            groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
                new[] {groupingParameter.Type, typeof(TableInfo)}, 
                groupByCall,
                Expression.Lambda(selector, groupingParameter));

            // generate Concat if needed
            if (query != null)
                query = Expression.Call(typeof(Queryable), nameof(Queryable.Concat), new[] {typeof(TableInfo)}, query,
                    groupByCall);
            else
                query = groupByCall;
        }

        // unusual situation, but Model can have no registered entities
        if (query == null)
            return null;

        return provider.CreateQuery<TableInfo>(query);
    }
}

【讨论】:

  • 我正在使用依赖注入并在构造函数中为上下文创建了我的上下文。所以当我传入_context时,它会说cannot convert from 'xxx.Interfaces.IAbcContext' to 'Microsoft.EntityFrameworkCore.DbContext'
  • 所以你给自己制造了困难。找到一种在提议的函数中传递DbContext 的方法。我对您的接口和存储库一无所知。
  • 确保函数可以重写以读取属性。但这不是原始问题。
  • 投了反对票,因为有零个 cmets 解释了 GetTablesInfo 方法的作用。如果你不解释,人们应该如何学习?
  • @cdarrigo,享受吧。我希望现在很清楚。
猜你喜欢
  • 1970-01-01
  • 2017-06-13
  • 2019-03-09
  • 2016-05-13
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
  • 2019-06-08
  • 1970-01-01
相关资源
最近更新 更多