After some advice on how to do this nicely using lambda expressions.
What I want is to get a list of placements based on an agency. I want the placements where the placement.agency != agency but where placement.agencypersonnel contains any of the agency staff.
So where the placement is not for that agency, but there are staff from that agency involved in another agency's placement
I don't know how to query based on the second condition.
So something like:
// agency is being passed in
var agencySupervisors = agency.AgencyPersonnel;
return agency.Placements
.Where(p => p.Supervisors.Contains(agencySupervisors))
.Where(p => p.Agency != agency);
I get that Contains is supposed to refer to a single object rather than a collection - which is why its erroring.. but I'm not sure how to get it to check against all objects in the collection.
Have also tried Any
return agency.Placements
.Where(p => agencySupervisors.Any<PlacementSupervisor>(p.Supervisors))
.Where(p => p.Agency != agency);
So hopefully its just I'm using the wrong one!!
Another spanner in the works is trying to figure out how the placement supervisor and the agency personnel entities relate to one another.. I think its linked on AgencyPersonnelId = SupervisorId so I'm guessing that will also have to be factored into my expression.
Thanks!
Edit: How do I handle if the type of objects in the two list aren't the same - but I know that the Id will match. Do I have to write a comparer and somehow incorporate that into the expression?? ie. AgencyPersonnelId = SupervisorId
I have tried:
return placements
.Where(p => p.Supervisors.Any(supervisor => agencySupervisors.Any(ap => ap.AgencyPersonnelId == supervisor.SupervisorId)));
But it is giving me no results so it is obviously wrong.
Edit: Actually when I try to iterate through the placements in the returned collection I'm getting a null reference exception - so I'm not sure if its something to do with my expression or the way I'm returning the results.
You are close with Any & Contains - try both at once
return agency.Placements
.Where(p => agencySupervisors.Any(supervisor => p.Supervisors.Contains(supervisor))
.Where(p => p.Agency != agency);
I think you can do it with .Intersect also:
return agency.Placements
.Where(p => agencySupervisors.Intersect(p.Supervisors).Any()
&& p.Agency != agency);
Thanks everyone for the help - Because the objects were of different types I ended up having to do something a little different - but then found I was able to use their Ids for the comparison so the result was:
var agencySupervisors = (from ap in agency.AgencyPersonnel
where ap != null
select ap.AgencyPersonnelId).ToList();
return
(from p in m_PlacementRepository.Linq orderby p.PlacementId select p)
.Where(p => p.Agency != agency)
.Where(p => p.Supervisors != null && p.Supervisors.Any(s => agencySupervisors.Contains(s.SupervisorId)));
Plus as Mikael rightly pointed out I was starting with the wrong collection in the first place :)
Related
I'm wondering why my linq statement doesn't properly evaluate the null check on the Agency object which is a property of the Users model.
var invalidUsers = this.DbContext.Users.Where(p => p.Agency != null).ToList();
var invalidUsersList = invalidUsers.Where(p => p.Agency != null).ToList();
When I run the code above, the first line returns a list of all Users, regardless of the Agency object being null or not. However, the second line executes and properly filters the list and returns a list that is correctly returning Users where Agency is not null.
Chances are since this appears to be a foreign key table you need to include it first in LINQ so it can query against it.
So something like.
var invalidUsers = await this.DbContext.Users
.Include(p => p.Agency)
.Where(p => p.Agency != null)
.ToListAsync();
Give that a try and see if it helps.
I have a database scheme with 3 tables. One for requisitions, one for hospitals, and one joining the two (many-to-many relationship).
I'd like to list all requisitions in the database that are linked to a selected hospital.
This is what I have so far:
var valgtSykehus = Db.Sykehus.Where(n => n.Navn == sykehus).Single(); //this gives me a variable with my current hospital. I want to list all requistions that contains this.
var Rekvisisjoner = Db.Rekvisisjoner
.Where(r => r.Arkivert == true) //get only archived requsitions
.Include(p1 => p1.Sykehus) //include hospitals
.ToList() //this generates a list of -all- requisitions with the hospitals they are attached to.
.Where(x => x.Created > DateTime.Now.AddYears(-3)) /only go 3 years back
.Where(x => x.Sykehus.Contains(valgtSykehus)); //here is the problem. I want to discard all requisitions that does NOT contain the hospital in the valgtSykehus variable
Anyway, this gives me zero requistions, but if I skip the last line, I get all archived requistions.
x.Sykehus.Contains(valgtSykehus) executes in LINQ to Objects context (due to the intermediate ToList call) and most likely uses reference equality, which normally should work as soon as you use tracking queries.
Still, it's safer and also more efficient to do the whole thing with a single db query using Any condition with primitive key. Something like this:
var Rekvisisjoner = Db.Rekvisisjoner
.Include(r => r.Sykehus) //include hospitals
.Where(r => r.Arkivert == true) //get only archived requsitions the hospitals they are attached to.
.Where(r => r.Created > DateTime.Now.AddYears(-3)) /only go 3 years back
.Where(r => r.Sykehus.Any(s => s.Navn == sykehus));
If there is an issues with using DateTime.Now.AddYears(-3) inside the query, just put into variable outside of the query and use it inside.
var minDate = DateTime.Now.AddYears(-3);
var Rekvisisjoner =
// ...
.Where(r => r.Created > minDate)
//...
The issue may lie in the implementation of Contains. Contains has to check equality somehow. Anyway, if your valgtSykehus object is logically contained in x.Sykehus (i.e. has the same data), but not exactly the same object (i.e. the same reference), it's possible that Contains fails to find it, due to the default implementation of == in reference types (== is true, if the objects are exactly the same reference, false otherwise, even though all the data is the same).
You could try the following:
var Rekvisisjoner = Db.Rekvisisjoner
.Where(r => r.Arkivert == true)
.Include(p1 => p1.Sykehus)
.ToList()
.Where(x => x.Created > DateTime.Now.AddYears(-3))
.Where(x => x.Sykehus.Any(sh => sh.Id == valgtSykehus.Id));
If Id (or whatever your ID property is named) is a value field (most likely) this will return true whenever the ID of an Sykehus matches the ID of valgtSykehus.
Oh my.
I just realised that none of the archived requisitions contains any connections to the hospitals, as they apparently are removed one-by-one when the requisition is processed in the program.
I figured this out while trying to reverse the query, so thanks for that tip.
I have a LINQ query retrieving a list of , such as this:
var results = SearchContext.GetQueryable<Person>()
.Where(i => i.Enabled)
.Where(i => i.TemplateName == "Person")
.Random(6);
Each object of type "Person" has a "Location" field which is also a Glass mapped item, and hence has an ID; I would like to only select items whose Location has a specific ID.
How can I go about doing this in an efficient manner?
EDIT: I should probably clarify that I am unable to perform this comparison, efficiently or not. Because the GUID is an object and I cannot perform ToString in a LINQ query, I am unable to only pick the items whose Location item has a specific ID. Any clues on how this could be achieved?
EDIT 2: Adding the clause
.Where(i => i.Location.Id == this.Id)
Doesn't work, for... some reason, as I'm unable to debug what LINQ "sees". If I convert the other ID I'm comparing it against to string this way:
var theOtherID = this.Id.ToString("N");
Then it works with this LINQ line:
.Where(i => i["Location"].Contains(theOtherID))
I still have no idea why.
One approach is to include a separate property on Person that is ignored by Glass mapper, but can be used in searches:
[SitecoreIgnore]
[Sitecore.ContentSearch.IndexField("location")]
public Sitecore.Data.ID LocationID { get; set; }
You can use this in your search as follows:
Sitecore.Data.ID locationId = Sitecore.Data.ID.Parse(stringOrGuid);
var results = SearchContext.GetQueryable<Person>()
.Where(i => i.Enabled)
.Where(i => i.TemplateName == "Person")
.Where(i => i.LocationID == locationId)
.Random(6);
I think the efficiency of using multiple where clauses vs. conditionals is debatable. They will likely result in the same Lucene query being performed. I would prefer readability over optimization in this instance, but that's just me.
I can't think of a more efficient methods than using a simple where statement like in:
var results = SearchContext.GetQueryable<Person>()
.Where(i => i.Enabled && i.TemplateName == "Person" &&
i.Location != null && i.Location.Id == 1)
.Random(6);
Keep in mind that if you use the && statement instead of a where for each parameter, you reduce the complexity of the algorithm.
You could also use an Inverse Navigation Property on Location to a virtual ICollection<Person> and then be able to do this:
var results = SearchContext.GetQueryable<Location>()
.Where(i => i.Id == 1 && i.Persons.Where(p => p.Enabled && p.TemplateName == "Person").Any())
.Random(6);
The first option would still be the most efficient, because the second one uses sub-queries. But it is worth knowing you can do your search the other way.
I have got a bit of an issue and was wondering if there is a way to have my cake and eat it.
Currently I have a Repository and Query style pattern for how I am using Linq2Sql, however I have got one issue and I cannot see a nice way to solve it. Here is an example of the problem:
var someDataMapper = new SomeDataMapper();
var someDataQuery = new GetSomeDataQuery();
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(x => someDataMapper.Map(x));
return results.Where(x => x.SomeMappedColumn == "SomeType");
The main bits to pay attention to here are Mapper, Query, Repository and then the final where clause. I am doing this as part of a larger refactor, and we found that there were ALOT of similar queries which were getting slightly different result sets back but then mapping them the same way to a domain specific model. So take for example getting back a tbl_car and then mapping it to a Car object. So a mapper basically takes one type and spits out another, so exactly the same as what would normally happen in the select:
// Non mapped version
select(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
// Mapped version
select(x => carMapper.Map(x));
So the car mapper is more re-usable on all areas which do similar queries returning same end results but doing different bits along the way. However I keep getting the error saying that Map is not able to be converted to SQL, which is fine as I dont want it to be, however I understand that as it is in an expression tree it would try to convert it.
{"Method 'SomeData Map(SomeTable)' has no supported translation to SQL."}
Finally the object that is returned and mapped is passed further up the stack for other objects to use, which make use of Linq to SQL's composition abilities to add additional criteria to the query then finally ToList() or itterate on the data returned, however they filter based on the mapped model, not the original table model, which I believe is perfectly fine as answered in a previous question:
Linq2Sql point of retrieving data
So to sum it up, can I use my mapping pattern as shown without it trying to convert that single part to SQL?
Yes, you can. Put AsEnumerable() before the last Select:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.AsEnumerable()
.Select(x => someDataMapper.Map(x));
Please note, however, that the second Where - the one that operates on SomeMappedColumn - will now be executed in memory and not by the database. If this last where clause significantly reduces the result set this could be a problem.
An alternate approach would be to create a method that returns the expression tree of that mapping. Something like the following should work, as long as everything happening in the mapping is convertible to SQL.
Expression<Func<EntityType, Car>> GetCarMappingExpression()
{
return new Expression<Func<EntityType, Car>>(x => new Car
{
Id = x.Id,
Name = x.Name,
Owner = x.FirstName + x.Surname
});
}
Usage would be like this:
var results = SomeRepository.HybridQuery(someDataQuery)
.Where(x => x.SomeColumn == 1 || x.SomeColumn == 2)
.OrderByDescending(x => x.SomeOtherColumn)
.Select(GetCarMappingExpression());
I'm not sure if my title is correct, but linq should pull the right experts in to help the title and answer the question.
If I have a list of People, how do I return a list of PeopleWrappers minus "Dave" and "Jane"?
query looks like this right now:
List<Person> People = CreatListofPersons();
People.Select(t => new PeopleWrapper(t)).ToArray();
People.Where( x => x.Name!="Dave" && x.Name!="Jane")
.Select(t => new PeopleWrapper(t))
.ToArray();
LINQ has a list of extension methods that allow you to filter or project (which you already to with Select()). In your case you can use Where() to specify which elements you want to let pass, in your example all persons whose name is neither Dave nor Jane.
does it matter if "Where" comes before
"Select" or after?
You typically want to filter as soon as you can, otherwise you will have to iterate and/or project over items you don't want to have anyway.
Conceptually though, yes, you can put there where() filter later but in your case you are dealing with a PeopleWrapper after you project with Select() - since the Where() extension method is using this data type as input its condition I don't think it would make much sense - you would filter people wrappers, not persons.
return People.Where(p => p.Name != "Dave" && p.Name != "Jane");
You can use Where to get just the elements that match a condition:
People.Where(p => p.name != "Dave" && p.name != "Jane" ).Select(t => new PeopleWrapper(t)).ToArray()