Setup
I am using a linq-to-sql query (using Linq2db) to perform a search.
Depending on which part of the application the user runs this search from, the result will have certain additional fields populated. The result object thus contains some properties that are common between parts, and some that are unique.
class UserSearchResult
{
// always populated
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Supervisor { get; set; }
// only populated in user maintenance
public string RoleName { get; set; }
// only populated in user scheduling
public int PTOAccrued { get; set; }
public int ScheduleId { get; set; }
public string ScheduleName { get; set; }
}
The columns that are specific to one part of the application are expensive to query. As such, I am currently a different query for each part:
// query for maintenance area
IQueryable<UserSearchResult> UserMaintenanceResultQry(IQueryable<User> filteredUsers) =>
from user in filteredUsers
join utor in db.UserToRole on user.UserId equals utor.UserId
select new UserSearchResult()
{
UserId = user.UserId,
FirstName = user.FirstName,
LastName = user.LastName,
Supervisor = $"{user.Supervisor.FirstName} {user.Supervisor.LastName} ({user.SupervisorId})",
RoleName = utor.Role.RoleName // uses navigation property
};
// query for scheduling area
IQueryable<UserSearchResult> UserSchedulingResultQry(IQueryable<User> filteredUsers) =>
from user in filteredUsers
join utos in db.UserToSchedule on user.UserId equals utos.UserId
join pto in db.PTO on user.UserId equals pto.UserId
select new UserSearchResult()
{
UserId = user.UserId,
FirstName = user.FirstName,
LastName = user.LastName,
Supervisor = $"{user.Supervisor.FirstName} {user.Supervisor.LastName} ({user.SupervisorId})",
PTOAccured = pto.PTOAccrued,
ScheduleId = utos.ScheduleId,
ScheduleName = utos.Schedule.ScheduleName
};
This is, of course, example code. There are lots of properties (both common and unique) and the joins are often complex and expensive.
My Question
In my real code, there are about 20 common properties and 3 areas. That means I duplicate the exact same common property-setting code in 3 different places. I would like a way to eliminate this duplication.
What I've Tried
One method that actually works is to create a constructor for UserSearchResult
:
// constructor
public UserSearchResult(User user)
{
UserId = user.UserId;
FirstName = user.FirstName;
// ...etc
}
// and then in the query ...
from user in filteredUsers
join utor in db.UserToRole on user.UserId equals utor.UserId
select new UserSearchResult(user)
{
// perfect! Now I can just set the unique properties!
RoleName = utor.Role.RoleName // uses navigation property
};
There are two major problems with this approach:
- Using the
User
object as a constructor parameter causes every field from theUser
table to be queried, even if it's not used. This query often 10s of thousands of results, so pulling (a lot of) additional columns isn't acceptable. - Navigation properties cannot be used within the constructor, so the
Supervisor
field cannot be set in the constructor. I COULD use linq2db'sLoadWith
method to load the navigation objects, but that in turn would load the entire navigation object instead of just the properties needed.
Conclusion
Is there any way to compose my queries to avoid needing to repeat the common properties that need to be set?