Dynamic LINQ query nested object - c#

I have a list of objects (say family), and each object contains a list of other non-value type object (say child). I would like to query this list and specify the where clause dynamically (during run-time).
var fselected = from f in families
from c in f.Children
where (f.FamilyAge > 15 && c.Age > 13)
select f;
The closest thing I found that would do that is Dynamic LINQ on NuGet, but beyond the simple where clause on the top level object, I can't find any examples on how to do above statement.
The only solution I can think of is to split into separate where clause for C and for F, run the c query first, then run F query on resultant data set...

don't use strings to let your users create dynamic queries on your database, that will leave you vulnerable to sql injection. Instead, expose nullable parameters to your users
public Family GetFamily(int? familyAge, int? age)
{
var families = GetAllFamilies();
if(familyAge.HasValue)
families = families.Where(x => x.familyAge = familyAge.value);
if(age.HasValue)
families = families.Where(x => x.age = age.value);
return familes.ToList();
}
Update
Despite the problems of injections when using strings to let your users query your db, you can use the Dynamic Linq Library to pass on a string as a query. But I do advise against using this.

Related

C# LINQ .Contains returns empty?

For a school project I need to filter students who have signed up for multiple courses at the same timeblock. Instead of querying the DB via procedures/views I want to use LINQ to filter it in memory for learning purposes.
Everything seems alright according to the debugger however the result of my linq query is 0 and I can't figure out how.
Here's the code:
foreach (Timeblock tb in ctx.Timeblocks)
{
List<Student> doublestudents = new List<Student>();
//Get the schedules matching the timeblock.
Schedule[] schedules = (from sched in ctx.Schedules
where sched.Timeblock.Id == tb.Id
select sched).ToArray();
/\/\/\Gives me 2 schedules matching that timeblock.
if (schedules.Count() > 1)
{
doublestudents = (from s in ctx.Students
where s.Courses.Contains(schedules[0].Course) && s.Courses.Contains(schedules[1].Course)
select s).ToList();
Console.WriteLine(doublestudents.Count); <<< count results in 0 students.
}
}
While debugging it seems everything should work alright.
Each student has a List and each Course hsa a List
schedules[0].Course has Id 1
schedules[0].Course has Id 6
The student with Id 14 has both these courses in it's list.
Still the linq query does not return this student. Can this be because it's not the same reference of course it wont find a match at the .Contains()?
It's driving me totally crazy since every way I try this it wont return any results while there are matches...
You are comparing on Course which is a reference type. This means the objects are pointing to locations in memory rather than the actual values of the Course object itself, so you will never get a match because the courses of the student and the courses from the timeblock query are all held in different areas of memory.
You should instead use a value type for the comparison, like the course ID. Value types are the actual data itself so using something like int (for integer) will let the actual numerical values be compared. Two different int variables set to the same number will result in an equality.
You can also revise the comparison to accept any number of courses instead of just two so that it's much more flexible to use.
if (schedules.Count() > 1)
{
var scheduleCourseIds = schedules.Select(sch => sch.Course.Id).ToList();
doublestudents = (from s in ctx.Students
let studentCourseIds = s.Courses.Select(c => c.Id)
where !scheduleCourseIds.Except(studentCourseIds).Any()
select s).ToList();
Console.WriteLine(doublestudents.Count);
}
Some notes:
Compare the Course IDs (assuming these are unique and what you use to match them in the database) so that you're comparing value types and will get a match.
Use the let keyword in Linq to create temporary variables you can use in the query and make everything more readable.
Use the logic for one set containing all the elements of another set (found here) so you can have any number of duplicated courses to match against.
The problem is that your schedule[0].Course object and the s.Courses, from the new query, are completely different.
you may use the element's key to evaluate your equality condition/expression, as:
if (schedules.Count() > 1)
{
doublestudents = (from s in ctx.Students
where s.Courses.Any(x=> x.Key == schedules[0].Course.Key) && s.Courses.Any(x=> x.Key == schedules[1].Course.Key)
select s).ToList();
Console.WriteLine(doublestudents.Count); <<< count results in 0 students.
}
}
In order to achieve this you will need to include
using System.Linq
As you have guessed, this is probably related to reference equality. Here is a quick fix:
doublestudents =
(from s in ctx.Students
where s.Courses.Any(c => c.Id == schedules[0].Course.Id) &&
s.Courses.Any(c => c.Id == schedules[1].Course.Id)
select s).ToList();
Please note that I am assuming that the Course class has a property called Id which is the primary key. Replace it as needed.
Please note that this code assumes that there are two schedules. You need to work on the code to make it work for any number of schedules.
Another approach is to override the Equals and GetHashCode methods on the Course class so that objects of this type are compared based on their values (the values of their properties, possibly the ID property alone?).

Join vs Navigation property for sub lists in Entity Framework

I have a sql statement like this:
DECLARE #destinations table(destinationId int)
INSERT INTO #destinations
VALUES (414),(416)
SELECT *
FROM GroupOrder grp (NOLOCK)
JOIN DestinationGroupItem destItem (NOLOCK)
ON destItem.GroupOrderId = grp.GroupOrderId
JOIN #destinations dests
ON destItem.DestinationId = dests.destinationId
WHERE OrderId = 5662
I am using entity framework and I am having a hard time getting this query into Linq. (The only reason I wrote the query above was to help me conceptualize what I was looking for.)
I have an IQueryable of GroupOrder entities and a List of integers that are my destinations.
After looking at this I realize that I can probably just do two joins (like my SQL query) and get to what I want.
But it seems a bit odd to do that because a GroupOrder object already has a list of DestinationGroupItem objects on it.
I am a bit confused how to use the Navigation property on the GroupOrder when I have an IQueryable listing of GroupOrders.
Also, if possible, I would like to do this in one trip to the database. (I think I could do a few foreach loops to get this done, but it would not be as efficient as a single IQueryable run to the database.)
NOTE: I prefer fluent linq syntax over the query linq syntax. But beggars can't be choosers so I will take whatever I can get.
If you already have the DestinationGroupItem as a Navigation-property, then you already have your SQL-JOIN equivalent - example. Load the related entities with Include. Use List's Contains extension method to see if the desired DestinationId(s) is(are) hit:
var destinations = new List<int> { 414, 416 };
var query = from order in GroupOrder.Include(o => o.DestinationGroupItem) // this is the join via the navigation property
where order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId)
select order;
// OR
var query = dataContext.GroupOrder
.Include(o => o.DestinationGroupItem)
.Where(order => order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId));

LINQ where db table does not contain in memory list elements

I have seen these StackOverflow Answers but they do not produce the same results when the filtering list is in memory.
I have a list of Ids. I want to remove any IDs that exists in the database, so that I am left with a list of IDs that need to be added. In other words, I need to perform a where not in SQL query, using Linq-To-Entities. The problem is, instead of producing that SQL, these methods each produce a SQL query per list item.
var providerIds = new [] {"1130", "1", "16"};
Method 1:
var missingProviders = (from provider in providerIds
where !JobProviders.Any(p => p.JobProviderID == provider)
select provider).ToList();
Method 2:
var missingProviders = (from provider in providerIds
where !(from p in JobProviders select p.JobProviderID).Contains(provider)
select provider).ToList();
Is there a way to structure the LINQ query such that it produces the intended not in form, or are these the only solutions?
What about something like
var providersInDb = (from provider in JobProviders
where providerIds.Contains(provider.JobProviderID)
select provider.JobProviderID).ToList();
var missingProviders = providerIds.Where(p => !providersInDb.Contains(p))
Tricky. I don't have my tools in front of me, so I don't know how this will pan out exactly.
var dbProviderIds = JobProviders.Select(p => p.JobProviderId);
var allProviders = dbProviderIds.Union(providerIds).Distinct();
var missing = allProviders.Except(dbProviderIds);
On the DB, get all the provider Ids, then combine that with the in-memory ones. Then remove the ones that are known on the database.

Use LINQ-to-SQL to return an object that has child objects filtered

I have a MembershipGroups table that is associated with a child Members table. The Members table has a Status column which can be set to Active or Inactive.
I want to select all MembershipGroups and only their active Members
As an example,
MembershipGroups
ID----Title
1-----Group #1
2-----Group #2
Members
MembershipGroupID-Name--Status
1-------------------------John----Active
1-------------------------Sally----Inactive
1-------------------------David---Inactive
I'm trying to create a query that looks something like the following (which doesn't currently work):
var query = from mg in db.MembershipGroups
where mg.Members.Status = "Active"
select mg
The result for this example should return a MembershipGroup of ID#1 with only one child Member entity
How can use LINQ-to-SQL to select a parent object that filters on child objects? If I were using straight T-SQL then this would be a simple join with a where clause but it seems to be much more difficult to do using LINQ-to-SQL.
Edit - Updated answer to return the MemberShipGroup object
var query = (from mg in db.MembershipGroups
join m in db.Members.Where(mem => mem.Status == "Active")
on mg.ID equals m.MembershipGroups into members
select new
{
MembershipGroup = mg,
Members = members
}).AsEnumerable()
.Select(m => new MembershipGroup
{
ID = m.MembershipGroup.ID,
Title = m.MembershipGroup.Title,
Members = m.Members
});
In LINQ to SQL, you can use the AssociateWith method on the DataLoadOptions to set your child filter at the context level.
DataLoadOptions opt = new DataLoadOptions();
opt.AssociateWith<Member>(m => m.Status == "Active");
db.LoadOptions = opt;
With this in place, you can simply return your member groups (or filter them for the active ones using where mg.Any(group => group.Members.Status == "Active"). Then when you try to drill into the Members of that group, only the Active ones will be returned due to the LoadOptions.
See also http://msdn.microsoft.com/en-us/library/system.data.linq.dataloadoptions.associatewith.aspx .
One word of warning, once you set the LoadOptions on a context instance, you can not change it. You may want to use a customized context to use this option.
As an alternative, you could use LINQ to SQL's inheritance model to create an ActiveMember type using the Status column as your discriminator and then create an association between the MemberGroups and ActiveMembers types. This would be the approach you would need to use to model this with the Entity Framework if you though about going that route as well as EF doesn't support the concept of the LoadOptions.
Make sure you are including the child objects you are trying to filter on, inside the query.
E.g.
var query = db.MembershipGroups
.Include("Members")
.Where(m => m.Members.Status == "Active");

Sorting Anonymous Types

I have a question of how to sort an anonymous type.
Using Linq2SQL I have the following query, which returns a list of submissions:
var submissions = EventSubmissions
.Where(s => s.EventId.Equals(eventId));
Consider the following interface (somewhat simplyfied):
public interface IQuerySorter
{
IOrderedQueryable Sort(IQueryable query);
IOrderedQueryable<T> Sort<T, U>(IQueryable<T> query, Expression<Func<T,U>> selector);
...
}
Using this interface allows me to implement a number of 'sorters', e.g. on Date, Rating or whether or not a submission has been nominated (for voting).
sortedQuery = sorter.Sort(submissions)
So far so good. A submission can be made "votable". I get the number of votes a nominated submission may have using the following query:
var withVoteCount = submissions
.Select(s => new {NumberOfVotes = s.Votes.Count(), Submission = s});
I would like to sort this new query by NumberOfVotes using my "general" sorter class, but run into the problem that the anonymous type/member does not seem to live outside the repository-method, hence I am unable to sort on it.
Any input would be greatly appreciated.
I would recommend creating a view or stored procedure using Group By and Count to get the same results as:
var withVoteCount = submissions
.Select(s => new {NumberOfVotes = s.Votes.Count(), Submission = s});
By using SQL you can tell it to order by Count of votes. Something like this (rough example):
select submissions.ID, submissions.Title, count(votes.ID) as NumberOfVotes
from submissions inner join votes on submissions.id = votes.submissionid
group by submissions.ID, submissions.Title
order by NumberOfVotes desc
After creating the sql view or stored procedure in your SQL server db, you can just drop it in the designer in Visual Studio and use it as regular class or function.

Categories

Resources