有没有办法按名称查询 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);
}
我们有大量包含用户 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);
}