How can I compose the result object of a linq to sql query to maximize code reuse?

226 Views Asked by At

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:

  1. Using the User object as a constructor parameter causes every field from the User 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.
  2. Navigation properties cannot be used within the constructor, so the Supervisor field cannot be set in the constructor. I COULD use linq2db's LoadWith 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?

0

There are 0 best solutions below