public Task <IList <TodoItemInfo> > GetByQueryAsync(TodoItemQuery todoItemQuery)
        {
            if (todoItemQuery == null)
            {
                throw new ArgumentNullException(nameof(todoItemQuery));
            }

            Validator.ValidateObject(todoItemQuery, new ValidationContext(todoItemQuery), validateAllProperties: true);
            return(InternalGetByQueryAsync(todoItemQuery));
        }
        private async Task <IList <TodoItemInfo> > InternalGetByQueryAsync(TodoItemQuery todoItemQuery)
        {
            IQueryable <TodoItem> todoItems = FilterItems(todoItemQuery)
                                              // Read more about query tags here:
                                              // https://docs.microsoft.com/en-us/ef/core/querying/tags
                                              .TagWith($"{nameof(TodoItemService)}#{nameof(GetByQueryAsync)}")
                                              // Read more about no tracking queries here:
                                              // https://docs.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries
                                              .AsNoTracking();

            todoItems = SortItems(todoItems, todoItemQuery);
            todoItems = PaginateItems(todoItems, todoItemQuery);
            IQueryable <TodoItemInfo> todoItemInfos = ProjectItems(todoItems);
            IList <TodoItemInfo>      result        = await todoItemInfos.ToListAsync();

            logger.LogInformation("Fetched {TodoItemsCount} todo item(s) for user [{User}] using query {TodoItemQuery}",
                                  result.Count, todoItemQuery.Owner.GetName(), todoItemQuery);

            return(result);
        }
        private IQueryable <TodoItem> FilterItems(TodoItemQuery todoItemQuery)
        {
            IQueryable <TodoItem> todoItems =
                todoDbContext.TodoItems.Where(todoItem => todoItem.CreatedBy == todoItemQuery.Owner.GetName());

            if (todoItemQuery.Id.HasValue)
            {
                todoItems = todoItems.Where(todoItem => todoItem.Id == todoItemQuery.Id.Value);
            }

            if (!string.IsNullOrWhiteSpace(todoItemQuery.NamePattern))
            {
                todoItems = todoItems.Where(todoItem => EF.Functions.Like(todoItem.Name, todoItemQuery.NamePattern));
            }

            if (todoItemQuery.IsComplete.HasValue)
            {
                todoItems = todoItems.Where(todoItem => todoItem.IsComplete == todoItemQuery.IsComplete.Value);
            }

            return(todoItems);
        }
        private static IQueryable <TodoItem> PaginateItems(IQueryable <TodoItem> todoItems, TodoItemQuery todoItemQuery)
        {
            int pageIndex = todoItemQuery.PageIndex ?? TodoItemQuery.DefaultPageIndex;
            int pageSize  = todoItemQuery.PageSize ?? TodoItemQuery.DefaultPageSize;

            IQueryable <TodoItem> result = todoItems.Skip(pageIndex * pageSize).Take(pageSize);

            return(result);
        }
        private static IQueryable <TodoItem> SortItems(IQueryable <TodoItem> todoItems, TodoItemQuery todoItemQuery)
        {
            Expression <Func <TodoItem, object> > keySelector = GetKeySelectorBy(todoItemQuery.SortBy);

            if (todoItemQuery.IsSortAscending.HasValue && !todoItemQuery.IsSortAscending.Value)
            {
                todoItems = todoItems.OrderByDescending(keySelector);
            }
            else
            {
                todoItems = todoItems.OrderBy(keySelector);
            }

            return(todoItems);
        }