I have a model which has is below and when I add ComboProducts object with the linq below I am getting the error. Is there something I am missing in this code?
Error
appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
Model
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public List<Product> ComboProducts { get; set; }
}
Code
ListOfProducts = (
from p in db.Products
join ptp in db.ProductToProducts on p.ProductId equals ptp.ParentProductId
join pr in db.Pricings on ptp.ProductToProductId equals pr.ProductToProductId
where p.SubProductTypeId == (int)SubProductTypeId
select new Model.Product
{
ProductId = p.ProductId,
Name = p.Name,
PreviewUrl = p.PreviewURL,
ShortName = p.ShortName,
Description = p.Description,
PublicProduct = p.PublicProduct,
Price = pr.Price,
ComboProducts = (
from ptpx in db.ProductToProducts
join ap in db.Products on ptpx.ChildProductId equals ap.ProductId
where ptpx.ParentProductId == p.ProductId
select new Model.Product
{
Name = ap.Name,
ProductId = ap.ProductId
}
).OrderBy(p2 => p2.Name).ToList(),
DisplayOrder = p.DisplayOrder != null ? (int)p.DisplayOrder : 0
})
.GroupBy(grp => new
{
grp.ProductId
}
)
.Select(x => x.FirstOrDefault())
.ToList();
return ListOfProducts;
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();
Split the complex linq into small unit(it will be useful for maintainablity, readablity, and reduce complexity)
Try my suggestion:
in above example you have added nested linq. So move ComboProducts linq out of this linq. First calculate the Comboproducts and assign that value into var. then next build parent query on top of it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With