Search code examples
c#entity-frameworkentity-framework-6

EntityFramework 6.4.4 - How to project collection to a custom object implementing IEnumerable<T>?


Cannot map to a custom object that implements IEnumerable ?

public class BranchCollection : IEnumerable<int>
{
      public IEnumerable<int> BranchIds { get; set; } = new List<int>();
      public BranchPermission() { }
      
      //Custom Code here
}
public class UserDto {
      public BranchCollection Collection { get; set; }
}

Trying to project to UserDto with code below

dbContext.Users.Select(
  x => new UserDto {
    Collection = new BranchCollection { BranchIds = x.Branches.Select(y => y.BranchId) }
  }
  .ToList();

Exception

A type that implements IEnumerable 'BranchCollection' cannot be initialized in a LINQ to Entities query.

Stack Trace

at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.CheckInitializerType(Type type) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MemberInitTranslator.TypedTranslate(ExpressionConverter parent, MemberInitExpression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MemberInitTranslator.TypedTranslate(ExpressionConverter parent, MemberInitExpression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SelectTranslator.Translate(ExpressionConverter parent, MethodCallExpression call) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Convert() at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable1 forMergeOption) at System.Data.Entity.Core.Objects.ObjectQuery1.<>c__DisplayClass41_0.b__1() at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess) at System.Data.Entity.Core.Objects.ObjectQuery1.<>c__DisplayClass41_0.b__0() at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func1 operation) at System.Data.Entity.Core.Objects.ObjectQuery1.GetResults(Nullable1 forMergeOption) at System.Data.Entity.Core.Objects.ObjectQuery1.<System.Collections.Generic.IEnumerable.GetEnumerator>b__31_0() at System.Data.Entity.Internal.LazyEnumerator1.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)


Solution

  • EF cannot understand/initiate that class. It's designed to accommodate projection to simple classes and collections. If you feel you must do something like that then double-project:

    var userDetails dbContext.Users.Select(x => new 
    {
        BranchIds = x.Branches.Select(b => b.BranchId).ToList()
    }).ToList()  // This executes the query getting our branch IDs (and include anything from the User we need as well.
    .Select(x => new UserDto
    {
        Collection = new BranchCollection { BranchIds = x.BranchIds }
    }.ToList(); // Project to your custom type from memory.
    

    For a class like that where you want to wrap IEnumerable I would recommend passing the source collection into a constructor to initialize a private member and make it immutable, exposing your enumerator etc. If items can be added/removed then accommodate that with methods in your class rather than exposing a contained collection where anyone later on could write

    userDto.Collection = new BranchCollection();

    ... and probably gunk things up.