LINQ: Left Outer Join with multiple conditions - c#

I have two IEnumerables called BaseReportDefinitions and InputReportDefinitions. I need to do a left outer join where i want all the InputReportDefinitions and whichever BaseReportDefinitions that match. Both IEnumberables contain ReportDefinition objects that contain ParentName and ReportName properties that need to be used as the join key. I want to return the ReportDefinition object for each (in the case of BaseReportDefinition entry it may be null) in an anonymous object.
I have seen many examples of linq outer joins and outer joins with a static second condition that often gets put into a where condition but nothing that really uses two conditions fully for the join.

var items = inputReportDefinitions.GroupJoin(
baseReportDefinitions,
firstSelector => new {
firstSelector.ParentName, firstSelector.ReportName
},
secondSelector => new {
secondSelector.ParentName, secondSelector.ReportName
},
(inputReport, baseCollection) => new {inputReport, baseCollection})
.SelectMany(grp => grp.baseCollection.DefaultIfEmpty(),
(col, baseReport) => new
{
Base = baseReport,
Input = col.inputReport
});
I believe this ends up being a left outer join. I don't know how to convert this monstrosity to a query statement. I think if you add AsQueryable() to the end it could be used in Linq-to-SQL, but honestly, I have little experience with that.
EDIT: I figured it out. Much easier to read:
var otherItems = from i in inputReportDefinitions
join b in baseReportDefinitions
on new {i.ParentName, i.ReportName}
equals new {b.ParentName, b.ReportName} into other
from baseReport in other.DefaultIfEmpty()
select new
{
Input = i,
Base = baseReport
};

Related

How do I group one to many in a nested fashion with linq to entities?

I have three tables, Chart, ChartQuestion, ChartAnswer
A Chart has many ChartQuestions, a ChartQuestion has many ChartAnswers.
I'd like to do a linq query that gives me an object containing all of this data, so it'd be a ChartObject containing List<ChartQuestion> and each ChartQuestion contains a List<ChartAnswer>
I started with this:
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions)
This seems to be the first step. However I want to include the ChartAnswers now, so I have to do another join to pull back the ChartAnswers but I don't know how to do this.
I can't do a join, and while I can do a from, I am not sure of the exact syntax.
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions
from chartQuestionsSelection in chartQuestions
join chartAnswer in context.ChartAnswers on chartQuestions.ChartAnswerId equals chartAnswer.ChartAnswerId into chartAnswers // This is wrong
)
With that code above you end up as chartAnswers being separate to the chartQuestions rather than belonging to them, so I don't think it is correct.
Any idea?
joining a table destructors it i.e. you now have the row as a variable if you need it in a nested fashion then select in like this
(from chart in db.Chart
select new { //can also be your own class/record, perhaps a DTO
chart.Id,
chart.Name,
questions = (from chartQuestion in db.chartQuestion
where chart.ChartId == chartQuestion.ChartId
select new { //perhaps a dto
chartQuestion.Id,
chartQuestion.Question,
Answers =
(from chartAnswer in context.ChartAnswers
where chartAnswer.ChartAnswerId == chartQuestion.ChartAnswerId
select chartAnswer).ToList() //this is translated and not evaluated
}).ToList() //this is translated not evaluated
}).ToListAsync(cancellationToken) //this will evaluate the expression
If you need it by joins then you can group join it like this:
from m in _context.Chart
join d in _context.ChartQuestions
on m.ID equals d.ID into mdJoin
select new
{
chartId = m.ID,
chartName = "m.name",
quess = from d in mdJoin
join dd in _context.ChartAnswer
on d.Iddomain equals dd.DomainId into anJoin
select new
{
quesId = d.ID,
quesName = d.Question,
anss = anJoin
}
}
A better way: If you edit your DbConfigurations to include navigation properties of each then the code will become way simpler.
example:
db.Chart
.Include(x => x.chartQuestions)
.ThenInclude(x => x.chartAnswers)
You can search more about how to do navigation properties in EFCore, but ofcourse if the code base is large then this might not be feasible for you.
Try following :
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId
join chartAnswer in context.ChartAnswers on chartAnswer.ChartAnswerId equals chartQuestion.ChartAnswerId
)

LINQ Type arguments cannot be inferred

I have two objects that I want to join _locations and lrsLocations. I want to update _locations attributes with the corresponding lrsLocations. To do this I am using LINQ to get references to both options and then going through and updating ms2 attributes to be lrs (I know this isn't what LINQ was made for so I am open to new ideas. I am using this LINQ query now:
var joinResult =
from
ms2 in _locations
from
lrs in lrsLocations
where
ms2.LRS_ID == lrs.attributes.route_id
&&
ms2.LRS_LOC_PT == lrs.attributes.measure
select
new {ms2, lrs};
It works but I really need a LEFT OUTER JOIN so after some googling I attempting:
var joinResult = from ms2 in _locations
join lrs in lrsLocations on new { ms2.LRS_ID, ms2.LRS_LOC_PT }
equals new { lrs.attributes.route_id, lrs.attributes.measure } into merge
from lrs in merge.DefaultIfEmpty()
select new { ms2, lrs };
The issue is that the join lrs complains: http://i.imgur.com/5EAeFmx.png (Click for picture)
So I either need a way to convert my working LINQ to be a LEFT OUTER JOIN, or understand what the problem is talking about in the new one, because a google didn't give me much insight.
The anonymous types in your JOIN must have the same property names:
join lrs in lrsLocations on new { ms2.LRS_ID, ms2.LRS_LOC_PT }
equals new { LRS_ID = lrs.attributes.route_id, LRS_LOC_PT = lrs.attributes.measure }
Obviously the types have to match as well. If they don't you need a conversion or a cast.

Methods of writing LINQ C#

I'm perplexed by the below.
I've been using LINQ in my projects using formats such as:
var query =
from obj_i in set1
join obj_j in set2 on
new {
JoinField1 = obj_i.SomeField1,
JoinField2 = obj_i.SomeField2,
JoinField3 = obj_i.SomeField3,
JoinField4 = obj_i.SomeField4
}
equals
new {
JoinField1 = obj_j.SomeOtherField1,
JoinField2 = obj_j.SomeOtherField2,
JoinField3 = obj_j.SomeOtherField3,
JoinField4 = obj_j.SomeOtherField4
}
But I was recently told that the below is also 'another way' of writing LINQ queries.
var query =
from obj_i in set1
join obj_j in set2 on
obj_i.SomeField1 = obj_j.SomeOtherField1 and
obj_i.SomeField2 = obj_j.SomeOtherField2 and
obj_i.SomeField3 = obj_j.SomeOtherField3 and
obj_i.SomeField4 = obj_j.SomeOtherField4
As I understand, using the single = is wrong (especially in the case where by == doesn't apply since you need to use equals, but also using and is wrong, since the correct keyword would be && if you were allowed to use anything but equals in this case.
I can understand the use of && and == in the where clause, which makes it even more outstanding that the above code can be used, since it doesn't even compile.
Am I missing something?
If so, could you point me to where I can learn of this alternate method of writing LINQ?
But I was recently told that the below is also 'another way' of writing LINQ queries.
No, the second syntax you show is incorrect. Just try it, you'll see that it doesn't compile.
The join clause in Linq query comprehension syntax is translated to a call to the Join extension method. For instance, this query:
var query =
from x in a
join y in b
on x.Foo equals y.Bar
select Baz(x, y);
Is translated to:
var query = a.Join(b, x => x.Foo, y => y.Foo, (x, y) => Baz(x, y));
So you can see that the parts on the left and right of equals correspond to different parameters of the Join method: the left part selects the join key for the first source, and the right part selects the key for the second source. These keys are then matched against each other to perform the join. This is the important part: the on clause doesn't specify a free-form join condition, it specifies how to extract the join key on each side. So the left and right key selectors have to be clearly separated; the second syntax you show can't work, because there's no way to extract full key information for either source: the join could only be performed by evaluating the join condition for each (x, y), which is an O(n²) operation instead of a (roughly) O(n) operation.
However, sometimes you need more flexibility than what an equi-join can give; in that case you can use a cross-join and filter the results:
var query =
from x in a
from y in b
where x.Foo == y.Bar
select Baz(x, y);
Or in your case:
var query =
from obj_i in set1
from obj_j in set2
where obj_i.SomeField1 == obj_j.SomeOtherField1
&& obj_i.SomeField2 == obj_j.SomeOtherField2
&& obj_i.SomeField3 == obj_j.SomeOtherField3
&& obj_i.SomeField4 == obj_j.SomeOtherField4;

Throwing exceptions with Linq

I am joining 2 in memory collections
var items =
from el in elements
join def in Cached.Elements()
on el.Value equals def.Name into temp1
from res in temp1.DefaultIfEmpty()
select new
{
el.NodeType,
res.DefKey,
res.DefType,
res.BaseKey,
el.Value
};
However, ideally if one of the elements can't be found, I'd like to raise an exception, something akin to
throw new System.Exception(el.Value + " cannot be found in cache!");
I was looking at the System.Interactive which offers a Catch extension method but I am unsure how to reference the current 'el' in that context. So for example I was wondering about something like
var items = (
from el in elements
join def in Cached.Elements()
on el.Value equals def.Name into temp1
from res in temp1.DefaultIfEmpty()
select new
{
el.NodeType,
res.DefKey,
res.DefType,
res.BaseKey,
el.Value
})
.ThrowIfEmpty();
but, istm, that that would entail passing the whole set into the extension method rather than raising the exception when the missing value is encountered.
Alternatively, I could replace the DefaultIfEmpty with a ThrowIfEmpty
var items = (
from el in elements
join def in Cached.Elements()
on el.Value equals def.Name into temp1
from res in temp1.ThrowIfEmpty()
select new
{
el.NodeType,
res.DefKey,
res.DefType,
res.BaseKey,
el.Value
});
Is there a 'proper'/better way to do this?
You can use GroupJoin. Something like this should work for you:
elements.GroupJoin(Cached.Elements(), e => e.Value, d => d.Name, (e, dSeq) => {
var d = dSeq.Single();
return new { e, d };
});
The GroupJoin resultSelector accepts two arguments: the left key, and the sequence of matching right keys. You can raise an exception if the sequence is empty; one way to achieve that would be to use the Single operator.
I think this is one of the places where you can use Composite Keys.
if you use equals keyword to execute equality on join.
from documentation :
You create a composite key as an anonymous type or named typed with
the values that you want to compare. If the query variable will be
passed across method boundaries, use a named type that overrides
Equals and GetHashCode for the key

How to do joins in LINQ on multiple fields in single join

I need to do a LINQ2DataSet query that does a join on more than one field (as
var result = from x in entity
join y in entity2
on x.field1 = y.field1
and
x.field2 = y.field2
I have yet found a suitable solution (I can add the extra constraints to a where clause, but this is far from a suitable solution, or use this solution, but that assumes an equijoin).
Is it possible in LINQ to join on multiple fields in a single join?
EDIT
var result = from x in entity
join y in entity2
on new { x.field1, x.field2 } equals new { y.field1, y.field2 }
is the solution I referenced as assuming an equijoin above.
Further EDIT
To answer criticism that my original example was an equijoin, I do acknowledge that, My current requirement is for an equijoin and I have already employed the solution I referenced above.
I am, however, trying to understand what possibilities and best practices I have / should employ with LINQ. I am going to need to do a Date range query join with a table ID soon, and was just pre-empting that issue, It looks like I shall have to add the date range in the where clause.
Thanks, as always, for all suggestions and comments given
var result = from x in entity
join y in entity2 on new { x.field1, x.field2 } equals new { y.field1, y.field2 }
var result = from x in entity1
join y in entity2
on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }
You need to do this, if the column names are different in two entities.
The solution with the anonymous type should work fine. LINQ can only represent equijoins (with join clauses, anyway), and indeed that's what you've said you want to express anyway based on your original query.
If you don't like the version with the anonymous type for some specific reason, you should explain that reason.
If you want to do something other than what you originally asked for, please give an example of what you really want to do.
EDIT: Responding to the edit in the question: yes, to do a "date range" join, you need to use a where clause instead. They're semantically equivalent really, so it's just a matter of the optimisations available. Equijoins provide simple optimisation (in LINQ to Objects, which includes LINQ to DataSets) by creating a lookup based on the inner sequence - think of it as a hashtable from key to a sequence of entries matching that key.
Doing that with date ranges is somewhat harder. However, depending on exactly what you mean by a "date range join" you may be able to do something similar - if you're planning on creating "bands" of dates (e.g. one per year) such that two entries which occur in the same year (but not on the same date) should match, then you can do it just by using that band as the key. If it's more complicated, e.g. one side of the join provides a range, and the other side of the join provides a single date, matching if it falls within that range, that would be better handled with a where clause (after a second from clause) IMO. You could do some particularly funky magic by ordering one side or the other to find matches more efficiently, but that would be a lot of work - I'd only do that kind of thing after checking whether performance is an issue.
Just to complete this with an equivalent method chain syntax:
entity.Join(entity2, x => new {x.Field1, x.Field2},
y => new {y.Field1, y.Field2}, (x, y) => x);
While the last argument (x, y) => x is what you select (in the above case we select x).
I think a more readable and flexible option is to use Where function:
var result = from x in entity1
from y in entity2
.Where(y => y.field1 == x.field1 && y.field2 == x.field2)
This also allows to easily change from inner join to left join by appending .DefaultIfEmpty().
var result = from x in entity
join y in entity2
on new { X1= x.field1, X2= x.field2 } equals new { X1=y.field1, X2= y.field2 }
select new
{
/// Columns
};
you could do something like (below)
var query = from p in context.T1
join q in context.T2
on
new { p.Col1, p.Col2 }
equals
new { q.Col1, q.Col2 }
select new {p...., q......};
Using the join operator you can only perform equijoins. Other types of joins can be constructed using other operators. I'm not sure whether the exact join you are trying to do would be easier using these methods or by changing the where clause. Documentation on the join clause can be found here. MSDN has an article on join operations with multiple links to examples of other joins, as well.
If the field name are different in entities
var result = from x in entity
join y in entity2 on
new {
field1= x.field1,
field2 = x.field2
}
equals
new {
field1= y.field1,
field2= y.myfield
}
select new {x,y});
As a full method chain that would look like this:
lista.SelectMany(a => listb.Where(xi => b.Id == a.Id && b.Total != a.Total),
(a, b) => new ResultItem
{
Id = a.Id,
ATotal = a.Total,
BTotal = b.Total
}).ToList();
I used tuples to do that, this is an example for two columns :
var list= list1.Join(list2,
e1 => (e1.val1,e1.val2),
e2 => (e2.val1,e2.val2),
(e1, e2) => e1).ToList();
from d in db.CourseDispatches
join du in db.DispatchUsers on d.id equals du.dispatch_id
join u in db.Users on du.user_id equals u.id
join fr in db.Forumreports on (d.course_id + '_' + du.user_id) equals (fr.course_id + '_'+ fr.uid)
this works for me
Declare a Class(Type) to hold the elements you want to join. In the below example declare JoinElement
public class **JoinElement**
{
public int? Id { get; set; }
public string Name { get; set; }
}
results = from course in courseQueryable.AsQueryable()
join agency in agencyQueryable.AsQueryable()
on new **JoinElement**() { Id = course.CourseAgencyId, Name = course.CourseDeveloper }
equals new **JoinElement**() { Id = agency.CourseAgencyId, Name = "D" } into temp1

Categories