简体   繁体   中英

Saved projection expression for re-use in different linq expressions with two source objects

This is how I would usually save a projection expression to re-use for my DTO's:

public class MyUserDTO
{
    public string Forename { get; set; }
    public string Surname { get; set; }

    public static Expression<Func<MyUserDTO, MyUserDBObject>> FromDBUserProjection = u => new MyUserDTO() { Forename = u.Forename, Surname = u.Surname };

    public static IQueryable<MyUserDTO> FromDBUser(IQueryable<MyUserDBObject> dbRecords)
    {
        return dbRecords.Select(FromDBUserProjection);
    }
}

This is a simple DTO with the projection expression necessary to project from a particular database object.

Whenever I want to query this data from the database, I can use it like this so that I dont have to write the projection every time:

IQueryable<MyUserDTO> myUsers = MyUserDTO.FromDBUser(context.MyUserDBObjects.Where(u => u.Forename = "Bob"));

In this way I separate data mapping from my database queries and ensures that mapping is consistent across different queries. This usually works fine for me, but I now have a situation where I need to project from two different database objects eg something like this:

public class MyCompositeDTO
{
    public string User1Forename { get; set; }
    public string User1Surname { get; set; }
    public string User2Forename { get; set; }
    public string User2Surname { get; set; }
}

//simplified example to demonstrate my question
IQueryable<MyCompositeDTO> matchingUsers = from u1 in context.MyUserDBObjects
                                           join u2 in context.MyUserDBObjects on u1.Surname equals u2.Surname
                                           select new MyCompositeDTO()
                                           {
                                               User1Forename = u1.Forename,
                                               User1Surname = u1.Surname,
                                               User2Forename = u2.Forename,
                                               User2Surname = u2.Surname
                                           };

In the above example I can easily create the projection within the original linq query as I have both u1 and u2 available at that point. But is there some way I can separate the projection into a different function as I have done in the first example and keep it as an IQueryable? There isn't a foreign key linking the two source records.

I am not as familiar with the query syntax so I will have to use the method syntax. In fact, I am not sure this can even be done with the query syntax; maybe somebody else can say for sure.

Given this expression:

public static readonly Expression<Func<MyUserDBObjects, MyUserDBObjects, MyCompositeDTO>> CompositeDtoProject = 
    (u1, u2) => 
        new MyCompositeDTO()
        {
            User1Forename = u1.Forename,
            User1Surname = u1.Surname,
            User2Forename = u2.Forename,
            User2Surname = u2.Surname
        };

You could do this:

IQueryable<MyCompositeDTO> matchingUsers =
    MyUserDBObjects
    .Join(
        MyUserDBObjects, 
        u1 => u1.Surname, 
        u2 => u2.Surname, 
        CompositeDtoProject);

Opinion Below Here:

I don't normally offer opinions or alternatives that people never asked for instead of answering the question because, well, I don't care for it when people do that to my questions. But since I have offered a solution to the question that was asked maybe I can get away with offering my unsolicited opinion. To me, what you have going on is confusing. I think the same thing could be accomplished in a more straightforward way. Consider this:

public class MyUserDTO
{
    public string Forename { get; set; }
    public string Surname { get; set; }
}

public static class MyUserDtoExtensions
{
    public static IQueryable<MyUserDTO> AsDto(this IQueryable<MyUserDBObject> source)
    {
        return 
            source
            .Select(x => new MyUserDTO()
                {
                    Forename = u.Forename,
                    Surname = u.Surname
                }
    }
}

Then instead of this

IQueryable<MyUserDTO> myUsers = MyUserDTO.FromDBUser(context.MyUserDBObjects.Where(u => u.Forename = "Bob"));

You could write this:

IQueryable<MyUserDto> myUser =
    context
    .MyUserDBObjects
    .Where(u => u.Forename = "Bob")
    .AsDto();

Which reads easier. Separating the projection out into a separate extensions class will also allow you to add additional features more easily. For instance, if you find yourself searching on the forename often you could add another extension method like this:

public static IQueryable<MyUserDto> WhereForenameIs(this IQueryable<MyUserDto> source, string name)
{
    return
        source
        .Where(u => u.Forename = name);
}

and then do this:

IQueryable<MyUserDto> myUser =
    context
    .MyUserDBObjects
    .AsDto()
    .WhereForenameIs("Bob");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM