【问题标题】:Get dependent ids when querying principal查询主体时获取依赖ID
【发布时间】:2022-09-23 21:59:42
【问题描述】:

如果每次查询委托人时查询委托人,我试图只获取家属的 ID。

我最初的想法是以某种方式将它添加到 OnModelCreating 定义中,但这似乎仅限于过滤更大的数据集,除非我遗漏了一些东西。

像这样的东西:

    builder.Entity<ListingModel>()
        .AlsoDoThis(
            x => x.MenuIds.AddRange(
                Menus.Where(y => y.ListingId == x.Id).Select(y => y.Id).ToList()
            )
        );

有必要不是在代码中为我有一个Select 的每个地方执行此操作,因为该功能在some base classes 中进行了规范化。基类有一个&lt;TModel&gt; 传入,并且不知道需要以这种方式处理哪些属性。


有一个解决方法,我使用AutoInclude() 获取所有内容,然后在模型定义中使用客户 getter/setter 将其过滤掉以返回 id 列表。但是,据我所知,它并没有提高性能(在数据库级别获取相关的 FK id),而是将所有数据传输到服务器,然后以编程方式选择一个 id 列表。

private List<int> _topicsIds = new();
[NotMapped]
public List<int> TopicsIds
{
    get { return Topics.Count > 0 ? Topics.Select(x => x.Id).ToList() : _topicsIds; }
    set { _topicsIds = value; }
}
public List<TopicModel> Topics { get; set; } = new();

\"在上下文中每次选择都会调用的额外 SQL\" 是(据我所知)几乎HasQueryFilter 所做的,只是稍微更广泛的操作。我认为这是我正在寻找的方法,只是选择更多的东西代替过滤掉东西.

  • 您可以通过Select 使用自定义投影来做到这一点。在这种情况下不需要包含。
  • @SvyatoslavDanyliv 你有一个例子或你可以指出的文档吗?我在这里没有遵循您的想法,但我对 EFC 有点陌生。

标签: entity-framework-core


【解决方案1】:

您可以通过Select 投影所有内容

var result = ctx.ListingModels
   .Select(lm => new // or to DTO
   {
       Id = lm.Id,
       OtherProperty = lm.OtherProperty,
       
       Ids = x.MenuIds.Select(m => m.Id).ToList()
   })
   .ToList();

为了制定更通用的解决方案,我们可以使用注释并定义如何投影此类实体。

在模型定义期间:

builder.Entity<TopicModel>()
    .WithProjection(
        x => x.MenuIds,
        x => x.Menus.Where(y => y.ListingId == x.Id).Select(y => y.Id).ToList()
    );

然后在通用代码中使用:

public virtual List<TModel> GetList(List<int> ids)
{
    var list = _context.Set<TModel>().Where(x => ids.Any(id => id == x.Id))
        .ApplyCustomProjection(_context)
        .ToList();
    return list;
}

ApplyCustomProjection(_context) 将找到先前定义的注释并应用自定义投影。


和扩展实现:

public static class ProjectionExtensions
{
    public const string CustomProjectionAnnotation = "custom:member_projection";

    public class ProjectionInfo
    {
        public ProjectionInfo(MemberInfo member, LambdaExpression expression)
        {
            Member = member;
            Expression = expression;
        }

        public MemberInfo Member { get; }
        public LambdaExpression Expression { get; }
    }

    public static EntityTypeBuilder<TEntity> WithProjection<TEntity, TValue>(
        this EntityTypeBuilder<TEntity> entity, 
        Expression<Func<TEntity, TValue>> propExpression, 
        Expression<Func<TEntity, TValue>> assignmentExpression) 
        where TEntity : class
    {
        var annotation = entity.Metadata.FindAnnotation(CustomProjectionAnnotation);
        var projections = annotation?.Value as List<ProjectionInfo> ?? new List<ProjectionInfo>();

        if (propExpression.Body is not MemberExpression memberExpression)
            throw new InvalidOperationException($"'{propExpression.Body}' is not member expression");

        if (memberExpression.Expression is not ParameterExpression)
            throw new InvalidOperationException($"'{memberExpression.Expression}' is not parameter expression. Only single nesting is allowed");

        // removing duplicate
        projections.RemoveAll(p => p.Member == memberExpression.Member);

        projections.Add(new ProjectionInfo(memberExpression.Member, assignmentExpression));
        return entity.HasAnnotation(CustomProjectionAnnotation, projections);
    }

    public static IQueryable<TEntity> ApplyCustomProjection<TEntity>(this IQueryable<TEntity> query, DbContext context)
        where TEntity : class
    {
        var et = context.Model.FindEntityType(typeof(TEntity));
        var projections = et?.FindAnnotation(CustomProjectionAnnotation)?.Value as List<ProjectionInfo>;

        // nothing to do
        if (projections == null || et == null)
            return query;

        var propertiesForProjection = et.GetProperties().Where(p =>
            p.PropertyInfo != null && projections.All(pr => pr.Member != p.PropertyInfo))
            .ToList();

        var entityParam = Expression.Parameter(typeof(TEntity), "e");

        var memberBinding = new MemberBinding[propertiesForProjection.Count + projections.Count];
        for (int i = 0; i < propertiesForProjection.Count; i++)
        {
            var propertyInfo = propertiesForProjection[i].PropertyInfo!;
            memberBinding[i] = Expression.Bind(propertyInfo, Expression.MakeMemberAccess(entityParam, propertyInfo));
        }

        for (int i = 0; i < projections.Count; i++)
        {
            var projection = projections[i];
            var expression = projection.Expression.Body;

            var assignExpression = ReplacingExpressionVisitor.Replace(projection.Expression.Parameters[0], entityParam, expression);

            memberBinding[propertiesForProjection.Count + i] = Expression.Bind(projection.Member, assignExpression);
        }

        var memberInit = Expression.MemberInit(Expression.New(typeof(TEntity)), memberBinding);

        var selectLambda = Expression.Lambda<Func<TEntity, TEntity>>(memberInit, entityParam);

        var newQuery = query.Select(selectLambda);
        return newQuery;
    }
}

【讨论】:

  • 啊好吧。这是有道理的,但我正在尝试在更通用的级别上执行此操作(自动返回依赖 ID,而不是每次使用主体时手动选择它们)
  • 这是正确的方式。您只需要对一个控制器进行此类查询,为什么必须在模型中指定某些内容?
  • 更大的范围更复杂。我有一个动态的基础服务和控制器类,它们锁定了一些标准功能以与许多模型进行交互。列出每个子类的选择中的每个属性基本上否定了目的
  • 然后动态构建表达式树,没什么特别的。
  • @RandyHall,更新了实现这种动态投影的问题。
猜你喜欢
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 1970-01-01
  • 2020-06-17
  • 2022-12-05
  • 1970-01-01
相关资源
最近更新 更多