簡體   English   中英

使用 EF Core 獲取子實體的數量,而不是使用具有規范模式的通用存儲庫獲取完整實體的數量?

[英]Fetching the count of child entities with EF Core instead of the full entity using a generic repo with specification pattern?

當我為他們的個人資料頁面獲取“用戶”信息時。 我正在從“用戶”(父)實體獲取多個子實體 collections 以從中獲取信息(例如圖像)或者我獲取它們只是為了獲得計數(例如喜歡)

問題- 當獲取所有“Yogaband”子實體時,它正在消磨獲取所需的時間(~12 秒),因為,我假設,每個“Yogaband”實體都有許多子實體,甚至孩子也有孩子,所以有一噸盡管數據庫中大約只有 ~15 個“Yogaband”實體,但數據或底層查詢很復雜(沒看過)。

我需要的 - 我需要的是來自“用戶”的“Yogabands”的計數和子 collections 的其他一些計數,但我不確定如何修改我的規范模式和通用回購以獲取計數。 該代碼現在僅獲取整個實體(例如 AddInclude()) 您可以在下面的自動映射器映射代碼中看到我有 YogabandCount,這是我嘗試保持“Yogabands”的數字計數但最終可能會斷開連接實際計數可能不是處理此問題的最佳方法。 我需要的是一種無需獲取整個實體即可獲取“Yogaband”計數的更好方法。

我想在規范模式下面有這樣的東西

添加計數()

我將從我的 controller 開始獲取“用戶”

[HttpGet("{username}", Name = "GetMember")]
public async Task<IActionResult> GetMember(string username)
{
    var spec = new MembersWithTypesSpecification(username);
    var user = await _unitOfWork.Repository<User>().GetEntityWithSpec(spec);
    if (user == null) return NotFound(new ApiResponse(404));
    var userToReturn = _mapper.Map<MemberForDetailDto>(user);
    return Ok(userToReturn);
}

這是 MembersWithTypesSpecification 用於創建我想要的一切

public class MembersWithTypesSpecification : BaseSpecification<User>
{
    public MembersWithTypesSpecification(string userName) 
        : base(x => x.UserName == userName) 
    {
        AddInclude(x => x.UserPhotos);
        AddInclude(x => x.PracticedStyles);
        AddInclude(x => x.PracticedPoses);
        AddInclude(x => x.InstructedStyles);
        AddInclude(x => x.InstructedPoses);
        AddInclude(x => x.InstructorPrograms);
        AddInclude(x => x.Yogabands);
        AddInclude(x => x.ReceivedReviews);
        AddInclude(x => x.Likers);     
    }
}

在 BaseSpecification 文件中,我在下面為 AddInclude

public class BaseSpecification<T> : ISpecification<T>
{
    public BaseSpecification() {}
    public BaseSpecification(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }
    public Expression<Func<T, bool>> Criteria { get; }
    public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
    public List<string> IncludeStrings { get; } = new List<string>();
    public Expression<Func<T, object>> OrderBy { get; private set; }
    public Expression<Func<T, object>> OrderByDescending { get; private set; }
    public Expression<Func<T, object>> GroupBy { get; private set; }
    public int Take { get; private set; }
    public int Skip { get; private set; }
    public bool IsPagingEnabled { get; private set; }
    protected void AddInclude(Expression<Func<T, object>> includeExpression)
    {
        Includes.Add(includeExpression);
    }
    protected void AddInclude(string includeString)
    {
        IncludeStrings.Add(includeString);
    }
    protected void AddOrderBy(Expression<Func<T, object>> orderByExpression)
    {
        OrderBy = orderByExpression;
    }
    protected void AddOrderByDescending(Expression<Func<T, object>> orderByDescExpression)
    {
        OrderByDescending = orderByDescExpression;
    }
    protected void AddGroupBy(Expression<Func<T, object>> groupByExpression)
    {
        GroupBy = groupByExpression;
    }
    protected void ApplyPaging(int skip, int take)
    {
        Skip = skip;
        Take = take;
        IsPagingEnabled = true;
    }
}

這是我的通用倉庫中的 GetEntityWithSpec()

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    private readonly DataContext _context;
    public GenericRepository(DataContext context)
    {
        _context = context;
    }
    public async Task<T> GetByIdAsync(int id)
    {
        return await _context.Set<T>().FindAsync(id);
    }
    public async Task<IReadOnlyList<T>> ListAllAsync()
    {
        return await _context.Set<T>().ToListAsync();
    }
    public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
    {
        return await ApplySpecification(spec).FirstOrDefaultAsync();
    }
    public async Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec)
    {
        return await ApplySpecification(spec).ToListAsync();
    }
    public async Task<int> CountAsync(ISpecification<T> spec)
    {
        return await ApplySpecification(spec).CountAsync();
    }
    private IQueryable<T> ApplySpecification(ISpecification<T> spec)
    {
        return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
    }
    public void Add(T entity)
    {
        _context.Set<T>().Add(entity);
    }
    public void Update(T entity)
    {
        _context.Set<T>().Attach(entity);
        _context.Entry(entity).State = EntityState.Modified;
    }
    public void Delete(T entity)
    {
        _context.Set<T>().Remove(entity);
    }
    public async Task<bool> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync() > 0;
    }
}

最后,這是我在獲取數據后如何使用 Automapper map 數據。

CreateMap<User, MemberForDetailDto>()
            .ForMember(d => d.YearsPracticing, o => o.MapFrom(s => System.DateTime.Now.Year - s.YearStarted))
            .ForMember(d => d.Age, o => o.MapFrom(d => d.DateOfBirth.CalculateAge()))
            .ForMember(d => d.Gender, o => o.MapFrom(d => (int)d.Gender))
            .ForMember(d => d.Photos, opt => opt.MapFrom(src => src.UserPhotos.OrderByDescending(p => p.IsMain)))
            .ForMember(d => d.Yogabands, opt => opt.MapFrom(source => source.Yogabands.Where(p => p.IsActive).Count()))
            // .ForMember(d => d.Yogabands, opt => opt.MapFrom(source => source.YogabandsCount))
            // .ForMember(d => d.Likers, opt => opt.MapFrom(source => source.Likers.Count()))
            .ForMember(d => d.Likers, opt => opt.MapFrom(source => source.LikersCount))
            .ForMember(d => d.Reviews, opt => {
                opt.PreCondition(source => (source.IsInstructor == true));
                opt.MapFrom(source => (source.ReceivedReviews.Count()));
            })
            // .ForMember(d => d.Reviews, opt => opt.MapFrom(source => source.ReceivedReviews.Count()))
            .ForMember(d => d.ExperienceLevel, opt => opt.MapFrom(source => source.ExperienceLevel.GetEnumName()))
            .ForMember(d => d.PracticedStyles, opt => opt.MapFrom(source => source.PracticedStyles.Count())) 
            .ForMember(d => d.PracticedPoses, opt => opt.MapFrom(source => source.PracticedPoses.Count())) 
            .ForMember(d => d.InstructedStyles, opt => opt.MapFrom(source => source.InstructedStyles.Count())) 
            .ForMember(d => d.InstructedPoses, opt => opt.MapFrom(source => source.InstructedPoses.Count()))
            .ForMember(d => d.InstructorPrograms, opt => opt.MapFrom(source => source.InstructorPrograms.Where(p => p.IsActive == true)));

我不確定您是否有一種直接的方法可以從當前存儲庫實現中獲取計數。 我建議你改造這個。 如果你只想計數,那么你應該只調用計數。

例如,僅獲得yogabands 計數:

_dbContext.Users
    .FindAsync(user => user.Id == userId)
    .Select(u => u.Yogabands)
    .CountAsync();

如果您想要對所有子實體進行計數,您可以部分執行類似的調用並立即或this收集計數。

我假設如果您使用規范模式,您所做的就是嘗試做“DDD 之類”的事情。

首先,當您的存儲庫方法返回IQueryable時,您應該重新思考為什么要使用存儲庫模式。 要在 EF 中使用規范,您根本不需要存儲庫 - 特別是當您使用這樣的實現時。

第二件事,您試圖實現的是存儲庫的責任。 因此,您應該將所有Include / Pagination 等移動到專門的存儲庫方法中。 您的規范應該包含沒有附加邏輯的謂詞(應該只包含域邏輯)。

所以創建像UserRepository這樣的專用存儲庫,然后將它與規范模式一起使用,例如。

class UserRepository : RepositoryBase {
    int CountUsers(UserSpecification spec){
        var query = ApplySpecification(spec);
        return query.Count();
    } 
}

因此,您將能夠評估 DB 方面的情況。

另外……您應該避免以這種丑陋的方式使用 AutoMapper。 如果有這么多自定義映射,手動映射更容易、更安全。 您還可以考慮使用投影而不是在獲取后映射 - 檢查ProjectTo查詢擴展。

我認為這是一個有趣的問題,它的起源是我發現自己無數次查找的知識漏洞:) 這是努力制定通用解決方案! 有時我們會因為 Linq 受到懲罰,因為當執行包含函數時,它通常會在調用給定函數之前解析投影數據。 我相信您需要做的是降低您的通用目標並內聯您的函數,以允許 EF/Linq 處理完整查詢,並且在您閱讀時還要確保使其不可跟蹤。

嘗試根據您的通用性對下面的這個進行基准測試,為了性能,我建議您為了特定而犧牲通用結構。 因此,當需要快速時,我們正在將我們的東西構建到存儲庫中。 我對示例中的數據進行了一些假設,如果有對清晰度的有用更正,請告訴我,因為我在您的代碼中沒有看到 dbcontext。

public class UserDbContext : DbContext {
  DbSet<AppUser> Users { get; set; }
  DbSet<Yogabands> Yogabands { get; set; }

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<AppUser>().HasKey(k => k.AppUserId);
    modelBuilder.Entity<Yogabands>().HasKey(k => k.YogabandsId);
    modelBuilder.Entity<AppUser>().HasMany(m => m.Yogabands).WithMany(n => n.Users);
  }

  public int CountYogaBands(Guid appUserId)
  {
    return Users.AsNoTracking()
            .Include(u => u.Yogabands).AsNoTracking()
            .Where(x => x.AppUserId == appUserId)
            .Select(y => y.Yogabands.Count())
            .FirstOrDefault();
  }

因此,由於您要問的是如何快速執行此操作,因此我的建議是將查詢推送到 dbcontext 並使其特定於避免使用它們之后解析的函數時的功能特性,它是否需要 N 個訪問器方法來代替1,是的,但請考慮這是否可行,畢竟您可以將多個計數返回到一個新對象中,例如

...Select(y => new {
     Yogabands = y.Yogabands.Count(),
     Likers = y.Likers.Count()
   })
   .FirstOrDefault();

盡管您可能更喜歡定義類型而不是泛型,以便更輕松地處理計數結果。

至少我個人的結論是,EF Core 還不夠成熟,無法將動態函數合並到解決最終查詢的 Linq 中,我也從未見過完全有能力的 EF 版本,你必須“非規范化”並且不能完全遵循 DRY 原則。

當您預先給它整個 linq 表達式時,EF Core 仍然會做得很出色,恕我直言,我們應該贊成這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM