использование отложенного выполнения IQueryable

Я работаю над простым отображением EntityFramework ‹> DTO, оно отлично работает, за исключением отложенного выполнения, у меня есть следующий код:

public abstract class Assembler<TDto, TEntity> : IAssembler<TDto, TEntity>
    where TEntity : EntityBase , new ()
    where TDto : DtoBase, new ()
{
    public abstract TDto Assemble(TEntity domainEntity);
    public abstract TEntity Assemble(TEntity entity, TDto dto);

    public virtual IQueryable<TDto> Assemble(IQueryable<TEntity> domainEntityList)
    {
        List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
        foreach (TEntity domainEntity in domainEntityList)
        {
            dtos.Add(Assemble(domainEntity));
        }
        return dtos.AsQueryable();
    }

    public virtual IQueryable<TEntity> Assemble(IQueryable<TDto> dtoList)
    {
        List<TEntity> domainEntities = Activator.CreateInstance<List<TEntity>>();
        foreach (TDto dto in dtoList)
        {
            domainEntities.Add(Assemble(null, dto));
        }
        return domainEntities.AsQueryable();
    }
}

Пример ассемблера:

public partial class BlogEntryAssembler : Assembler<BlogEntryDto, BlogEntry>, IBlogEntryAssembler
{
    public override BlogEntry Assemble(BlogEntry entity, BlogEntryDto dto)
    {
        if (entity == null)
        {
            entity = new BlogEntry();
        }
        /*
        entity.Id = dto.Id;
        entity.Created = dto.Created;
        entity.Modified = dto.Modified;
        entity.Header = dto.Header;
        */
        base.MapPrimitiveProperties(entity, dto);

        this.OnEntityAssembled(entity);
        return entity;
    }

    public override BlogEntryDto Assemble(BlogEntry entity)
    {
        BlogEntryDto dto = new BlogEntryDto();

        //dto.Id = entity.Id;
        //dto.Modified = entity.Modified;
        //dto.Created = entity.Created;
        //dto.Header = entity.Header;

        base.MapPrimitiveProperties(dto, entity);

        dto.CategoryName = entity.Category.Name;
        dto.AuthorUsername = entity.User.Username;
        dto.AuthorFirstName = entity.User.FirstName;
        dto.AuthorLastName = entity.User.LastName;

        dto.TagNames = entity.Tags.Select(t => t.Name)
            .ToArray();

        dto.TagIds = entity.Tags.Select(t => t.Id)
            .ToArray();

        dto.VotedUpUsernames = entity.BlogEntryVotes.Where(v => v.Vote > 0)
            .Select(t => t.User.Username)
            .ToArray();

        dto.VotedDownUsernames = entity.BlogEntryVotes.Where(v => v.Vote < 0)
            .Select(t => t.User.Username)
            .ToArray();

        // Unmapped
        dto.FileCount = entity.BlogEntryFiles.Count();
        dto.CommentCount = entity.BlogEntryComments.Count();
        dto.VisitCount = entity.BlogEntryVisits.Count();
        dto.VoteCount = entity.BlogEntryVotes.Count();
        dto.VoteUpCount = entity.BlogEntryVotes.Count(v => v.Vote.Equals(1));
        dto.VoteDownCount = entity.BlogEntryVotes.Count(v => v.Vote.Equals(-1));
        dto.VotePuntuation = entity.BlogEntryVotes.Sum(v => v.Vote);
        dto.Published = entity.Visible && entity.PublishDate <= DateTime.Now;

        this.OnDTOAssembled(dto);
        return dto;
    }
}

мой класс обслуживания:

    public virtual PagedResult<BlogEntryDto> GetAll(bool includeInvisibleEntries, string tag, string search, string category, Paging paging)
    {
        var entries = this.Repository.GetQuery()
            .Include(b => b.Tags)
            .Include(b => b.User)
            .Include(b => b.Category)
            .Include(b => b.BlogEntryFiles)
            .Include(b => b.BlogEntryComments)
            .Include(b => b.BlogEntryPingbacks)
            .Include(b => b.BlogEntryVisits)
            .Include(b => b.BlogEntryVotes)
            .Include(b => b.BlogEntryImages)
           .AsNoTracking();

        if (!includeInvisibleEntries)
        {
            entries = entries.Where(e => e.Visible);
        }

        if (!string.IsNullOrEmpty(category))
        {
            entries = entries.Where(e => e.Category.Name.Equals(category, StringComparison.OrdinalIgnoreCase));
        }

        if (!string.IsNullOrEmpty(tag))
        {
            entries = entries.Where(e => e.Tags.Count(t => t.Name.Equals(tag, StringComparison.OrdinalIgnoreCase)) > 0);
        }

        if (!string.IsNullOrEmpty(search))
        {
            foreach (var item in search.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
            {
                entries = entries.Where(e => e.Header.Contains(item));
            }
        }

        return this.Assembler.Assemble(entries).GetPagedResult(paging);
    }

Когда я вызываю метод GetAll, он возвращает и преобразует все сущности в таблице в Dto, и только затем выгружает результирующую коллекцию, конечно, это не то, что я ожидал. Я хотел бы выполнить код внутри моего метода Assemble после того, как пейджинг был сделан, есть идеи?

PS: я знаю, что мог бы использовать Automapper, просто пытаясь изучить внутренности.


person Marc    schedule 21.02.2014    source источник


Ответы (2)


В вашем ассемблере вы добавляете спроецированные DTO в файл List<TDto>. Это вредно в двух отношениях. Во-первых, это принудительное выполнение, потому что список заполняется, а затем возвращается. Во-вторых, в этот момент вы переключаетесь с LINQ на Entities на LINQ на объекты и обратной дороги нет. Вы можете снова преобразовать список в IQueryable с помощью AsQueryable, но это не приведет к повторной вставке поставщика запросов EF. На самом деле преобразование бесполезно.

Вот почему оператор AutoMapper ProjectTo<T> такой классный. Он передает выражения, идущие после To, обратно к исходному IQueryable и, следовательно, к его поставщику запросов. Если эти выражения содержат операторы подкачки (Skip/Take), они будут преобразованы в SQL. Поэтому я думаю, что вы быстро придете к выводу, что вам лучше использовать AutoMapper.

person Gert Arnold    schedule 21.02.2014

public virtual IQueryable<TDto> Assemble(IQueryable<TEntity> domainEntityList)
{
    List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
    foreach (TEntity domainEntity in domainEntityList)
    {
        dtos.Add(Assemble(domainEntity));
    }
    return dtos.AsQueryable();
}

Цикл foreach в приведенном выше коде — это точка, в которой вы выполняете запрос на сервере базы данных.

Как видно из этой строки:

вернуть this.Assembler.Assemble(записи).GetPagedResult(пейджинг);

Этот метод вызывается перед GetPagedResult(paging).. так что это причина, по которой пейджинг происходит в полном наборе результатов.

Вы должны понимать, что перечисление запроса (foreach) требует выполнения запроса. Ваш цикл foreach обрабатывает каждую запись, возвращенную этим запросом. На данный момент слишком поздно для метода пейджинга, чтобы остановить его!

person Martin Booth    schedule 21.02.2014