Equivalent linq query syntax - c#

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;

Related

Converting SQL with LEFT JOIN to Linq (Method Syntax)

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 }
);

Group Linq Query Results in a String

Here is what I am trying to accomplish:
I have a list of companies that offer different services. I am trying to group the services together of a company in a string format so when I can export to excel it shows up in one column.
Right now if a company has 4 services, they will show up 4 different times in query. Which is logical, just want to group them together.
Here is what I have tried and get "A lambda expression with a statement body cannot be converted to an expression tree"
Services = (from cc in CharityCategories join c in Cao_Categories on cc.CategoryID equals c.CategoryID
join chy in CharityYears on cc.CharityYearID equals chy.CharityYearID
where chy.CampYearID == 5 && chy.StatusID == 1
group c by c.Category into cg
select new { Categories = cg.Key.Trim()}).Aggregate(new StringBuilder(), (a, b) =>
{
if (a.Length > 0)
a.Append(",");
a.Append(b.ToString().Split('=')[1].Replace(" }", ""));
return a;
}).ToString() ,
LinqPad shows the error on the line, right after StringBuilder(), "Aggregate(new StringBuilder(), (a, b)"
I can get them to group in a link and when clicked, that link lists them in a format like { myservice = 1} - This is why I am using .Append
use below code then change your logic accordingly, hopefully this will work
Correction in cg.Key.ToString().Trim()
Services = (from cc in CharityCategories
join c in Cao_Categories on cc.CategoryID equals c.CategoryID
join chy in CharityYears on cc.CharityYearID equals chy.CharityYearID
where chy.CampYearID == 5 && chy.StatusID == 1
group c by c.Category into cg
select new { Categories = cg.Key.ToString().Trim() }).Aggregate(new StringBuilder(), (a, b) =>
{
if (a.Length > 0)
a.Append(",");
a.Append(b.ToString().Split('=')[1].Replace(" }", ""));
return a;
}).ToString();
I don't know your requirement, we can correct this if you can provide exact expected result
Well as the error says you cannot convert a lambda expression with a statement body to an expression tree. Entity framework uses expression trees to convert to sql statements. However, you can materialize the results first with (AsEnumerable) and then use your code with linq to objects.
Services = (from cc in CharityCategories
join c in Cao_Categories on cc.CategoryID equals c.CategoryID
join chy in CharityYears on cc.CharityYearID equals chy.CharityYearID
where chy.CampYearID == 5 && chy.StatusID == 1
group c by c.Category into cg
select new { Categories = cg.Key.ToString().Trim() })
.AsEnumerable()
.Aggregate(new StringBuilder(), (a, b) =>
{
if (a.Length > 0)
a.Append(",");
a.Append(b.ToString().Split('=')[1].Replace(" }", ""));
return a;
}).ToString();
Important: The aggregation will take place after all the rows were retrieved from the DB.
Assuming your result set can be represented in the form of the following class
public class Category
{
public string Name{get;set;}
public string Product{get;set;}
}
You could write the following LINQ query to get the result in the way you wished to:
var testList = new List <Category> {
new Category {Name = "A",Product = "decks"},
new Category {Name = "B",Product = "cards"},
new Category {Name = "C",Product = "paper"},
new Category {Name = "A",Product = "scissor"},
new Category {Name = "B",Product = "crates"},
new Category {Name = "C",Product = "rocks"}
};
var finalList = testList
.GroupBy(x => x.Name)
.Select(x => new {
Category =x.Key,
Items = String.Join(",", x.Select(y => y.Product))
});
Thus the result set would be in the form of:
Category A -> decks, scissor
Category B-> cards,crates
Category C-> paper, rocks
Do mark as answer if this helps.

Entity Framework - Count distinct books pr. tag using navigation property

I have a table "Book" with a many-to-many relationship to "Tag" and need a distinct Book-count pr. Tag. In SQL, the query looks like this:
SELECT t.NAME,
count(DISTINCT b.BookId)
FROM _Tag t
JOIN Book.BookTag bt
ON t.Id = bt.TagId
JOIN Books b
ON b.BookId = bt.BookId
GROUP BY t.NAME
ORDER BY count(DISTINCT b.BookId) DESC;
I have fetched the tags and included the Books navigation-property and from this I should be able to get distinct BookId's pr. tagname. I want to get the result in a tuple.
So far I have tried the following:
var tagTuples = from tag in tags
join book in tags.Select(t => t.Books) on tag.Books equals book
group new {tag, book} by tag.Name
into g
select new Tuple<string, string, int>("tags", g.Key, g.Select(x => x.book).Count());
...and...
var tagTuples = tags.GroupBy(t => t.Name)
.Select(t2 => new Tuple<string, string, int>("tags", t2.Key, t2.Sum(t4 => t4.Books
.Select(b => b.BookId).Distinct().Count())))
.Where(t3 => !string.IsNullOrWhiteSpace(t3.Item2)).Take(15);
...and my latest version:
var tagTuples =
tags.Select(t => new {t.Name, BookId = t.Books.Select(b => b.BookId)})
.GroupBy(tb => tb.Name)
.Select(tb2 => new Tuple<string, string, int>("tags", tb2.Key, tb2.Sum(tb3 => tb3.BookId.Distinct().Count())));
Nevermind the small differences in the query's - I'm only interested in a solution to the problem described above.
Frustration! It takes me 2 minutes to write an SQL query that does this and I'm pretty sure there's a simple answer, but I lack EF routine.
Thankyou for your time. :)
using(var ctx = new EntitiesContext())
{
// GROUP By name
var bookCountByTag = ctx.Tags.GroupBy(t => t.Name)
.Select(t2 => new {
// SELECT the key (tag name)
t2.Key,
// "GroupBy" has many result, so use SelectMany
Count = t2.SelectMany(t3 => t3.book)
.Distinct()
.Count()})
.ToList();
}

Converting a SQL Query to a Predicate Expression using Fluent Synatx

How do I write the following sql query to a Predicate Expression using Fluent Sytax:
select c.* from customer c
inner join Orders r on r.CustomerId = c.Id
where
c.MemberSince > '01/01/2013' &&
r.OrderDate > '01/01/2014'
order by r.OrderDate
I am expecting something like this:
Expression<Func<Customer, bool>> filters = PredicateBuilder.True<Customer>();
filters = filters.And(x => x.MemberSince > DateTime.Parse('01/01/2013'));
filterr = ....
I am not sure how to add the Orders predicate. And then I call:
var list = db.Customers.AsExpandable().Where(filters).ToList();
You don't need PredicateBuilder at all. You just need to understand that the result of a join is effectively a sequence of pairs:
DateTime memberCutoff = ...;
DateTime orderCutoff = ...;
var query = context.Customers
.Join(context.Orders,
c => c.Id, r => r.CustomerId, (c, r) => new { c, r })
.Where(pair => pair.c.MemberSince > memberCutoff &&
pair.r.OrderDate > orderCutoff)
.OrderBy(pair => pair.r.OrderDate);

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