0
votes

I have a problem on my code. I created the query below:

var query = from s in _context.Stores
            join co in _context.Countries on s.CountryId equals co.CountryId
            join ci in _context.Cities on s.CityId equals ci.CityId
            let categories = (from category in _context.Categories
                              join sc in _context.StoresCategories on s.StoreId equals sc.StoreId
                              where category.CategoryId == sc.CategoryId
                              select category.Description)
            let categoriesIds = (from cat in _context.Categories
                                 join sc in _context.StoresCategories on s.StoreId equals sc.StoreId
                                 where cat.CategoryId == sc.CategoryId
                                 select cat.CategoryId)
            select new Response.StoreResponse
                   {
                        StoreId = s.StoreId,
                        Name = s.Name,
                        Email = s.Email,
                        Phone = s.Phone,
                        CountryId = co.CountryId,
                        Country = co.Name,
                        CityId = ci.CityId,
                        City = ci.Name,
                        Categories = string.Join(",", categories),
                        CategoriesIds = string.Join(",", categoriesIds),
                        //Categories = categories.ToList(),
                        //CategoriesIds = categoriesIds.ToList(),
                        Logo = s.profile_url
                    };
List<Response.StoreResponse> resp = query.ToList<Response.StoreResponse>();

But I get this error:

System.InvalidOperationException: The query contains a projection '<>h__TransparentIdentifier2 => DbSet() .Join( inner: DbSet(), outerKeySelector: cat => <>h__TransparentIdentifier2.<>h__TransparentIdentifier1.<>h__TransparentIdentifier0.s.StoreId, innerKeySelector: sc => sc.StoreId, resultSelector: (cat, sc) => new { cat = cat, sc = sc }) .Where(<>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.cat.CategoryId == <>h__TransparentIdentifier0.sc.CategoryId) .Select(<>h__TransparentIdentifier0 => <>h__TransparentIdentifier0.cat.CategoryId)' of type 'IQueryable'. Collections in the final projection must be an 'IEnumerable' type such as 'List'. Consider using 'ToList' or some other mechanism to convert the 'IQueryable' or 'IOrderedEnumerable' into an 'IEnumerable'. at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VerifyReturnType(Expression expression, ParameterExpression lambdaParameter) at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VerifyReturnType(Expression expression, ParameterExpression lambdaParameter) at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes) at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node) at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression) at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression) at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query) at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.Process(Expression query) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_01.<Execute>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.GetEnumerator() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) at Store.API.Controllers.StoreController.SearchStores(StoreListRequest request) in D:\Projects\ViewInStore\src\Store.API\Controllers\StoreController.cs:line 230 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Thanks Alessandro

1
You're trying to cast an anonymous type to Response.StoreResponse. That won't work.user47589
Sorry. missing to copy the type.. I updated the post.Alexgr79
Add parenthesis.and ToList(). Make query (from .......select new { .....}).ToList();jdweng
Hi @jdweng thanks for your response. I don't understand.Alexgr79
The results wants a LIST. You need to convert the query results to a LIST. So simply adding a parenthesis around the entire query and then add to the end .ToList();jdweng

1 Answers

0
votes

One potential issue I can see will be attempting to do the string.Join's within the Linq expression.

Firstly, I would recommend reading up on navigation properties with EF. It is very common for developers accustomed to writing SQL / Stored Procedures / Views looking at EF and Linq to start essentially reproducing their SQL in Linq QL. You have a Store entity, and StoreCategory, and Category which is enough to establish the many-to-many relationship between these entities.

Assuming EF Core 5:

public class Store
{
    // ... Store fields.
    public virtual City City { get; set; }
    public virtual Country Country { get; set; }
    public virtual ICollection<Category> Categories { get; set; } = new List<Category>();
}

Then in your configuration, if you don't have these wired up already: (I.e. OnModelCreating or EntityTypeConfiguration)

modelBuiler.Entity<Store>()
    .HasRequired(x => x.City)
    .WithMany()
    .HasForeignKey("CityId");
// Do the same as above for Country...

modelBuiler.Entity<Store>()
    .HasMany(x => x.Categories)
    .WithMany()
    .UsingEntity<StoreCategory>(
        x => x.HasOne(sc => sc.Store).WithMany().HasForeignKey(sc => sc.StoreId),
        x => x.HasOne(sc => sc.Category).WithMany().HasForeignKey(sc => sc.CategoryId));             

If you're using an earlier version of EF Core then the relationship has to be done through Store.StoreCategories with a many-to-one-to-many relationship.

From here instead of that Linq expression you can simplify it to use the navigation properties. However, we should avoid doing the string.Join in something that will need to be translated into SQL. For the Categories we project the details we need into a child response object:

var query = _context.Stores
    .Select(s => new Response.StoreResponse
    {
        StoreId = s.StoreId,
        Name = s.Name,
        Email = s.Email,
        Phone = s.Phone,
        CountryId = s.Country.CountryId,
        Country = s.Country.Name,
        CityId = s.City.CityId,
        City = s.City.Name,
        Categories = s.Categories
            .Select(c => new Response.CategoryResponse {c.CategoryId, c.Description})
            .ToList(),
        Logo = s.profile_url
    });
var resp = query.ToList();

This means declaring a new simple POCO for Categories in your Response namespace:

[Serializable] 
public class CategoryResponse
{
    public int CategoryId { get; set; }
    public int Description { get; set; }
}

Then in your Company Response class you can handle provisioning the Categories in a comma-delimited list for your consumer (view etc.), or just let your consumer format the data:

public class CompanyResponse
{
    public ICollection<CategoryResponse> Categories { get; set; } = new List<CategoryResponse>();
    public string CategoryIds 
    {
        if (Categories == null) return null;
        return string.Join(",", categories.Select(x => x.CategoryId));
    }
    public string CategoryDescriptions 
    {
        if (Categories == null) return null;
        return string.Join(",", categories.Select(x => x.Description));
    }
}

The caveat of formatting in the view model / DTO (Response) is that it doubles up the amount of data being serialized.