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

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.

Related

Sql to LINQ 'where in' query

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.

Linq join on two values

Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for

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

Counting in a Linq Query

I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (One to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
select new {c.ClassID, c.Name, (from o in orders where o.classId == c.ClassId select o).Count()}
Is this possible for you? Best I can do without knowing more of the arch.
If the relationships are as you describe:
var foo = db.Class.Where(c=>c.Phylum.PhylumID == "something")
.Select(x=> new { ClassID = x.ClassID,
ClassName = x.Name,
NumOrders= x.Order.Count})
.ToList();
Side question: why are you joining those entities? Shouldn't they naturally be FK'd, thereby not requiring an explicit join?

Trying to create some dynamic linq

I'm trying to create a linq query based on some dynamic/optional arguments passed into a method.
User [Table] -> zero to many -> Vehicles [Table]
User [Table] -> zero to many -> Pets
So we want all users (including any vechile and/or pet info). Optional filters are
Vehicle numberplate
Pet name
Because the vehicle and pet tables are zero-to-many, i usually have outer joins between the user table and the vehicle|pet table.
To speed up the query, i was trying to create the dynamic linq and if we have an optional argument provided, redfine the outer join to an inner join.
(The context diagram will have the two tables linked as an outer join by default.)
Can this be done?
I'm also not sure if this SO post can help me, either.
I think you are heading in the wrong direction. You can easily use the fact that LINQ queries are composable here.
First, you would always use the outer join, and get all users with the appropriate vehicles and pets:
// Get all the users.
IQueryable<User> users = dbContext.Users;
Then you would add the filters if necessary:
// If a filter on the pet name is required, filter.
if (!string.IsNullOrEmpty(petNameFilter))
{
// Filter on pet name.
users = users.Where(u => u.Pets.Where(
p => p.Name == petNameFilter).Any());
}
// Add a filter on the license plate number.
if (!string.IsNullOrEmpty(licensePlateFilter))
{
// Filter on the license plate.
users = users.Where(
u => u.Cars.Where(c => c.LicensePlace == licensePlateFilter).Any());
}
Note that this will not filter out the pets or cars that don't meet the filter, as it is simply looking for the users that have pets with that name, or cars with that plate.
If you are trying to change tables or joins of a LINQ to SQL query at runtime you need to do that with reflection. LINQ expressions are not special; same as working with any other object call - you can change the value of properties and variables at runtime, but choosing which properties to change or which methods to call requires reflecting.
I would add to that by pointing out dynamically creating LINQ expressions via reflection is probably a little silly for most (all?) cases, since under the hood the expression is essentially reflected back into SQL statements. Might as well write the SQL yourself if you are doing it on-the-fly. The point of LINQ is to abstract the data source from the developer, not the end-user.
This is how I do what you are asking...
var results = u from dc.Users
join veh from dc.vehicles on u.userId equals v.userId into vtemp from v in vtemp.DefaultIfEmpty()
join pet from dc.pets on u.userId equals p.userId into ptemp from p in ptemp.DefaultItEmpty()
select new { user = u, vehicle = v, pet = p };
if ( !string.IsNullOrEmpty(petName) )
{
results = results.Where(r => r.pet.PetName == petName);
}
if ( !string.IsNullOrEmpty(licNum) )
{
results = results.Where(r => r.vehicle.LicNum == licNum);
}

Categories