3

I have a question in regards with the below,

  1. Left outer join of two tables who are not connected through Foreign Key.
  2. Order by the results matched in second table.
  3. I would like this to be done in LINQ Query method syntax as I am adding lots of conditions depending on the input provided along with skip and limit.

If we have below Product and Favorite tables

enter image description here

enter image description here

So the output that I would like to have is:

enter image description here

meaning with the favorites as part of first set and which are not favorites should be behind them. Below are the tries that I did. I am able to join the tables get the output but not sure how I can make sure that in the first page I get all the favs.

This answer was very near to what I thought but it gets the result and then does the ordering which will not be possible in my case as I am doing pagination and using IQueryable to get less data.

Group Join and Orderby while maintaining previous query

Open to any solutions to achieve the same.

[Table("Product")]
public class ProductModel
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid ProductId { get; set; }
    public string ProductName {get; set;}
    public bool IsFavorite { get; set; }
}

[Table("UserFavorite")]
public class UserFavoriteModel
{
    [Required]
    public Guid UserId { get; set; }
    [Required]
    public Guid Identifier { get; set; }
    [Required]
    public FavoriteType Type { get; set; }
}

// Gets products
private async Task<List<ProductModel>> GetProductsAsync(
    Guid categoryId, 
    Guid subCategoryId, 
    int from,
    int limit)
{
    var query = _context.Products.AsQueryable();

    if (!string.IsNullOrEmpty(categoryId))
        query = query.Where(product => product.CategoryId == categoryId);
    if (!string.IsNullOrEmpty(subCategoryId))
        query = query.Where(product => product.SubCategoryId == subCategoryId);

    query = query.Skip(from).Take(limit);

    var products = await query.ToListAsync();

    query = query.GroupJoin(
    _context.Favorites.AsNoTracking()
    .Where(favorite => favorite.Type == FavoriteType.FASHION)
    // This user Id will come from context just adding for overall picture.
    .Where(favorite => favorite.UserId == userId),
    //This orderby if I add will not make any difference.
    //.OrderByDescending(favorite => favorite.Identifier),
    v => v.ProductId,
    f => f.Identifier,
    (product, fav) => new { product, fav }).
    SelectMany(x => x.Fav.DefaultIfEmpty(),
                    (x, y) => SetFavorite(x.Project, y));

}

private static ProductModel SetFavorite(ProductModel v, UserFavoriteModel si)
{
    v.IsFavorite = (si != null);
    return v;
}
Genusatplay
  • 761
  • 1
  • 4
  • 15
POCCoder
  • 33
  • 4

1 Answers1

2

I would do something like this:

var query =
   _context.Products.AsQueryable().Select(p => new ProductModel {
      ProductId = p.ProductId,
      ProductName = p.ProductName,
      IsFavorite =
         _context.Favorites.Any(f =>
            f.Identifier = p.ProductId &&
            f.Type == FavoriteType.FASHION &&
            f.UserId == userId
         )
   }).OrderByDescending(favorite => favorite.Identifier);
Aducci
  • 26,101
  • 8
  • 63
  • 67
  • 1
    AsNoTracking inside projection and without returning records - something new for me ;) – Svyatoslav Danyliv Jun 08 '21 at 22:19
  • @Aducci Thanks alot. This looks simpler than I thought, lot to learn :). there are some typos that might be useful for others f.Identifier = p.ProductId (this should have ==) and order should be OrderByDescending(favorite => favorite.IsFavorite). I had one small clarification if I have large number of fields in ProductModel is there a way to simplify that rather than declaring all in select. I tried Select(p => { p.IsFavorite =favs condition here ; return p;}) but I suppose this is not supported in Expressions. – POCCoder Jun 09 '21 at 21:09
  • @SvyatoslavDanyliv I just made this method to post in SO and forgot to add the return part :). The AsNoTracking as I understand was to use for only reads and I am using that even for _context.Products.AsNoTracking, Is there a reason I should not be using. It is because of IQueryable? – POCCoder Jun 09 '21 at 21:22
  • 1
    `AsNoTracking` is for "notifying" ChangeTracker to do not register loaded entities. If you did not get records, just `Count` or `Any`, etc. - so nothing for change tracking. – Svyatoslav Danyliv Jun 09 '21 at 21:45