Reputation: 1410
I have a linq select part, that I use quite often so I wondered, if there is a way to "reuse" it, i.e. put it in a variable of some sort.
My linq statement looks a bit like this:
var today = DateTime.Now;
var tmp = _context.Student
.Where(b => b.Semester > 10)
.Select(b => new {
BName = b.Name,
SuperList = b.Seminars.SelectMany(sem => sem.DateFrom <= today && ......) // this is a superfancy and long subquery that I wanna reuse
});
Since I have some other linq queries that will use exactly the same subquery, I tried to create a function that gives me this subquery part for reusing. But alas, it doesnt work, the query does not throw an error but it looks as if the subquery just was completely ignored:
var today = DateTime.Now;
var f1 = GetFancySubquery(today);
var tmp = _context.Student.Where(b => b.Semester > 10)
.Select(b => new {
BName = b.Name,
SuperList = f1.Invoke(b)
});
private Func<Student, List<SemesterSomethingDTO>> GetFancySubquery(DateTime tag)
{
Func<Student, List<SemesterSomethingDTO>> condition = s => s.Seminars.SelectMany(sem => ....).ToList();
return condition;
}
I feel like I'm close (or maybe not) - what am I doing wrong? Is what I'm trying to do even possible?
Upvotes: 0
Views: 2628
Reputation: 78
I feel like I'm close (or maybe not) - what am I doing wrong? Is what I'm trying to do even possible?
You were close indeed, and yes it's possible.
I would think the best approach for you case it to create an extension method for IEnumerable<Student> (the list you want to query), since you'll be re-using query in several parts, and that's why extension methods can help you keep things D.R.Y.
It would be something like this:
class Program
{
static void Main(string[] args)
{
var today = DateTime.Now;
List<Student> students = new List<Student>();
// Not quite the same code as yours since I don't have all the classes
// but it's the same idea
var tmp = students
.Where(b => b.Name == "Student 1")
.Select(b => new
{
BName = b.Name,
SuperList = b.Courses.Select(course => course.Semester == 2).ToList()
});
// This is exactly the same query using and extension method
var tmp2 = students.FancyQuery("Student 1", 2);
}
}
public static class LinqExtension
{
/// <summary>
/// Extension method to query an IEnumerable<Student> by name and course semester
/// </summary>
/// <param name="query">Type that the method extends</param>
/// <param name="name">Student name</param>
/// <param name="semester">Course semester</param>
/// <returns>IEnumerable<QuerySelectResult> after filtering by name and semester</student></returns>
public static List<QuerySelectResult> FancyQuery(this IEnumerable<Student> query, string name, int semester)
{
return query.Where(b => b.Name == name)
.Select(b => new QuerySelectResult
{
BName = b.Name,
SuperList = b.Courses.Where(course => course.Semester == 2).ToList()
}).ToList();
}
}
public class Student
{
public string Name { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public string Name { get; set; }
public int Semester { get; set; }
}
public class QuerySelectResult
{
public string BName { get; set; }
public List<Course> SuperList { get; set; }
}
Notes:
If something isn't clear, please do ask :)
Hope it's useful.
EDIT 1: Now the extension method doesn't have the whole query (Where and Select clauses).
Thank you very much! The problem here is: fancyQuery contains now the entire query. While I want only the subquery as reusable, the rest can look very different. so unfortunately this doesnt help me
how? it seems like I'm missing an important puzzlepiece here - how do I do it ?
class Program
{
static void Main(string[] args)
{
var today = DateTime.Now;
List<Student> students = new List<Student>();
// Not quite the same code as yours since I don't have all the classes
// but it's the same idea
var tmp = students
.Where(b => b.Name == "Student 1")
.Select(b => new
{
BName = b.Name,
SuperList = b.Courses.Select(course => course.Semester == 2).ToList()
});
// As you can see "students.Where(b => b.Name == "Student 1")" it's still and IEnumerable after the Where()
// clause is used, so the extension method can be used after it
var tmp2 = students.Where(b => b.Name == "Student 1").FancyQuery(2);
}
}
public static class LinqExtension
{
/// <summary>
/// Extension method to query an IEnumerable<Student> by name and course semester
/// </summary>
/// <param name="query">Type that the method extends</param>
/// <param name="name">Student name</param>
/// <param name="semester">Course semester</param>
/// <returns>IEnumerable<QuerySelectResult> after filtering by name and semester</student></returns>
public static List<QuerySelectResult> FancyQuery(this IEnumerable<Student> query, int semester)
{
// Took out the Where() clause from here, and left only the Select() one
return query.Select(b => new QuerySelectResult
{
BName = b.Name,
SuperList = b.Courses.Where(course => course.Semester == semester).ToList()
}).ToList();
}
}
EDIT 2: Extension method only for SuperList (IEnumerable<Course> with my classes)
but fancyQuery adds the entire select - which I do not want. maybe next time I do not need BName but wanna query something else - the only thing I need is SuperList
You can extend whatever type you want, just changed the the return and signature of the extension method. Now it's only used on SuperList, but can be used with any object of type IEnumerable<Course> (in this case)
static void Main(string[] args)
{
var today = DateTime.Now;
List<Student> students = new List<Student>();
// Not quite the same code as yours since I don't have all the classes
// but it's the same idea
var tmp = students
.Where(b => b.Name == "Student 1")
.Select(b => new
{
BName = b.Name,
SuperList = b.Courses.Select(course => course.Semester == 2).ToList()
});
// Now the extension method is used only on SuperList
var tmp2 = students
.Where(b => b.Name == "Student 1")
.Select(b => new
{
BName = b.Name,
SuperList = b.Courses.FancyQuery(2)
});
}
}
public static class LinqExtension
{
/// <summary>
/// Extension method to query an IEnumerable<Student> by name and course semester
/// </summary>
/// <param name="query">Type that the method extends</param>
/// <param name="name">Student name</param>
/// <param name="semester">Course semester</param>
/// <returns>IEnumerable<QuerySelectResult> after filtering by name and semester</student></returns>
public static List<Course> FancyQuery(this IEnumerable<Course> query, int semester)
{
// Took out the Where() clause from here, and left only the Select() one
return query.Where(course => course.Semester == semester).ToList();
}
}
Upvotes: 1
Reputation: 27282
Such helper functions needs expanding into resulting Expression Tree. It is not supported by vanilla EF any versions. I would suggest to use use LINQKit for such task:
public static class DTOExtensions
{
[Expandable(nameof(GetFancySubqueryImpl))]
public static List<SemesterSomethingDTO> GetFancySubquery(this Student student, DateTime tag)
{
throw new InvalidOperationException();
}
private static Expression<Student, DateTime, List<SemesterSomethingDTO>> GetFancySubqueryImpl()
{
return (student, tag) => student.Seminars.SelectMany(sem => ....).ToList();
}
}
And usage:
var today = DateTime.Now;
var tmp = _context.Student
.Where(b => b.Semester > 10)
.AsExpandable() // activating LINQKit
.Select(b => new {
BName = b.Name,
SuperList = b.GetFancySubquery(today)
});
Upvotes: 1
Reputation: 1629
It looks like you are missing the .Include statement to ensure the seminars data is loaded.
Does it work if you try:
_context.Student.Include(s => s.Seminars)... // continue your query here
You can read more about .Include here.
Also, you can declare your Func delegate as a variable instead of returning it from a function. Consider this example from Microsoft's documentation:
// Declare a Func variable and assign a lambda expression to the
// variable. The method takes a string and converts it to uppercase.
Func<string, string> selector = str => str.ToUpper();
// Create an array of strings.
string[] words = { "orange", "apple", "Article", "elephant" };
// Query the array and select strings according to the selector method.
IEnumerable<String> aWords = words.Select(selector);
Upvotes: -1
Reputation: 135
I don't have any idea about your entites. But you can try belong code
var today = DateTime.Now;
var tmp = _context.Student.Include(i=>i.SuperList).Where(b => b.Semester > 10)
.Select(b => new {
BName = b.Name,
SuperList = b.Seminars.Where(sem => sem.DateFrom <= today && ......)
});
Upvotes: 0