有没有办法按名称查询 DbSet 的属性?

Is there a way to query properties, by name, of a DbSet, by name?

我们有大量包含用户 ID 属性的实体,我创建了一个自定义属性 UserIdAttribute 来注释这些模型。例如:

public class MyEntity
{
  [UserId]
  public Guid Owner { get; set; }
}

我们将有一个后台服务,其目的是在所有实体中查找用户 ID 属性并对其执行查找以填充另一个 table。我们希望动态地找到这些值,因为我们 models/application 的状态正在不断变化。我知道我可以使用 Microsoft.Data.SqlClient 实现我想要的,如下所示:


var allUserIds = new List<Guid>();

foreach (var entityType in dbContext.Model.GetEntityTypes())
{
  foreach (var property in entityType.GetProperties())
  {
    foreach var attribute in property.PropertyInfo.GetCustomAttributes())
    {
      if (attribute is MyCustomAttribute)
      {
        //
        // do this using EF & reflection
        //
        using (var connection = new SqlConnection("my_conn_string"))
        {
          try
          {
            var tableName = entityType.GetTableName();
            var command = new SqlCommand(
              $"select {property.Name} from {tableName}",
              conn);
            conn.Open();
            var reader = command.ExecuteReader();
            while (reader.Read())
            {
              Console.WriteLine($"{entityType.ClrType.Name}.{property.Name}: {reader[property.Name]}");
            }
          }
          finally
          {
            conn.Close();
          }
        }
      }
    }
  }
}

PopulateUserInfoTable(allUserIds);

我们更愿意在这里使用 EF,问题是当我只有 DbSet 名称的字符串表示形式时,我看不到任何查询 DbSet 的方法。这甚至可以实现吗?

实际上,不需要任何不需要的数据库往返就很容易。 LINQ 非常酷。

var valuesQuery = CreateValuesQuery<Guid>(ctx, (e, p) => 
   p.PropertyInfo.GetCustomAttributes().Any(a => a is MyCustomAttribute));

if (valuesQuery != null)
{
   var userInfoQuery = 
      from ui in dbContext.UserInfoTable
      join v in valuesQuery on ui.UserId equals v
      select ui;

   var infoResut = userInfoQuery.ToList();
}

和实现:

public static IQueryable<T> CreateValuesQuery<T>(DbContext ctx, Func<IEntityType, IProperty, bool> filter)
{
    Expression query = null;
    IQueryProvider provider = null;
    var ctxConst = Expression.Constant(ctx);
    foreach (var entityType in ctx.Model.GetEntityTypes())
    {
        ParameterExpression entityParam = null;
        foreach (var property in entityType.GetProperties().Where(p => p.ClrType == typeof(T) && filter(entityType, p)))
        {
            entityParam ??= Expression.Parameter(entityType.ClrType, "e");
            var setQuery = Expression.Call(ctxConst, nameof(DbContext.Set), new[] {entityType.ClrType});
            provider ??= ((IQueryable) Expression.Lambda(setQuery).Compile().DynamicInvoke()).Provider;
            var propertyLambda = Expression.Lambda(Expression.MakeMemberAccess(entityParam, property.PropertyInfo), entityParam);
            var projection = Expression.Call(typeof(Queryable), nameof(Queryable.Select), new[]
                {
                    entityType.ClrType,
                    typeof(T)
                }, 
                setQuery, 
                propertyLambda
            );
            if (query != null)
                query = Expression.Call(typeof(Queryable), nameof(Queryable.Union), new []{typeof(T)}, query, projection);
            else
                query = projection;
        }
    }

    if (query == null)
        return null;

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