2

I am currently using one of the many repository patterns available online to perform CRUD operations with EF6. I am happy to use it as is but I have recently been handed a few legacy projects that have database tables with a very high number of columns. I would like a way to make my application as well as future applications smoother by devising a way to select only a subset of columns.

Current method.

public virtual TEntity Get(Expression<Func<TEntity, bool>> where, 
        params Expression<Func<TEntity, object>>[] navigationProperties)
    {
        TEntity item = null;
        IQueryable<TEntity> dbQuery = this.Context.Set<TEntity>();

        //Apply eager loading
        foreach (Expression<Func<TEntity, object>> navigationProperty in navigationProperties)
            dbQuery = dbQuery.Include<TEntity, object>(navigationProperty);

        item = dbQuery
            .AsNoTracking()         //Don't track any changes for the selected item
            .FirstOrDefault(where); //Apply where clause
        return item;
    }

I would like to enhance that method to retrieve only the columns I require but still return TEntity. I do know I have to inject a Select after the '.AsNoTracking()' but I am unsure as to how I could pass the properties in as I am only starting out with Expression Trees. In essence I would like to be able to do this.

public class Employee
{
    public int EmployeeId { get;set; }
    public string EmployeeRole { get;set; }
    public string EmployeeFirstName { get;set; }
    public string EmployeeLastName { get;set; }
    public string DOB { get;set; }
    ...
}

Employee employee = EmployeeRepository.Get(where: e => e.EmployeeRole == "Developer",
    columns: x => x.EmployeeFirstName, x => x.EmployeeLastName,
    navigationProperties: null);

Where columns is a list of expressions specifying the columns to be added to the Select clause. Any help would be appreciated. Thanks in advance...

Update.

I ended up with using a DTO to do the necessary querying and extraction as I couldn't find an elegant way to perform it generically. There was a solution developed by a colleague of mine but it made the repository far too complex and would have been hard to manage in the future. So I create a StaffBasicInformation class to hold the subset of columns I use regularly. I also created an interface for it if I needed in the future. The below code sample shows the final implementation of retrieving data for the DTO.

public virtual IStaffBasicInformation GetStaffBasicInformation<TEntity2>(Expression<Func<TEntity2, bool>> where) 
            where TEntity2 : ActiveStaffMember
        {
            TEntity2 item = null;
            StaffBasicInformation resultItem = null;
            IQueryable<TEntity2> dbQuery = this.Context.Set<TEntity2>();

            resultItem =
                dbQuery.Where(where)
                .Select(x => new StaffBasicInformation
                {
                    GivenName = x.GivenName,
                    Department = x.Department,
                    Description = x.Description,
                    DisplayName = x.DisplayName,
                    Gender = x.Gender,
                    IID = x.IID,
                    Mail = x.Mail,
                    Title = x.Title,
                    ID = x.Id
                })
                .FirstOrDefault();

            return resultItem;            
        }
Judge Bread
  • 501
  • 1
  • 4
  • 13
  • I wouldn't go this path. You'll notice soon enough that it's highly error prone to have partly filled entities for each situation. The code will get littered with null checks ("is this really the entity created for this case?"). Project to dedicated DTO's or domain objects, using AutoMapper. – Gert Arnold Jan 18 '15 at 23:23
  • Yes, this is the real case entity in question. And to remain consistent we use a generic DAL in our projects. And thus, I would like to enhance the DAL to be more efficient. The entity is huge as it doesn't seem to be normalized. It contains a ton of data including all personal info i.e. address, dob, start date, end date plus a ton of company specific info which is useless to retrieve in some projects that require only 4-6 columns. – Judge Bread Jan 19 '15 at 17:13
  • 1
    What I mean is that you'll see a lot of null checks to ensure that you've got the right partly filled entity. – If the used properties are project-specific I would create contexts per project, mapping only these properties. So much for generic code if it doesn't serve a real purpose. It may look efficient. Code reuse and all. But the real maintenance hell is to work with classes that have a swarm of properties you never use (but keep forgetting which). Not to mention the clunky repository methods you're going to need. Just an opinion. – Gert Arnold Jan 19 '15 at 19:19
  • I do see your point and I am starting to lean towards DTOs or cut down versions of the object. – Judge Bread Jan 19 '15 at 20:01

1 Answers1

2

Your return value will not be of type TEntity anymore after you have done the projection, it will be an anonymous type. You have to decide, if you want to map this anonymous type to an instance of TEntity, including mapping all navigationproperties, or return dynamic or object from your repository. Both choices are not very pleasant. Mapping would include a huge amount of reflection, which will not be very fast. By returning a dynamic type you loose all type safety. You have seen this problem allready i assume.

That said: You will need to build this expression manually. Based on this answer you can modify the

public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)

to

public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<Expression> fieldNames)

end extract the property names from the expressions. I would suggest to use an ExpressionVisitor for that, but you could also use the code from this answer

For the mapping you can compile the expressions and use the returned Func to retrieve the value from the anonymous type. After that you would need to use expression and find the hosting type for the selected property by using an ExpressionVisitor again. Then you will need to create a object of type of TEntity via Activator.CreateType(), and objects for every hosting type. Assign the value from Func(AnonymousType) to the created object of the hosting type using the property name from the expression. After that you have to determin the relationship between TEntity and the hosting type and build it up.

I will try to post some code tomorrow for this scenario, although i am quite sure there is a better and faster way.

Community
  • 1
  • 1
user3411327
  • 1,031
  • 8
  • 14
  • Wow. I didn't consider the navigation properties being an issue. I know for one of the projects the foreign keys are not important as the tables are barely normalized. Thus, the big size of the tables. So, if I had to ignore the navigation properties as such could a ConvertTo<> or a new TEntity within the Select be used? P.S - Thanks for response. – Judge Bread Jan 18 '15 at 20:16
  • A Select(p => new YourEntity() { Name = p.Name}) cannot be used, as L2E only allows projections into anonymous types or DTOs. After fiddling around with reflection for the mapping i am very inclined to drop it and tell you, like Gert Arnold, to go with DTOs and AutoMapper. I will finish it just for the sake of completeness, but it will not be a very nice solution. – user3411327 Jan 19 '15 at 20:30
  • Hi yes, a colleague of mine did manage to get it to work but in a really hacky way and we've decided that using DTOs would be the most elegant and useful way. I would highly appreciate your implementation to see how you'd achieve this. Thanks. – Judge Bread Jan 20 '15 at 19:06
  • It is not finished yet and my time is currently very sparse. I will post, when finished. – user3411327 Jan 21 '15 at 19:30