简体   繁体   中英

using IQueryable deferred execution

I am working on a simple mapping EntityFramework <> DTO's , it's working perfecly excepto for the deferred execution , I have the following code :

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();
    }
}

Sample Assembler :

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;
    }
}

my service class :

    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);
    }

When I call the GetAll method it returns and converts all the entities in the table to Dto's and only then it pages the resulting collection, of course that's not what I was expecting.. I would like to execute the code inside my Assemble method once the paging has been done, any idea?

PS : I know I could use Automapper, just trying to learn the internals.

In your assembler you add the projected DTOs to a List<TDto> . That's detrimental in two ways. First, it is forced execution, because the list is filled and then returned. Second, at that moment you switch from LINQ to Entities to LINQ to objects and there is no way back. You can convert the list to IQueryable again by AsQueryable , but that does not re-inject the EF query provider. In fact, the conversion is useless.

That's why AutoMapper's ProjectTo<T> statement is so cool. It transfers expressions that come after the To all the way back to the original IQueryable and, hence, its query provider. If these expressions contain paging statements ( Skip/Take ) these will be translated into SQL. So I think that you'll quickly come to the conclusion you better use AutoMapper after all.

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();
}

The foreach loop in the above code is the point at which you're executing the query on the database server.

As you can see from this line:

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

This method is getting called before GetPagedResult(paging).. so that is the reason paging happens on the full result set.

You should understand that enumerating a query (foreach) requires the query to run. Your foreach loop processes each and every record returned by that query.. it's too late at this point for a Paging method to do anything to stop it!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM