This project has moved and is read-only. For the latest updates, please go here.
1
Vote

EF6 Projection Query / Context Persistence Issue

description

We've written a generic repository pattern for our project and within our Get method, we are using a projection query to shape the query that ultimately gets run on our data store so we can avoid SELECT * FROM [Table]. However, when we're unit testing our repository, we're running into an issue where when the query is executed with the projection, the entity object(s) that get returned aren't being persisted to our underlying context. We are getting the data we need out of the Effort data store, but it not persisting to the context caused a false positive passing test as a live dbConnection does persist it.

Here is our get method:
public IEnumerable<OEntity> Get<OEntity>(
            Expression<Func<TEntity, OEntity>> columns,
            out int totalRecords,
            out int actualPage,
            Expression<Func<TEntity, bool>> filter = null,
            List<SearchSortParameters> sortParams = null,
            List<Expression<Func<TEntity, object>>>
                includeProperties = null,
            int? page = 1,
            int? pageSize = 50)
        {
            if (page.HasValue)
            {
                if (page < 1)
                    page = 1;
            }

            if (pageSize.HasValue)
            {
                if (pageSize < 1)
                    pageSize = 1;

                if (pageSize > 50)
                    pageSize = 50;
            }

            IQueryable<TEntity> query = _dbSet;

            if (includeProperties != null)
                includeProperties.ForEach(i => query.Include(i));

            if (filter != null)
                query = query.Where(filter);

            totalRecords = query.Count();

            var skip = 0;

            if (page.HasValue && pageSize.HasValue)
            {
                skip = (page.Value - 1) * pageSize.Value;

                if (skip < totalRecords)
                {
                    actualPage = page.Value;
                }
                else
                {
                    var remainder = (totalRecords % pageSize.Value);
                    skip = totalRecords - remainder;

                    if (remainder == 0)
                        actualPage = totalRecords / pageSize.Value;
                    else
                        actualPage = (totalRecords / pageSize.Value) + 1;
                }
            }
            else
                actualPage = 1;

            if (sortParams != null && sortParams.Count > 0)
            {
                for (int i = 0; i < sortParams.Count; i++)
                {
                    var sortParam = sortParams[i];

                    if (i == 0)
                    {
                        switch (sortParam.direction.ToUpper())
                        {
                            case "ASC":
                                query = query.OrderBy(sortParam.column, "OrderBy");
                                break;
                            case "DESC":
                                query = query.OrderByDescending(sortParam.column, "OrderByDescending");
                                break;
                        }
                    }
                    else
                    {
                        switch (sortParam.direction.ToUpper())
                        {
                            case "ASC":
                                query = query.OrderBy(sortParam.column, "ThenBy");
                                break;
                            case "DESC":
                                query = query.OrderByDescending(sortParam.column, "ThenByDescending");
                                break;
                        }
                    }
                }
            }

            if (page != null && pageSize != null)
                query = query
                    .Skip(skip)
                    .Take(pageSize.Value);
            
            var securityHelper = new SecurityHelper();
            if (typeof(IReadAuditable).IsAssignableFrom(typeof(TEntity)))
            {
                if (securityHelper.IsAuthenticated() && securityHelper.HasClaim(Constants.CustomClaimTypes.UserId.GetStringValue()))
                {
                    var userId =
                        int.Parse(
                            securityHelper.GetClaimValue(Constants.CustomClaimTypes.UserId.GetStringValue()).First());
                    
                    foreach (TEntity item in query)
                    {
                        _auditLog.Insert(null, new AuditLog
                        {
                            Action = "Read",
                            UserId = userId,
                            AuditDate = DateTime.Now,
                            RecordId = string.Join(",", ((System.Data.Entity.Infrastructure.IObjectContextAdapter)_context).ObjectContext.ObjectStateManager.GetObjectStateEntry(item).EntityKey.EntityKeyValues.Select(x =>
                                $"{x.Key}:{x.Value}")),
                            TableName = ObjectContext.GetObjectType(item.GetType()).Name
                        });
                    }
                }
            }

            //var entities = query.ToList();
            return query.Select(columns).ToList(); //This line being executed with Effort isn't persisting the returned entities to the underlying dbset and context, but it is returning the data.
        }
If I were to uncomment this line "var entities = query.ToList();" to execute a non-projecting query against the db store, the data is persisted to the dbset and context as expected. This line isn't necessary when we run the same code above against non-Effort dbConnection.

Any help would be greatly appreciated because we really don't want to have to remove projections or have to put in hard-coded handling for our tests.

comments