1

A rather complicated object mapping I've inherited uses static Expression<Func<...>> in the .Select call (from my research these seem to be called "Expression Trees", but for brevity I'll refer to them as "Projections"). E.G. ProjectionItem.MainProjection is used to map results of a query on the FooBarObject table to instances of the ProjectionItem class:

// the query
_repo.Query<FooBarObject>()
    .Select(ProjectionItem.MainProjection);

// elsewhere...
public class ProjectionItem
{
    public int Foo { get; set; }
    public int Bar { get; set; }
    public int YaddaYadda { get; set; }

    public static Expression<Func<FooBarObject, ProjectionItem>> MainProjection
    {
        get
        {
            return x => new ProjectionItem
            {
                Foo = x.Foo,
                Bar = x.SomeOtherStuff.AndMoreStuff
                    .ThisDoesSomething(),
                YaddaYadda = x.YouGetTheDrill,
                // in reality there's like 80 more columns being mapped
            };
        }
    }
}

However, now we want another "Projection" for the same FooBarObject table that'd be NEARLY identical to MainProjection except for 3-4 columns. Is there a way to define the shared properties between these "Projections" in one place to cut down on code duplication? I am aware the most correct solution is probably to just... rewrite this whole thing as a normal query, but let's work under the assumption that in this scenario, I just can't right now.

I tried adding a constructor to the ProjectionItem class that would house all shared logic between the "Projections":

public class ProjectionItem
{
    public int Foo { get; set; }
    public int Bar { get; set; }
    public int YaddaYadda { get; set; }

    public ProjectionItem(FooBarObject x)
    {
        Foo = x.Foo;
        Bar = x.SomeOtherStuff.AndMoreStuff
            .ThisDoesSomething();
    }

    public static Expression<Func<FooBarObject, ProjectionItem>> MainProjection
    {
        get
        {
            return x => new ProjectionItem(x)
            {
                YaddaYadda = x.YouGetTheDrill,
            };
        }
    }
    
    public static Expression<Func<FooBarObject, ProjectionItem>> NewProjection
    {
        get
        {
            return x => new ProjectionItem(x)
            {
                YaddaYadda = x.YouGetTheDrill + 1,
            };
        }
    }
}

But that attempt resulted in this exception:

System.NotSupportedException: 'Only parameterless constructors and initializers are supported in LINQ to Entities.'

NOTE: this project is still on .NET Framework 4.6 and is using Entity Framework for its database interactions

4
  • 2
    Those aren't mappings, they're an attempt to create pre-built projections using expressions. Not expression trees. An Expression tree is the construct created by the compiler, or by your own code if you try to dynamically create an expression at runtime. If .ThisDoesSomething() isn't something that maps to SQL, those projections can only run on the client, once the objects are loaded. There's no "inheritance" here, especially since those are static methods. You're asking if one static method can "inherit" from another static method - that's not possible. Commented May 28 at 15:00
  • 1
    You may want to look at AutoMapper's Queryable Extensions. What you describe isn't mapping in the ORM sense, it's mapping between DTOs. AutoMapper generates automatic mappings based on conventions (you get rid of Foo=x.Foo or CustomerName=x.Customer.Name). You can also create more complex mappings. With the Queryable extensions those mappings can be applied to any IQueryable<> and in the case of EF, alter the generated Select clauses. You still need to ensure the mappings can be translated to SQL. Commented May 28 at 15:13
  • @PanagiotisKanavos >You're asking if one static method can "inherit" from another static method - that's not possible. VERY Good point. Thanks for the explanation! Commented May 28 at 15:53
  • 1
    You should look at LINQKit, a library that makes generating expressions dynamically and combining subexpressions a lot easier. Commented May 29 at 7:27

1 Answer 1

0

If you pass around IQueryable instead of Expression, then you can use an intermediate projection which takes the columns you need, then consoldiate the rest of the projection into a third function.

private static IQueryable<ProjectionItem> ProjectionCore(IQueryable<Intermediate> query)
{
    return query.Select(x => new ProjectionItem
    {
        Foo = x.FooBar.Foo,
        Bar = x.FooBar.SomeOtherStuff.AndMoreStuff
            .ThisDoesSomething(),
        YaddaYadda = x.YaddaYadda,
        // in reality there's like 80 more columns being mapped
    }
}

public static IQueryable<ProjectionItem> MainProjection(IQueryable<FooBarObject> query)
{
    return ProjectionCore(query.Select(x => new Intermediate
    {
        FooBar = x,
        YaddaYadda = x.YouGetTheDrill,
    }));
}

public static IQueryable<ProjectionItem> NewProjection(IQueryable<FooBarObject> query)
{
    return ProjectionCore(query.Select(x => new Intermediate
    {
        FooBar = x,
        YaddaYadda = x.YouGetTheDrill + 1,
    }));
}

private class Intermediate
{
    public FooBarObject FooBar { get; set; }
    public int YaddaYadda { get; set; }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Interesting! I sadly can't use records due to being stuck on pre-C#9 (love ya framework) but I guess a class would suffice for that purpose. And I assume I would just pass the query call itself as the parameter of the XyzProjection method, e.g. ProjectionItem.MainProjection(_repo.Query<FooBarObject>()), right? OH and unrelated, but i assume record class is just a typo? or is that actually its own thing?
Yes correct, that's how you would pass it around. Typo fixed it all now

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.