4

I am having trouble with the Select method in my repository method when I try to use certain return types.

The repository method where I am having the issue is:

public IEnumerable<T> List(Expression<Func<T, bool>> filter = null,
            string include = "", 
            int Taked = 0, Expression<Func<T, T>> selector = null)
{
    IQueryable<T> query = dbSet;
    if (filter != null)
        query = query.Where(filter);

    #region Stringleri İnclude Eder
    foreach (var includeProperty in 
         include.Split(new char[] {','}, 
                       StringSplitOptions.RemoveEmptyEntries))
    {
         query = query.Include(includeProperty);
    }
    #endregion

    if (selector != null)
        query = query.Select(selector);
    if (Taked != 0)
        return query.Take(Taked).ToList();
    return query.ToList();
}

internal DbContext context;
internal DbSet<T> dbSet;

I want to return my entity class using the method above, but I only want certain properties to be populated. I have tried the following approach:

AdminWork workunit = new AdminWork();
IEnumerable<AdminMenu> adminMenus = workunit.Menu.List(x => x.Online == true,
    selector: z => new AdminMenu 
    {
        MenuID = z.MenuID, 
        Name = z.Name, 
        Path = z.Path
    });

Which throws the exception:

AdminMenu cannot be constructed in a LINQ to Entities query

I have also tried the following approach but it requires to return an IEnumerable<int>:

IEnumerable<AdminMenu> menus = workunit.Menu.List(x => x.Online == true, 
     selector: z => z.MenuID);

My question is how can I create new instances of my entity class in linq to entities, so not every property is populated.

Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
ierhalim
  • 298
  • 3
  • 16
  • 1
    Can you show the definition of the `dbSet` variable? And can you expand on "having trouble in return type"? – Colin Jan 20 '14 at 10:09

1 Answers1

4

You cannot pass a selector that creates instances of AdminMenu because that is a type mapped in your Entity Framework context. So Entity Framework wants to be the only one creating instances of that type so it can keep track of changes, return the same instance if already loaded etc... See this question for more info.

So, if you want to return a list of AdminMenu, you won't need to pass a selector (as the dbSet you start querying is already of that type). You would only need to pass a selector when the return type is a different one than the type in the dbSet.

You could have this method: (I have made selector a non-optional parameter as it will be used to define the TResult type. I am also assuming T is a generic parameter defined in the class like public class Repository<T>, so the only generic parameter to add in the method is TResult)

public IEnumerable<TResult> ListProjected<TResult>(Expression<Func<T, TResult>> selector,
                                        Expression<Func<T, bool>> filter = null,
                                        string include = "",
                                        int Taked = 0)
{
    IQueryable<T> query = dbSet;
    if (filter != null)
        query = query.Where(filter);
    #region Stringleri İnclude Eder

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

    #endregion

    if (Taked != 0)
        query = query.Take(Taked);

    return query.Select(selector).ToList();
}

And a different one when you don't want to project into a different type:

public IEnumerable<T> List(Expression<Func<T, bool>> filter = null,
                        string include = "",
                        int Taked = 0)
{
    return ListProjected(x => x, filter, include, Taked);
}

You can then use those methods as in:

IEnumerable<AdminMenu> menus = workunit.Menu.List(x => x.Online == true);

IEnumerable<int> menuIds = workunit.Menu.ListProjected(x => x.MenuID, 
                                                       x => x.Online == true);

Finally, if you only want some columns, you will need to either use an anonymous object or create another class (like a DTO) that only contains the columns you are interested in:

var menuSomeColumnsWithAnonymousObject = workunit.Menu.ListProjected(x => new
{
    MenuID = x.MenuID,
    Path = x.Path,
    Name = x.Name
}, x => x.Id == 1);

var menuSomeColumnsWithDTO = workunit.Menu.ListProjected(x => new AdminMenuDTO
{
    MenuID = x.MenuID,
    Path = x.Path,
    Name = x.Name
}, x => x.Id == 1);

A longer answer than I thought, but I hope it helps!

Community
  • 1
  • 1
Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112