4

I am using the example here to create a repository pattern with Unit of Work.

Within the code, there is a generic Get method:

public class GenericRepository<TEntity> where TEntity : class
{
    internal AdminDbContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(AdminDbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<TEntity, TEntity> selector = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = dbSet;

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


        foreach (var includeProperty in includeProperties.Split
            (new char[] { ','}, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

At this moment when this method is called, all the records from the database are fetched and column selection is done in memory.

My question is how can I extend the Get method here that would allow me to dynamically pass column names to this method so that only the desired columns are selected at database level?

I have tried:

  • Accepting a string parameter with the comma seperated values of the desired columns but that I could not map them to the entity.
  • Creating a parameter with the same type of filter which gave an error.
Subliminal Hash
  • 13,614
  • 20
  • 73
  • 104

1 Answers1

1

This is kind of a long answer but here is an extension method I created for doing this. I am returning an object in this case because this is used in webAPI to just return json and I do not need a particular type but this can easily be adapted to return a generic entity type.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;

namespace Your.Extensions
{
    public enum PropertyFormat
    {
        AsIs,
        PascalCase,
        CamelCase
    }

    public static class DataShapingExtensions
    {
        public static object ToDataShape<ObjectIn>(this ObjectIn objectToShape, string fields, PropertyFormat propertyFormat = PropertyFormat.AsIs) where ObjectIn : class
        {
            var listOfFields = new List<string>();

            if (!string.IsNullOrWhiteSpace(fields))
            {
                listOfFields = fields.ToLower().Split(',').ToList();
            }

            if (listOfFields.Any())
            {
                var objectToReturn = new JObject();

                //====
                var enumerable = objectToShape as IEnumerable;

                if (enumerable != null)
                {
                    var listOfObjects = new List<JObject>();

                    foreach (var item in enumerable)
                    {
                        var objectToReturn2 = new JObject();

                        listOfFields.ForEach(field =>
                        {
                            try
                            {
                                var prop = item.GetType()
                                    .GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

                                var fieldName = prop.Name;
                                var fieldValue = prop.GetValue(item, null);

                                fieldName = GetName(fieldName, propertyFormat);
                                objectToReturn2.Add(new JProperty(fieldName, fieldValue));
                            }
                            catch (Exception ex) { }
                        });

                        listOfObjects.Add(objectToReturn2);
                    }

                    return listOfObjects.ConvertAll(o => o);
                }
                //====

                listOfFields.ForEach(field =>
                {
                    try
                    {
                        var prop = objectToShape.GetType()
                            .GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

                        var fieldName = prop.Name;
                        var fieldValue = prop.GetValue(objectToShape, null);

                        fieldName = GetName(fieldName, propertyFormat);
                        objectToReturn.Add(new JProperty(fieldName, fieldValue));
                    }
                    catch (Exception ex) { }
                });

                return objectToReturn;
            }

            return objectToShape;
        }

        private static string GetName(string field, PropertyFormat propertyFormat)
        {
            switch (propertyFormat)
            {
                case PropertyFormat.AsIs: return field;
                case PropertyFormat.PascalCase: return field.ToPascalCase();
                case PropertyFormat.CamelCase: return field.ToCamelCase();
                default: return field;
            }
        }
    }
}

//Usage

[HttpGet, Route("api/someroute")]
public async Task<IHttpActionResult> YourMethod(string fields = null)
{
    try
    {
         var products = await yourRepo.GetProductsList();

         if (fields.HasValue())
         {
              return Ok(products.ToDataShape(fields, PropertyFormat.CamelCase));
         }

         return Ok(products);
     }
     catch (Exception)
     {
         return InternalServerError();
     }
}

//Call route

/api/someroute?fields=productID,productName

//output (json)

{
    productID: 1,
    productName: "Some Name"
}
Stephen Brickner
  • 2,584
  • 1
  • 11
  • 19
  • Thanks for taking time however, with this method, the data is "shaped" in memory after the records have been fetched from the database right? I am hoping to find a solution that sends the correct sql command to the database. – Subliminal Hash Apr 11 '16 at 11:59
  • @KemalEmin you could try Extend your repository to return `IQuerable GetAsQuerable()` and `Select(your column names)` – Eldho Apr 11 '16 at 12:07
  • @KemalEmin I guess I misunderstood what you are trying to do. As code caster pointed out in the comments you can just use .Select (as a queriable) but then you would not be returning it as the desired entity type (unless you wanted all other fields to be null). – Stephen Brickner Apr 11 '16 at 12:08
  • I apologise for not being very clear earlier Stephen. – Subliminal Hash Apr 11 '16 at 12:39