The exception message is clear, you have two select new NCCN.Model.Product statements in one LINQ statement that don't set the same properties in the same order. It's an EF limitation we have to deal with. LINQ-to-objects wouldn't throw this exception.
Although it's not quite clear from your question, I think I understand what you're actually asking. Even when understanding the exception message, it's not obvious how to deal with it. Before pointing out the problem, let me first introduce a simplified model.
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public ICollection<ProductComponent> Components { get; set; }
}
public class ProductComponent
{
public int ProductId { get; set; }
public int ComponentProductId { get; set; }
public Product Product { get; set; }
public Product ComponentProduct { get; set; }
}
With mapping code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductComponent>().HasKey(e => new { e.ProductId, e.ComponentProductId });
modelBuilder.Entity<ProductComponent>().HasRequired(e => e.Product)
.WithMany(p => p.Components).HasForeignKey(e => e.ProductId);
modelBuilder.Entity<ProductComponent>().HasRequired(e => e.ComponentProduct)
.WithMany().HasForeignKey(e => e.ComponentProductId)
.WillCascadeOnDelete(false); // Prevent circular cascade
}
Now we can use a navigation property instead of joining all the time.
The task is to map this model to a DTO class:
public class ProductDto
{
public int ProductId { get; set; }
public string Name { get; set; }
public List<ProductDto> ComboProducts { get; set; }
}
It should be as simple as
var products = db.Products.Select(p => new ProductDto
{
ProductId = p.ProductId,
Name = p.Name,
ComboProducts =
p.Components.Select(pc => pc.ComponentProduct)
.Select(c => new ProductDto
{
ProductId = c.ProductId,
Name = c.Name,
}).ToList()
});
But now EF throws the exception you reported. In your case you even skipped a whole range of properties, here it's only ComboProducts in the nested ProductDto, but that's enough. The inner ProductDto should have a ComboProducts collection too.
Unfortunately, that's not a simple as one would expect. For example, with this inner select...
.Select(c => new ProductDto
{
ProductId = c.ProductId,
Name = c.Name,
ComboProducts = null
}).ToList()
...EF throws
NotSupportedException: Unable to create a null constant value of type 'System.Collections.Generic.List`1[[ProductDto]]'. Only entity types, enumeration types or primitive types are supported in this context.
And...
.Select(c => new ProductDto
{
ProductId = c.ProductId,
Name = c.Name,
ComboProducts = new List<ProductDto>()
}).ToList()
...throws
NotSupportedException: A type that implements IEnumerable 'System.Collections.Generic.List`1[[ProductDto]]' cannot be initialized in a LINQ to Entities query.
What it boils down to is: with such nested projections you have to use two different types for the main and nested types. But anonymous types also fit the bill, so I think the easiest way to work around this annoying EF limitation is to project to anonymous types and then to ProductDto:
var products = db.Products.Select(p => new
{
ProductId = p.ProductId,
Name = p.Name,
ComboProducts =
p.Components.Select(pc => pc.ComponentProduct)
.Select(c => new
{
ProductId = c.ProductId,
Name = c.Name
}).ToList()
}).AsEnumerable() // Continue in memory so EF won't translate the following part
.Select(x => new ProductDto
{
ProductId = x.ProductId,
Name = x.Name,
ComboProducts = x.ComboProducts.Select(cp => new ProductDto
{
Name = cp.Name,
ProductId = cp.ProductId,
}).ToList()
}
).ToList();
NameandProductIdin the innerNCCN.Model.Productobject?