Sql to LINQ 'where in' query - c#

I'm trying to convert this SQL query to LINQ
select * from fichepfe where valid = 1 and id_fiche in ( select fiche_id from enseignant_fiche where id_ens = '*variable*');
This query can return multiple rows.
This is what I tried but I keep getting this error
Operator '==' cannot be applied to operands of type 'method group' and 'IQueryable'
What I tried:
var fiches = (from fiche in _context.Fichepfes where fiche.Valid == true && fiche.IdFiche ==
(from fens in _context.enseignant_fiche where IdEns == *variable*
select fens.ficheId )
select fiche ).ToList();
Thanks in advance.

This should work:
var fiches =
(from fiche in _context.Fichepfes where
fiche.Valid == true &&
_context.enseignant_fiche.Any(fens => fens.IdEns == *variable* && fens.ficheId == fiche.IdFiche)
).ToList();
However this may cause part of the query to be executed by the client, since LINQ-to-SQL may be unable to convert the .Any() call to a raw query.
A better way would be to use a join:
var fiches =
(from fens in _context.enseignant_fiche where fens.IdEns == ens
join fiche in _context.Fichepfes on fens.ficheId equals fiche.IdFiche
where fiche.Valid
select fiche.IdFiche).ToList();
The join can be done the other way around, too, but it's around 5 to 6 times slower on my computer.
var fiches =
(from fiche in _context.Fichepfes where fiche.Valid
join fens in _context.enseignant_fiche on fiche.IdFiche equals fens.ficheId
where fens.IdEns == ens
select fiche.IdFiche).ToList();

So you have a table of Fiches (FichePfes), and a table of EnseignantFiches (enseignant_fiche).
There seems to be a relation between Fiches and EnseignantFiches: every Fiche has (owns?) exactly one EnseignantFiche, namely the EnseignantFichethat the foreign keyIdFiche` refers to.
Furthermore, every Fiche has a Boolean property Valid; every EnseignantFiche has a (string?) property IdEns.
Requirement: Give me all Valid Fiches, that owns an EnseignantFiche with a value of IdEns that equals "variable"
var validFiches = dbContext.Fiches.Where(fiche => fiche.Valid);
var variableEnseignantFiches = dbContext.EnseignantFiches
.Where(enseignantFiche => enseignantFiche.IdEns == "*variable*";
var requestedFiches = validFiches.Join(
variableEnseignantFiches,
fiche => validFiche.IdFiche, // from every Fiche take the foreign key
enseignantFiche => enseignantFiche.IdFiche, // from every EnseignatFiche take primary key
(fiche, enseignantFiche) => new // when they match, make one new object
{
// Select the fiche properties that yo plan to use
FicheId = fiche.Id,
FicheName = fiche.Name,
...
// Select the EnseignantFiche properties that you plan to use:
EnseignantName = enseignantFiche.Name,
...
});
In words:
From the table of Fiches, keep only the Valid ones.
From the table of EnseignantFiches, keep only those with an IdEns equal to "Variable"
Join these two tables on Primary key equals Foreign key, and Select the properties that you plan to use.
Of course you can do this in one big LINQ statement. Because the query is not executed yet, this won't enhance process speed. It will surely deteriorate readability, testability and reusability.

Related

Is it safe to join a table twice in the same query?

I need to write some linq (linq-to-sql) for a search page that allows the user to search for cars and optionally include search criteria for the car's parts. The two tables are CAR and CAR_PARTS. Here is what I have so far:
var query = db.CAR;
//if the user provides a car name to search by, filter on car name (this works)
if(model.CarName != "")
{
query = from c in query
where c.Name == model.CarName
select c;
}
//if the user provides a car part name to filter on, join the CAR_PART table
if(model.CarPartName != "")
{
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartName == model.CarPartName
select c;
}
//if the user provides a car part code to filter on, join the CAR_PART table
if(model.CarPartCode != "")
{
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartCode == model.CarPartCode
select c;
}
If the user decides they want to search on both CarPartName and CarPartCode, this logic would result in the CAR_PART table being joined twice. This feels wrong to me, but is this the correct way to handle this?
How would you write this?
It's legal to do so, but whether it makes sense, depends on your datamodel and your desired outcome.
Generally your code does the following if partname and partcode are defined
Join the cars table with the parts table with partname as join condition
Join the result of the first join again with the parts table with partcode as join condition.
Thus, this is equal to a join with join condition car.partname = part.name and car.partcode = part.code. I don't know, whether this is your desired behaviour or not.
There are some cases to distinguish
Joining with AND condition
CASE 1.1: name and code of a part are keys in the parts table
In this case for each name and code are each unique in the parts table, thus for each name there is exactly one code. The double join is not necessary, and may even lead to wrong results, because
if selected name and code identify the same part, it's the first join will already get the desired results
if name and code identifiy different parts, your result will be empty because the condition cannot be fullfilled.
In that situation I would suggest to write is as follows
if (!string.IsNullOrEmpty(model.CarPartName)){
// your join on partname
} else if (!string.IsNullOrEmpty(model.CarPartCode)) {
// your join on partcode
}
CASE 1.2: name and code of a part are NOT keys in the parts table
In this case, neither name nor code may be unique, and for one name there may be different codes and vice versa. Here the double join is necessary and will only return results containing parts which match both, name and code
Joining with OR condition
If on the other hand you want your join condition to be like car.partname = part.name and car.partcode = part.code you have to consider the following cases
CASE 2.1 name and code are keys
Here applies the same as above in case 1.1
CASE 2.2 name and code are NOT keys
Here you can't use the stepwise approach, because the result of the first join will only contain cars, where the name matches. There may be parts where only the code condition matches, but they can never be included in the final result, if they are not contained in the result of the first match. So in this case, you will have to define your query something like this
if (!string.IsNullOrEmpty(model.CarPartName) && !string.IsNullOrEmpty(model.CarPartCode)) {
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartName == model.CarPartName || parts.PartCode == model.CarPartCode
select c;
} else if (!string.IsNullOrEmpty(model.CarPartName)) {
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartName == model.CarPartName
select c;
} else if (!string.IsNullOrEmpty(model.CarPartCode)) {
query = from c in query
join parts in db.CAR_PARTS on c.ID equals parts.CarID
where parts.PartCode == model.CarPartCode
select c;
}
What is wrong in there is actually with proper relations you don't need the join at all. Add that the behavior of LinqToSQL you can write that as:
var query = db.CAR
.Where( c =>
( string.IsNullOrEmpty(model.CarName)
|| c.Name == model.CarName ) &&
( string.IsNullOrEmpty(model.CarPartName)
|| c.Parts.Any( p => p.PartName == model.CarPartName )) &&
( string.IsNullOrEmpty(model.CarPartCode)
|| c.Parts.Any( p => p.PartCode == model.CarPartCode )));
Yours would work provided query is IQueryable (db.CAR.AsQueryable()). The two Linq approaches are similar but not the same. Depending on your real necessity yours might be the correct one or the wrong one. Yours would produce two inner joins, while this one simply create 2 exists check. Assume you have:
Car, Id:5, Name: Volvo
And parts like:
CarID:5, PartName:HeadLights, PartCode:1 ... other details
CarID:5, PartName:HeadLights, PartCode:2 ... other details
CarID:5, PartName:HeadLights, PartCode:3 ... other details
If user asks with model.CarName = "Volvo" and model.PartName = "HeadLights", you would get back the same Volvo 3 times. In second approach, you get back a single Volvo.
HTH
I feel more comfortable with fluent syntax, but I'm sure something similar to the following will work for you. I would check the fields in your model as part of a Select statement and then conditionally join using one field or the other. If neither are set, leave it null.
var query = db.CAR;
if (!string.IsNullOrWhitespace(model.CarName))
{
query = query.Where(car => car.Name == model.CarName);
}
var items = query.Select(car => new
{
Car = car, // maybe better to split this up into different fields, but I don't know what the car object looks like
// I assume your Car entity model has a navigation property to parts:
CarPart = !string.IsNullOrWhitespace(model.CarPartName)
? car.Parts.FirstOrDefault(part => part.PartName == model.CarPartName)
: !string.IsNullOrWhitespace(model.CarPartCode)
? car.Parts.FirstOrDefault(part => part.PartCode == model.CarPartCode)
: null
})
.ToList();
This does mean that the Code will be ignored if the Name is filled in. Reverse it if it needs to be the other way around. Or if you want to use both fields, you can put the string null checks in the Where clause.

Linq query for Where on the Joined table without needing join

Trying to get a linq query (or lambda syntax) for the following SQL which Selects all "Data" which in the joining table have an Attribute equal to "blob".
EXCEPT: without explictly using the Join, but the
select data.*
from data
join settings on data.DataID = settings.DataID
where settings.Attribute = 'blob'
Explicitly defining the join
from d in dbcontext.Data
join s in dbcontext.Settings on d.DataID equals s.DataID
where s.Attribute == "blob"
select d
but is there a way to use the context dbcontext.Data.Settings
like the following?
from d in dbcontext.Data
where d.Settings.Attribute == "blob"
select d
Settings is a collection Type, so things like .Contains, and .Where come to mind.
using .Contains, my understanding is i would need to pass in an object type
where d.Settings.Contains(new Settings(d.DataID, "blob", null))
but i dont care about the null (Value) matching, just column Settings
some table structures
Data
DataID
Name
Settings
DataID
Attribute
Value
As I understand, you have Settings collection navigation property, so instead of explicit join you could simply use it ("navigate"):
from d in dbcontext.Data
from s in d.Settings
where s.Attribute == "blob"
select d
Alternatively you could use Any extension method which in this case is more appropriate than Contains (although Contains can also be used, but needs to be combined with Select):
dbcontext.Data.Where(d => d.Settings.Any(s => s.Attribute == "blob"))
For completeness, here is the Contains version:
dbcontext.Data.Where(d => d.Settings.Select(s => s.Attribute).Contains("blob"))
If I understand your question correctly, you want to create a LINQ that will grab any DataID that has an attribute of of "Blah" that is stored in another table.
If so this may work.
var dataIDs = Setting.Where(entry => entry.Attribute == "Blah")
.Select(entry => entry.DataID); // gets all DataIDs that match the attribute
var data = Data.Where(entry => entry.DataID in dataIDs); // gets data info based on DataIDs.
It should work, but what you should do instead is do an left join somewhat like
select a.*
from data a
left join settings b
on a.DataID = b.DataID
where b.Attribute = 'blob'
but in LINQ. This query would allow you to fetch all the data for DataIDs that match attribute 'blob. I haven't done it in LINQ so if someone more familiar with left joins with linq could respond that might work better

Linq2SQL choose IDs where join returns negative

I am attempting to query for an ID from table that needs to be joined with a negative result. In other words choose all the ScacIDs where that ScacID is not present in the joined table ScacSetup. This query returns no result.
var tasksNotAssociated = from scac in db.Scacs
where !db.ScacSetupTasks.Any(s => s.ScacTaskID == taskID)
group scac by scac.ScacCode into scacNotAssociated
select scacNotAssociated.FirstOrDefault();
Great and yes my solution was to use the left join that was provided by my navigation property; ScacSetupTasks. Guess I should have posted my navigation property and tagged with Entity Framework. OK, here is what I found to work and is essentially a left join where leftside == null as suggested by MarcinJeraszek, thanks buddy.
var scacsNotAssociated = db.Scacs.Where(s => s.ScacSetupTasks.Count(sst => sst.ScacTaskID == taskID) == 0);

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;

Linq to Entities selecting properties from related objects

I have several tables that are properly linked and mapped through FKs / link tables.
ie: link_table -> 1 to many relationship to table_one and table_two -> many to many relationship to table_three
var results = db.link_table.Where(l => l.table_one.RandomProperty == "value");
The above obviously works but I want to be able to make a select like this:
var results = db.link_table.Where(l => l.table_one.RandomProperty == "value" && l.table_two.table_three.RandomProperty == "anothervalue");
This fails because it seems not possible to access the properties that belong to table_three. The SQL query I would like should resemble something like this (the WHERE part is the most important):
SELECT * FROM link_table
LEFT JOIN table_one ON table_one.assoc = link_table.assoc
LEFT JOIN table_two ON table_two.assoc = link_table.assoc
LEFT JOIN table_three ON table_two.assoc = table_three.assoc
WHERE table_one.RandomProperty = "value" AND table_three.RandomProperty = "AnotherValue"
How would I be able to access the "RandomProperty" of table_three through the relational mapping of Linq2Entities? Is it even possible in one single line of code and thus getting the result I want in one automatically generated SQL query?
First declare all your FK properties as virtual in all model classes.
And then use this code:-
var results = db.link_table.Where(l => l.table_one.RandomProperty == "value" && l.table_two.table_three.RandomProperty == "value").FirstOrDefault();
The in-depth answer is given to this question: linq to entities, a where in where clause? (inner where). It involves using the .Any() command to search through the related many-to-many table data.
i.e.:
var results = db.link_table.Where(l => l.table_one.RandomProperty == "value" && l.table_two.table_three.Any(t => t.RandomProperty == "value"));

Categories