Converting SQL with LEFT JOIN to Linq (Method Syntax) - c#

It's my first post here, so if I get anything wrong let me know and I'll fix it.
I'm struggling to convert a simple SQL statement with a left join, to a LINQ statement (Method syntax). I cannot use Linquer since this is a .Net Core 5.0 MVC project.
Consider that I have two tables:
dbo.OrganisationChannel (Id, OrganisationId, ChannelId)
dbo.Channel (Id, ChannelName, ChannelUrl)
I want to show all channels that an organisation DOESN'T currently have.
Here is the correct SQL query
SELECT c.Id, c.ChannelName, c.ChannelUrl
FROM dbo.Channel c
LEFT JOIN dbo.OrganisationChannel oc ON c.Id = oc.ChannelId
WHERE oc.ChannelId IS NULL OR oc.OrganisationId <> 1
However, the corresponding .GroupJoin and .SelectMany is perplexing me.. I can't find the right place to add the WHERE clauses:
var groupItems = db.Channel
.GroupJoin(
db.OrganisationChannel,
c => c.Id,
oc => oc.ChannelId,
(c, oc) => new { c, oc })
.SelectMany(
x => x.oc.DefaultIfEmpty(),
(chan, orgChan) => new
{
Id = chan.c.Id,
ChannelName = chan.c.ChannelName,
ChannelUrl = chan.c.ChannelUrl,
IsActive = chan.c.IsActive,
}
);
I'd be grateful for any help here, thanks!
Si

Method syntax with LEFT JOIN is a nightmare. If you really want method syntax install Reshaper and click "convert to method chain". But I do not recommend to do that - query become unmaintainable.
Your query is simple with query syntax
var query =
from c in db.Channel
join oc in db.OrganisationChannel on c.Id equals oc.ChannelId into gj
from oc in gj.DefaultIfempty()
where (int?)oc.ChannelId == null || oc.OrganisationId != 1
select new
{
c.Id,
c.ChannelName,
c.ChannelUrl
};

You can use the LeftJoin extension method :
public static IQueryable<TResult> LeftJoin<TResult, Ta, Tb, TKey>(this IQueryable<Ta> TableA, IEnumerable<Tb> TableB, Expression<Func<Ta, TKey>> outerKeySelector, Expression<Func<Tb, TKey>> innerKeySelector, Expression<Func<JoinIntermediate<Ta, Tb>, Tb, TResult>> resultSelector)
{
return TableA.GroupJoin(TableB, outerKeySelector, innerKeySelector, (a, b) => new JoinIntermediate<Ta, Tb> { Value = a, ManyB = b }).SelectMany(intermediate => intermediate.ManyB.DefaultIfEmpty(), resultSelector);
}
public class JoinIntermediate<Ta, Tb>
{
public Ta Value { get; set; }
internal IEnumerable<Tb> ManyB { get; set; }
}
It's usage is similar to the Join extension method but will perform a left join instead of a regular join. Then you can add your call to the Where method right after the call to LeftJoin.

Use the following query instead of lambda expressions
from left in lefts
join right in rights on left equals right.Left into leftRights
from leftRight in leftRights.DefaultIfEmpty()
select new { }

check this url https://dotnettutorials.net/lesson/left-outer-join-in-linq/
also my working code example:
UserApiKeys
.Where(w => w.AppID == AppID && w.IsActive)
.Join(
UserApiApplications,
keys => keys.AppID,
apps => apps.AppID,
(keys, apps) => new { UserApiKeys = keys, UserApiApplications = apps}
)
.OrderByDescending(d => (d.UserApiKeys.ExpirationDate ?? DateTime.MaxValue))
.Select(s => new {
ApiKey = s.UserApiKeys.ApiKey,
IsActive = s.UserApiKeys.IsActive,
SystemName = s.UserApiKeys.SystemName,
ExpirationDate = (s.UserApiKeys.ExpirationDate == null)
? "Newer Expires"
: s.UserApiKeys.ExpirationDate.ToString(),
s.UserApiApplications
})
.ToList()
in addition, to refer #nalka post about extension method usage:
NotificationEvents
.Where(w => w.ID == 123)
.LeftJoin(
Events,
events => events.EventID, ev => ev.EventID,
(events, ev) => new { NotificationEvents = events, Events = ev }
);

Related

Equivalent linq query syntax

What is the equivalent query syntax for the following overload of GroupBy:
public static IEnumerable<IGrouping<TKey, TElement>>
GroupBy<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector
)
This question cropped up when trying to write the equivalent query syntax of the following:
var groups =
products.SelectMany(p => p.Categories,
(p, c) => new { Product = p, Category = c })
.GroupBy(p => p.Category, p => p.Product);
The "normal" query syntax would give back redundant information: Category already given by the Key property:
var groups = from p in products
from c in p.Categories
let pc = new { Product = p, Category = c }
group pc by pc.Category into g
select g;
Which is equivalent to:
var groups =
products.SelectMany(p => p.Categories,
(p, c) => new { Product = p, Category = c })
.GroupBy(p => p.Category);
This issue cropped up while answering this question.
The expression between group and by is the element selector. You went out of your way to add the information you specifically don't want into that element. You just need to...not do that.
var groups = from product in products
from category in product.Categories
group product by category into g
select g;

Cast after Join

I need to add some information to an entity before returning it via GET.
I created a class, kind of like a ViewModel, with the original entity plus one int field, to return the complete data to a client, but was enable to make it work. I also don't want to return the Depth field, but need it in the Where.
How do I go from this Join to correctly returning the data to a client?
Is iterating and copying item by item the only way?
public class FluxoHierarchyOutput
{
public FluxoHierarchy FluxoHierarchy { get; set; }
public int ParentId { get; set; }
}
public IEnumerable<FluxoHierarchyOutput> GetFluxoHierarchyByFluxo([FromRoute] int idFluxo)
{
return _context.FluxoHierarchy
.Join(
_context.FluxoClosure,
h => h.NodeId,
c => c.ChildId,
(h, c) => new { FluxoHierarchy = h, c.ParentId, c.Depth }
)
.Where(x => x.FluxoHierarchy.FluxoId == idFluxo && x.Depth == 1)
.ToList(); // Cannot implicitly convert type...
}
Try projecting to your concrete type, rather than an anonymous type.
(h, c) => new FluxoHierarchyOutput { FluxoHierarchy = h, c.ParentId, c.Depth }
You're going to need a Select in there somewhere, probably just before the ToList() call. Otherwise, your result will be the entire join.
The fluent API is not the prettiest with joins. Here is the same thing using query syntax. You completely avoid having to specify an intermediate result prior to the select.
return (
from h in _context.FluxoHierarchy
join c in _context.FluxoClosure on h.NodeId equals c.ChildId
where h.FluxoId == idFluxo && c.Depth == 1
select new FluxoHierarchyOutput {
FluxoHierarchy = h,
ParentId = c.ParentId
}).ToList();
Without a Select, your query returns an anonymous object, which then will have to be converted to your actual return type. This conversion cannot be implicit as the error message is telling you. Add a Select to the end of your query and it will work.
return _context.FluxoHierarchy
.Join(
_context.FluxoClosure,
h => h.NodeId,
c => c.ChildId,
(h, c) => new { FluxoHierarchy = h, c.ParentId, c.Depth }
)
.Where(x => x.FluxoHierarchy.FluxoId == idFluxo && x.Depth == 1)
.Select(s => new FluxoHierarchyOutput {
FluxoHierarchy = s.FluxoHierarchy,
ParentId = s.ParentId,
//Add all the fields you want.
})
.ToList();
You have the same issue in the Join as Robert Harvey explained.

Can make left join in Entity Framework without dataset

I have this query
return _ctx.TestPackages.Where(s => s.Id == TestPackageId).
Join(_ctx.TestPackageReportDetails, s => s.Id, d => d.TestPackageId, (s, d) => new { reportDetail = d, testpack = s }).
Join(_ctx.TestPackageReports, p => p.reportDetail.TestPackageReportId, o => o.Id, (p, o) => new { combined = p, report = o })
.ToList()
As you can see my query makes join between 3 tables TestPackages TestPackageReportDetails and TestPackageReports. When I have more than one record in TestPackageReportDetails with same testpackageid, the result is repeated 3 times in the output. How can I avoid the repetition?
Should I make a left join between TestPackageReportDetails and TestPackages? If yes how can I do that?
If your intention is to Eager load the report details then you should be using .Include :
_ctx.TestPackages.Include(t=>t.TestPackageReportDetails.TestPackageReports).Where(s => s.Id == TestPackageId);
Since you're only selecting three fields (As shown before you edit your answer again and remove the select) then you can do this:
(from s in _ctx.TestPackages
join d in _ctx.TestPackageReportDetails,
on s.Id equals d.TestPackageId
join r in _ctx.TestPackageReports
on s.Id equals r.reportDetail.TestPackageReportId
where s.Id == TestPackageId
select new
{
s.Id,
s.packageNumber,
s.Size,
s.TestPackageOrder
}).Distinct().ToList().Select(m=> new ..) // continue your normal selection

Orderby lambda expression on anonymous type

I refactored a linq to entities query to speed it up and broke my orderby lambda feature.
Is there any way to get this to working again since the query is now a join and creating an anonymous type?
Refactored code that is broken because of the orderBy:
public List<UserProductRating> GetUserProductRatings<TKey>(int userId, IPager pager, Func<UserProductRating, TKey> orderBy)
{
var result = _userProductRatingRepo.Table.Where(a => a.UserId == userId)
.Join(_productRepo.Table, outer => outer.ProductId, inner => inner.ProductId,
(outer, inner) => new { UserProductRating = outer, Product = inner })
.OrderByDescending(o => orderBy) // won't work because the query creates an anonymous type above that doesn't match the Func<> definition
.Skip(pager.Skip).Take(pager.PageSize)
.Select(a => new
{
a.UserProductRating.UserId,
a.UserProductRating.ProductId,
a.UserProductRating.VoteCount,
a.UserProductRating.TotalViews,
a.UserProductRating.Rating,
a.Product.Name
}).ToList();
}
Old code that works with orderBy:
public List<UserProductRating> GetUserProductRatings<TKey>(int userId, IPager pager, Func<UserProductRating, TKey> orderBy)
{
return _userProductRatingRepo.Table
.Include(a => a.Product)
.Where(a => a.UserId == userId)
.OrderByDescending(orderBy)
.Skip(pager.Skip)
.Take(pager.PageSize)
.ToList();
}
Since your OrderBy parameter takes a UserProductRating and you include it as one of the anonymous type's properties, you should be able to do this:
public List<UserProductRating> GetUserProductRatings<TKey>(int userId, IPager pager, Func<UserProductRating, TKey> orderBy)
{
var result = _userProductRatingRepo.Table.Where(a => a.UserId == userId)
.Join(_productRepo.Table, outer => outer.ProductId, inner => inner.ProductId,
(outer, inner) => new { UserProductRating = outer, Product = inner })
.OrderByDescending(o => orderBy(o.UserProductRating)) // <-- pass the joined property to the order function
.Skip(pager.Skip).Take(pager.PageSize)
.Select(a => new
{
a.UserProductRating.UserId,
a.UserProductRating.ProductId,
a.UserProductRating.VoteCount,
a.UserProductRating.TotalViews,
a.UserProductRating.Rating,
a.Product.Name
}).ToList();
}

Conditional Join with LINQ

I would like to create a LINQ join statement equivalent of a Left Join
My tables are set up like so:
Recipe
RecipeID
...
Instruction
RecipeID
StepID
SomeFlag
...
Equivalent SQL:
SELECT *
FROM Recipe r
LEFT JOIN Instruction i
ON r.RecipeID = i.RecipeID
AND SomeFlag > 0
This is what I have so far:
var tmp = db.Recipe
.GroupJoin(
db.Instruction,
r => r.RecipeID,
i => i.RecipeID,
(r, i) => new {r, i},
???);
Firstly, is GroupJoin the correct choice for this type of operation? From what I understand, Join is equivalent to the SQL 'Inner Join' and GroupJoin is equivalent to 'Left Join'. Second, what is the correct syntax to obtain my desired result? I have been searching for a while and I can't seem to find a suitable answer using extension methods.
Don't forget to read the help from (GroupJoin: MSDN http://msdn.microsoft.com/en-us/library/bb535047.aspx and Join MSDN http://msdn.microsoft.com/fr-fr/library/bb534675.aspx)
The last argument of the GroupJoin and Join is optional (by overload) and is not usually used.
It is a function that allow you to specify how to compare r.RecipeID with i.RecipeID. As RecipeID must be an integer, using the default comparer is a good choice. So let it with:
var tmp = db.Recipe
.Join(db.Instruction,
r => r.RecipeID,
i => i.RecipeID,
(r, i) => new {r, i});
Now what you want to have is to remove all the instructions that have SomeFlag > 0. Why not do this before joining?
Like this:
var tmp = db.Recipe
.Join(db.Instruction.Where(instruction => instruction.SomeFlag > 0),
r => r.RecipeID,
i => i.RecipeID,
(r, i) => new {r, i});
Update
#usr has perfectly commented saying Join performs an INNER JOIN.
As you may have remarked, LINQ does not have different methods for INNER, OUTER, LEFT, RIGHT joins. To know the equivalent LINQ of a particular SQL join you may find help on MSDN ( http://msdn.microsoft.com/en-us/library/vstudio/bb397676.aspx ).
var tmp = from recipe in Recipes
join instruction in
from instruction in Instructions
where instruction.SomeFlag > 0
select instruction
on recipe.RecipeID equals instruction.RecipeID into gj
from instruction in gj.DefaultIfEmpty()
select new
{
recipe,
instruction
};
using extension methods it is a bit of an ugly solution:
var tmp = Recipes.GroupJoin(Instructions.Where(instruction => instruction.SomeFlag > 0),
recipe => recipe.RecipeID,
instruction => instruction.RecipeID,
(recipe, gj) => new { recipe, gj })
.SelectMany(#t => #t.gj.DefaultIfEmpty(),
(#t, instruction) => new
{
#t.recipe,
instruction
});
Please tell me if I did't understand you, but this extension method returns the same result that you priveded in sql.
public static IEnumerable<ResultType> GetLeftJoinWith(this IEnumerable<Recipe>, IEnumerable<Instructions> ins)
{
var filteredInstructions = ins.Where(x => x.SomeFlag > 0);
var res = from r in rec
join tmpIns in filteredInstructions on r.RecipeID equals t.RecipeID into instructions
from instruction in instructions.DefaultIfEmpty()
select new { r, instruction };
return res;
}
try this
var model = db.Recipe
.GroupJoin(db.Instructions.Where(instruction => instruction.SomeFlag > 0),r => r.RecipeID,i => i.RecipeID, (r, i) => new { Recipe = r, Instructions = i })
.SelectMany(t => t.Instructions.DefaultIfEmpty(),(t, Instructions) => new
{
Recipe = t.Recipe,
Instructions = Instructions
});

Categories