Conditional Join with LINQ - c#

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

Related

LINQ Multiple Join FOR XML Lambda Query

I'm trying to replicate the below SQL query in LINQ Lambda by using String.Join. Could someone please point out how I can correct Lambda query.
I have formatted my SQL and placed the error message here.
Here is the error I receive:
System.InvalidOperationException: The LINQ expression 'DbSet<TblObligor>()
.Join(
inner: DbSet<TblObligorGuaranties>(),
outerKeySelector: t => t.ObligorId,
innerKeySelector: t0 => (decimal)t0.ObligorID,
resultSelector: (t, t0) => new TransparentIdentifier<TblObligor, TblObligorGuaranties>(
Outer = t,
Inner = t0
))
.Join(
inner: DbSet<TblObligorGuarantyTypes>(),
outerKeySelector: ti => ObligorService.MapObligorGuaranties(
o: ti.Outer,
og: ti.Inner).GuarantyTypeID,
innerKeySelector: t1 => (Nullable<int>)t1.GuarantyTypeID,
resultSelector: (ti, t1) => new TransparentIdentifier<TransparentIdentifier<TblObligor, TblObligorGuaranties>, TblObligorGuarantyTypes>(
Outer = ti,
Inner = t1
))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
I want a string concatenated colmn of GuarantyTypeDescription.
select
o2.ObligorID,
STUFF(
(
select
',' + cast(
ogt.GuarantyTypeDescription as nvarchar
)
from
tblObligor o
left join tblObligorGuaranties og on o.ObligorId = og.ObligorID
left join tblObligorGuarantyTypes ogt on og.GuarantyTypeID = ogt.GuarantyTypeID
where
1 = 1
and o.ObligorID = o2.ObligorID
and o.assetid = 1996323923 for xml path('')
),
1,
1,
''
) as xmlstring
from
tblObligor o2
where
1 = 1
and o2.assetid = 1996323923
Here is my code:
public async Task<IEnumerable<ObligorGuarantyDTO>> GetObligorsListAsync(int? assetId)
{
var obligorGuarantiesList = _context.TblObligor
.Join(_context.TblObligorGuaranties, o => o.ObligorId, og => og.ObligorID, (o, og) => new { o, og })
.Select(join => MapObligorGuaranties(join.o, join.og))
.Join(_context.TblObligorGuarantyTypes, og => og.GuarantyTypeID, ogt => ogt.GuarantyTypeID, (og, ogt) => new { og, ogt })
.Select(join => MapObligorGuarantyTypes(join.og, join.ogt))
.AsEnumerable();
return obligorGuarantiesList;
}
Here are my maps:
private static ObligorGuarantyDTO MapObligorGuaranties(TblObligor o, TblObligorGuaranties og)
=> new ObligorGuarantyDTO()
{
ObligorID = o.ObligorId,
GuarantyID = og.GuarantyID,
GuarantyTypeID = og.GuarantyTypeID,
Release = og.Release,
ReleaseDate= og.ReleaseDate,
Note= og.Note,
EditBy = og.EditBy,
EditTime= og.EditTime
};
private static ObligorGuarantyDTO MapObligorGuarantyTypes(ObligorGuarantyDTO og, TblObligorGuarantyTypes ogt)
=> new ObligorGuarantyDTO()
{
GuarantyTypeID = ogt.GuarantyTypeID,
GuarantyTypeDescription = String.Join(", ", ogt.GuarantyTypeDescription)
};
I'm a bit out of my element with multi-level joins in LINQ, but I think the following should put you on the right track.
As far as I know, there is nothing you can write in LINQ that will translate to the FOR-XML string concatenation SQL. Although recent versions of SQL Server now have a STRING_AGG() function, I'm not sure is there is a LINQ mapping for that either. (Someone may correct me if I am wrong.)
So the apparent best plan is to write a query to return a collection of ObligorID/GuarantyTypeDescription pairs, group by ObligorID, and then String.Join() the GuarantyTypeDescription values.
The first step is writing LINQ that will translate into the equivalent of the following SQL:
select o.ObligorID, ogt.GuarantyTypeDescription
from
tblObligor o
left join tblObligorGuaranties og on o.ObligorId = og.ObligorID
left join tblObligorGuarantyTypes ogt on og.GuarantyTypeID = ogt.GuarantyTypeID
where 1=1
and o.assetid = 1996323923
Assuming that you are using a provider like Entity Framework that adds collection properties to your classes that represent foreign key relationships, I think the following may do the job:
_context.TblObligor
.Where(o => o.assetid = 1996323923)
.SelectMany(o => o.TblObligorGuaranties, (o, og) => new { o, og })
.SelectMany(o2 => oog.TblObligorGuarantyTypes, (o2, ogt) => new { o2.o, o2.og, ogt })
.Select(o3 = new { o3.o.ObligorID, o3.ogt.GuarantyTypeDescription })
If your classes don't have those collection properties, the following can be used (similar to what you had, but without the mapping classes):
_context.TblObligor
.Where(o => o.assetid = 1996323923)
.Join(_context.TblObligorGuaranties, o => o.ObligorId, og => og.ObligorId,
(o, og) => new { o, og })
.Join(_context.TblObligorGuarantyTypes, o2 => o2.o.ObligorId, ogt => og.ObligorId,
(o2, ogt) => new { o2.o, o2.og, ogt })
.Select(o3 = new { o3.o.ObligorID, o3.ogt.GuarantyTypeDescription })
At this point we need to switch to IEnumerable<> to to the grouping and concatenation in memory:
If you care about order, you may need apply an .OrderBy() before the .Select().
.AsEnumerable()
.GroupBy(x => x.ObligorID)
.Select(g => new { ObligorID = g.Key, Descriptions = String.Join(", ", g })
.ToList();
The above is untested and I can't even guarantee that it is correct at this time, but I think it is close to what you need.
(I invite others who identify errors or have improvements to post a comment or directly update this answer.)
Follow up: After writing this up, I did a search on "LINQ to STRING_AGG" and the following was one of the top results: Converting T-SQL string_agg to LINQ C#. Worth a look.

Linq method based left join with multiple criteria

How i can obtain the similar Linq query as this SQL query using Linq method based?
SELECT * FROM F_ARTICLE A
LEFT JOIN F_ARTFOURNISS AF ON AF.AR_Ref = A.AR_Ref AND AF.AF_Principal = 1
ORDER BY A.AR_Design DESC
OFFSET 500 ROWS FETCH NEXT 100 ROWS ONLY
I'm using method based due to System.Linq.Dynamic requirements.
The mapping is like this:
I started by this but i don't know how to limit to AF_Principal = 1:
var query = context.F_ARTICLE
.Join(
context.F_ARTFOURNISS,
a => a.AR_Ref,
t => t.AR_Ref,
(a, t) => new { a, t })
.Select(
z => new ArticleItem()
{
Article = z.a,
Tarif = z.t.FirstOrDefault()
})
.OrderBy($"{field} {direction}");
return query.Skip(startIndex).Take(count).ToList();
The code is from my head but the point is using DefaultIfEmpty for doing LEFT JOIN:
var query = from farticle in context.F_ARTICLE
join b in context.F_ARTFOURNISS
on new {farticle.AR_Ref, AF_Principal = 1} equals new {b.AR_Ref, b.AF_Principal} into gj
from subres in gj.DefaultIfEmpty()
select farticle;
return query.Skip(startIndex).Take(count).ToList();
Also you can limit by AF_Principal using Where clause (I think DBMS engine will optimize the query anyway)
see if this works
var query = from F1 in context.F_ARTICLE join F2 in context.F_ARTFOURNISS
on F1.AR_Ref equals F2.AR_Ref into newF
from F3 in newF.where(f=>f.AF_Principal == 1).DefaultIfEmpty()
select f;
return query.Skip(startIndex).Take(count).ToList();
I think i found the query:
var query = context.F_ARTICLE
.GroupJoin(
context.F_ARTFOURNISS,
a => a.AR_Ref,
t => t.AR_Ref,
(a, t) => new { a, t })
.Select(
z => new ArticleItem()
{
Article = z.a,
Tarif = z.t.Where(t=>t.AF_Principal == 1).FirstOrDefault()
})
.OrderBy($"{field} {direction}");
return query.Skip(startIndex).Take(count).ToList();

How to convert Linq expression with multiple joins into method syntax OR retrieve select index?

I have an existing (working!) linq expression:
from ca in db.CustomAnswer
join ss in db.SurveySubmission on ca.SubmissionId equals ss.Id
join cq in db.CustomQuestion on ca.QuestionId equals cq.Id
where (ss.SurveyId == request.SurveyId)
orderby ss.Submitted, cq.SortOrder
select new
{
SubmissionId = ss.Id,
Answer = ca.Answer
}
I want to add the index of the select into the new object, e.g.
from ca in db.CustomAnswer
join ss in db.SurveySubmission on ca.SubmissionId equals ss.Id
join cq in db.CustomQuestion on ca.QuestionId equals cq.Id
where (ss.SurveyId == request.SurveyId)
orderby ss.Submitted, cq.SortOrder
select new
{
SubmissionId = ss.Id,
**Code = selectIndex,**
Answer = ca.Answer
}
To do this, I believe I need to first convert my query to method syntax so I can use the Select((q, index) => ...) form. To my simple mind, I think it should be:
db.SurveySubmission
.Where(ss => ss.SurveyId == request.SurveyId)
.OrderBy(ss => ss.Submitted)
.Join(db.CustomAnswer, ss => ss.Id, ca => ca.SubmissionId, (ss, ca) => new { ss, ca })
.Join(db.CustomQuestion, o => o.ca.QuestionId, cq => cq.Id, (o, cq) => new { o.ss, o.ca, cq })
.OrderBy(q => q.cq.SortOrder)
.Select((q, idx) => new {
SubmissionId = q.ss.Id,
Answer = q.ca.Answer,
Code = idx
});
However, when the expression is evaluated I get an error:
LINQ to Entities does not recognize the method
'System.Linq.IQueryable1[<>f__AnonymousTypef3[System.Guid,System.String,System.Int32]]
Select[<>f_AnonymousTypee3,<>f__AnonymousTypef3]
(System.Linq.IQueryable1[<>f__AnonymousTypee3[My.Data.Namespace.SurveySubmission,
My.Data.Namespace.CustomAnswer,My.Data.Namespace.CustomQuestion]],
System.Linq.Expressions.Expression1[System.Func3[<>f_AnonymousTypee3[My.Data.Namespace.SurveySubmission,
My.Data.Namespace.CustomAnswer,My.Data.Namespace.CustomQuestion],System.Int32,<>f__AnonymousTypef3[System.Guid,
System.String,System.Int32]]])'
method, and this method cannot be translated into a store expression.
I'm hoping this is glaringly obvious to someone? I've stared at it for several hours and the only conclusion I can make is that I'm not clever enough ... can anyone help please??
EF can't translate that into SQL, because in SQL sets are unordered; the idea of an index just doesn't make any sense to it.
Instead do everything but getting the index using an EF query, and then tack on the indexes in a linq to objects query:
var query = //your original query goes here
var finalQuery = query.AsEnumerable()
.Select((answer, index) => new
{
answer.SubmissionId,
answer.Answer,
Code = index,
});

How to group using LINQ, then get value of largest group

Here's what I have so far:
var bestReason =
from p in successfulReasons
group p by p.Reason into g
select new { Excuse = g.Key, ExcuseCount = g.Count() };
What I need to do now is return one reason that is the best reason, determined by which were successful in the past.
Sample data:
ID,Reason
---------
0,Weather
1,Traffic
2,Illness
3,Weather
4,Traffic
5,Traffic
6,Pirates
should return "Traffic"
Would like to do it all in one LINQ statement, if possible.
Thanks.
EDIT: If there are 7 Pirate Attacks, and 7 Traffic Accidents, I'm ok with returning either one (the first alphabetically would be fine).
var bestReason = successfulReasons
.GroupBy(r => r.Reason)
.OrderByDescending(grp => grp.Count())
.First().Key;
If I understand your question correctly, you can do:
string bestReason =
(from p in successfulReasons
orderby p.Reason
group p by p.Reason into g
orderby g.Count() descending
select g.Key).FirstOrDefault();
var group = excuses.GroupBy(m => m.Reason)
.OrderByDescending(m => m.Count())
.Select(m => m.Key)
.FirstOrDefault();
Which produces the following sql statement:
SELECT TOP (1) [t1].[Reason]
FROM (
SELECT COUNT(*) AS [value], [t0].[Reason]
From [dbo].[Excuses] As [t0]
GROUP BY [t0].[Reason]
) As [t1]
ORDER BY [t1].[value] DESC
Since this is a moderately complicated IQueryable expression, you might consider compiling it to speed up the response time:
Func<ExcusesDataContext, string> commonResult = CompiledQuery.Compile(
(ExcusesDataContext c) => c.Excuses.GroupBy(m => m.Reason).OrderByDescending(m => m.Count()).Select(m => m.Key).FirstOrDefault()
);
Console.WriteLine(commonResult(new ExcusesDataContext()));
Console.ReadLine();
You could also just call the stored procedure via a repository and snag the particular value that you're looking for. This would be the fastest path to happiness, but the least fun to maintain:
string excuse = this.repo.Excuses.MostCommonFor(ProblemList.BeingLate);

Method Chaining equivalent?

This is working properly (from initial testing).
Since method chaining is my preferred format, I've tried to figure out what the method chaining equivalent is, but with no luck. Any ideas?
var data = (from p in db.Persons
from c in db.Companies
where c.CompanyName == companyName && p.CompanyId == c.CompanyId
select p)
.Select(p => new
{
Id = p.PersonId,
Name = string.Format("{0} {1}", p.FirstName, p.LastName)
});
Thanks,
--Ed
I would reorder the query a bit to filter out the companyName first, then perform the join. This would allow you to use this fluent syntax:
var query = db.Companies.Where(c => c.CompanyName == companyName)
.Join(db.Persons, c => c.CompanyId, p => p.CompanyId, (p, c) => p)
.Select(p => new
{
Id = p.PersonId,
Name = string.Format("{0} {1}", p.FirstName, p.LastName)
});
Having said that, some queries are a lot easier to write in query syntax, so why constrict yourself? Complicated joins are usually nicer in query syntax and you also get the benefit of using SelectMany join format with from ... from... instead of join p in ... on x equals y. See this question for more details: When to prefer joins expressed with SelectMany() over joins expressed with the join keyword in Linq.
Without changing the query, something like below, where oi is the opaque identifier:
var data = db.Persons.SelectMany(p => db.Companies, (p, c) => new {p,c})
.Where(oi => oi.c.CompanyName == companyName
&& oi.p.CompanyId == oi.c.CompanyId)
.Select(oi => oi.p)
.Select(p => new
{
Id = p.PersonId,
Name = string.Format("{0} {1}", p.FirstName, p.LastName)
});
However, you might also consider a few re-writes; maybe a join, or moving the company-name check earlier; and removing the double-select.
Non-toplevel from clauses translate to SelectMany calls:
db.Persons.SelectMany(p => db.Companies.Select(c => new { p, c }))
It is non-intuitive and a little hacky (although logical and reliable). I personally tend to use the query syntax for complex queries like this one because the SelectMany form is unreadable.

Categories