Linq join query with left outer join throwing object reference not found - c#

I have a list and two tables. (This is a very simplified version of the actual schema, but should work for the question)
List_A
FPI
1
2
3
4
Table_B
FPI_______NI
2_________1
4_________2
Table_C
NI_______Name
1_________x
2_________y
My linq query:
(from a in List_A
join b in Table_B on a.FPI equals b.FPI into ab
from b in ab.DefaultIfEmpty()
join c in Table_C on b.FI equals c.FI into bc
from c in bc.DefaultIfEmpty()
select new {
FPI = a.FPI,
Name = c?.Name}).ToList();
this code throws an exception that Object reference not set to an instance of an object..
After a lot of trial and experiment, I have reached to a conclusion that in the second join when i'm doing b.FI equals c.FI, at that time it is failing for the entries for which there is no value in the Table_B.
The expected output of the query should be
ABC
FPI____NI___Name
1_____null__null
2_____1_____x
3_____null__null
4_____2_____y
I'm not sure why this error is coming and what would be the best solution for this problem.

Your query would be perfectly valid if it was a LINQ to Entities query translated to SQL.
However, since the root of the query is List_A which is not a IQueryable, the whole query executes in LINQ to Objects context, where you are supposed to perform null checks on right side variable of the left outer join anywhere, including further join conditions.
So the simple fix would be using
join c in Table_C on b?.FI equals c.FI into bc
However, note that the query is highly inefficient. Since it is resolved to Enumerable methods, the whole Table_B and Table_C will be read in memory and then joined.
A better approach would be to separate the db and in memory queries:
var dbQuery =
from b in Table_B
join c in Table_C on b.FI equals c.FI into bc
from c in bc.DefaultIfEmpty()
select new { b.FPI, c.Name };
var query =
from a in List_A
join bc in dbQuery on a.FPI equals bc.FPI into abc
from bc in abc.DefaultIfEmpty()
select new
{
FPI = a.FPI,
Name = bc?.Name
};
var result = query.ToList();

You can try
var list=(from a in Table_A
join b in Table_B on a.FPI equals b.FPI into ab
from b in ab.ToList()
join c in Table_C on b.NI equals c.NI into bc
from c in bc.DefaultIfEmpty()
select new {
FPI = a.FPI,
Name = c.Name}).ToList();
Update
var list = (from a in Table_A
join b in Table_B on a.FPI equals b.FPI into ab
from b in ab.DefaultIfEmpty()
join c in Table_C on b == null ? 0 : b.NI equals c.NI into bc
from c in bc.DefaultIfEmpty()
select new
{
FBI = a.FPI,
NI = c != null ? c.NI : null,//if NI is nullable
//NI = c != null ? c.NI : 0,//if NI is not nullable
Name = c!=null?c.Name:null
}).ToList();

Related

To determine if outer join has a match using linq

I have two tables A and B,which I can outer join with linq. I need a way to find if it is possible to have a boolean along with the query if the outer join has a match. For example, I need a boolean to be true if a record is present in tableA and not in tableB. This can be done in SQL using IF, I was wondering if there was something similar in Linq
var result = from a in tableA
join b in tableB on a.Id equals b.userId into group1
from g1 in group1.DefaultIfEmpty()
select new{id = g1.Id,userId = g1.userId,boolIsPresent =(present in tableA not in tableB)}.ToList();
Currently, you're performing an inner join, not a left outer join, which means only records/objects that exist in both tables are retrieved. thus your bool boolIsPresent would always be true.
edit:
to test if a record is present in tableA and does not have a matching record in tableB just check if g1 != null, i.e:
var result = from a in tableA
join b in tableB on a.Id equals b.userId into group1
from g1 in group1.DefaultIfEmpty()
select new
{
id = g1 != null? g1.Id : enterDefault,
userId = g1 != null? g1.userId : enterDefault,
boolIsPresent = g1 != null
}.ToList();
It will be something like
boolisPresent = b != null
Once you get an Outer join working!

How can I convert this left outer join SQL statement in to a LINQ query?

this is a bit confusing for me. I know that left outer joins aren't built in to LINQ natively and that you have to use 'into' and 'DefaultIfEmpty()', but I have a bit of a complex SQL query.
The query:
SELECT * FROM TableA as a
LEFT OUTER JOIN TableB as b
on a.ID = b.ID and a.StatusOne = 1 AND b.StatusOne = 1 AND (a.StatusTwo != 1 OR b.StatusTwo!= 1)
LEFT OUTER JOIN TableC as c
on a.ID = c.ID AND a.StatusOne = 1 AND c.StatusOne = 1 AND (a.StatusTwo != 1 OR c.StatusTwo != 1)
WHERE
a.ID = 99999 AND (b.ID is not null OR c.ID is not null)
I'm not even really sure where to begin on this. If someone could help me out I would appreciate it immensely.
I am giving you the idea behind, I didn't tested the code, but to join two tables based on different fields, your join should have a anonymous type to compare.
var one = 1
from a in tableA
join b in tableB on new { a.ID, b.StatusOne } equals new { b.ID, one} into ab
But in your case you have more than one condition, so before calling DefaultIsEmpty, you should check the latest condition
from a in tableA
join b in tableB on new { a.ID, b.StatusOne } equals new { b.ID, one} into ab
from ab in ab.Where(x => x.a.StatusTwo != 1 || x.b.StatusTwo != one).DefaultIfEmpty()
Then the other outer join follows the same pattern and you need to make a final join. A good way to start is downloading the LinqPad and see which lambda expression it generates for your query, and you can take it from there. You can optimize it later but you will get the idea behind the generation. Hope this helps

Correct syntax for multiple left joins in LINQ?

I am trying to figure out the LINQ syntax for multiple left join, but I am getting the error: The name 'c' is not in scope on the left side of 'equals'. Consider swapping the expressions on either side of 'equals'.
I have already tried swapping, and if I do, it makes both 'c' and 'd' have the "not in scope" error.
var result =
from a in db.tableA
join b in db.tableB //first join (inner join)
on a.field1 equals b.field1
join c in db.tableC //second join (left join)
on a.field1 equals c.field1
into left_one
join d in db.tableD //third join (left join)
on c.field2 equals d.field2
// ^ here
into left_two
where a.field1 == theValueImSearchingFor
from c in left_one.DefaultIfEmpty()
from d in left_two.DefaultIfEmpty()
select new CombinedObject()
{
...
}
The reason I am using on c.field2 equals d.field2 in the third join statement is that my tables are structured like this:
tableA: field1
tableB: field1
tableC: field1 field2
tableD: field2
That is, the only way to relate tableD to the rest of the data is to use field2.
Can someone please correct my syntax? Or is there a certain way I have to do it given my setup of tables?
I use this type of syntax:
var results = (from a in db.tableA
from b in db.tableB.Where(s => s.field1 == a.field1)
from c in db.tableC.Where(s => s.field1 == a.field1).DefaultIfEmpty()
from d in db.tableD.Where(s => s.field2 == c.field2).DefaultIfEmpty()
select new CombinedObject() { });
It seems to work well on multiple tables. I think I got my field1s and field2s right to match your example :)
[Edit]
As per the comment, if you want to add in some additional filtering, you just add it in where appropriate into the Where(). Eg:
from c in db.tableC.Where(s => s.field1 == a.field1 && s.field3 == someVariable).DefaultIfEmpty()
Something like that :)

LinQ query with multiple tables and extracting data

I'm doing a query to inner join 4 tables and I have to extract data and convert into string and place it in an array for it.
var query = from a in context.as
join b in context.bs on a.prikey equals b.forkey
join c in context.cs on b.prikey equals c.forkey
join d in context.ds on c.prikey equals d.forkey
where b.gender == gender
where c.age == age
select new
{
a.Name,
a.Age,
b.Gender,
};
string[] results = new string[] {}
return results;
Normally, if a single table is involved
a as = plural of table a
as t = query.First()
string[] results = new string[] {t.Name, t.Age, t.Gender}
return results;
I'm missing a step to extract the data.
It depends what exactly you want to do with the data. Your code won't actually compile at the moment as it's trying to create an anonymous type with multiple properties all called "arg" but I'm assuming you've really got a more sensible query.
Ultimately, the fact that you're using multiple tables is irrelevant here - you're only getting a single result element at a time: the fact that each result element contains data from multiple tables is neither here nor there in terms of how you access it.
Now I've just noticed that you say you want to "extract data and convert into string". If possible, you should express that in your query. You may be able to do that at the database, or you may need to force the final part of the execution to execute locally, like this:
// Not executed yet!
var dbQuery = from a in context.a
join b in context.bs on a.prikey equals b.forkey
join c in context.cs on b.prikey equals c.forkey
join d in context.ds on c.prikey equals d.forkey
where ...
select { a.Age, b.Name, c.Salary, d.Location };
// This still won't talk to the database!
var finalQuery = dbQuery.AsEnumerable()
.Select(x => string.format("Age: {0}; Name: {1}; " +
"Salary: {2}; Location: {3}",
x.Age, x.Name, x.Salary,
x.Location));
// This will finally execute the query
string[] results = finalQuery.ToArray();
Now you don't have to do it like this - but it's probably the best approach, at least with the amount of information you've given us. If you can tell us more about how you're trying to combine the data from the multiple tables, we may be able to help you more.
EDIT: Okay, now you've given us a bit more information, I suspect you want:
var query = from a in context.a
join b in context.bs on a.prikey equals b.forkey
join c in context.cs on b.prikey equals c.forkey
join d in context.ds on c.prikey equals d.forkey
where ...
select new string[] { a.arg, b.arg, c.arg, d.arg };
string[] results = query.First();
I haven't tried creating arrays in LINQ to SQL... that may work, or you may need to go via an anonymous type and AsEnumerable as per the earlier part of my answer.
You should also think about what you want to happen if there are no results, or multiple results.
EDIT: Having seen the edited question, you really can treat multiple tables the same way as a single table. You'd use exactly the same code for handling the result, once it's been projected into an anonymous type:
var query = from a in context.as
join b in context.bs on a.prikey equals b.forkey
join c in context.cs on b.prikey equals c.forkey
join d in context.ds on c.prikey equals d.forkey
where ...
select new { a.Name, a.Age, b.Gender };
var result = query.First();
// Call ToString appropriately on each property, of course
string[] array = new string[] { result.Name, result.Age, result.Gender };
var query = from a in context.a
join b in context.bs on a.prikey equals b.forkey
join c in context.cs on b.prikey equals c.forkey
join d in context.ds on c.prikey equals d.forkey
where a.arg == arg
where b.arg == arg
where c.arg == arg
select new
{
allStrings = a.arg +
a.arg +
b.arg +
c.arg +
d.arg
};
string[] results = query.ToArray();

LINQ and joining on selects

I have the following SQL:
select o.tekst as Enhet,
coalesce(f.Antall,0) as AntallF,
coalesce(f.snitt,0) as SnittF,
coalesce(b.antall,0) as AntallB
from tblhandlingsplan hp
inner join tblorg o on hp.eierorgid = o.orgid
left outer join (select f.handlingsplanid, count(t.tiltakid) as Antall, coalesce(avg(convert(float,t.status)),0) as Snitt from tblhandlingsplanforbedring f left outer join tblhandlingsplantiltak t on f.forbedringsid = t.forbedringsid group by f.handlingsplanid) f on hp.handlingsplanid = f.handlingsplanid
left outer join (select b.handlingsplanid, count(b.bevaringsid) as Antall from tblhandlingsplanbevaring b group by b.handlingsplanid) b on hp.handlingsplanid = b.handlingsplanid
where utsendingsid = 1
Which works exactly how I want it... Now I'm trying to convert this to LINQ...
I have gotten this far
from h in TblHandlingsplans
join o in TblOrgs
on h.EierOrgID equals o.OrgID
join f in TblHandlingsplanForbedrings
on h.HandlingsplanID equals f.HandlingsplanID into f2
join b in TblHandlingsplanBevarings
on h.HandlingsplanID equals b.HandlingsplanID into b2
where h.UtsendingsID == 1
select new {
Enhet = o.Tekst,
AntallF = f2.Count(),
AntallB = b2.Count()
}
however now I'm stuck... I can't for the life of me figure out how to include the average part from the SQL solution... Any takers?
I'm thinking of shoving the whole thing into a SP and leave it with that...
var query1 = from a in DB.Table1
select new
{
Id = a.Id,
Average = a.B.Average()
};
var query2 = from b in DB.Table2
join c in query1 on b.Id equals c.Id
select c;
Just freehanded it, so it might not actually work, but is that the kind of thing you're trying to do? That would result in a single SQL query being created when query2 was used.

Categories