2

I have 2 API endpoints which have similarities so I am trying to reuse some similar code. I have an EF Core method syntax to retrieve data and this query is used in both.

Rather than repeat it in both, as the Select()s are identical, I am trying to put it into a method.

This is one of the queries

var exhibitors = await Db.CompanyDescriptions
    .Join(Db.Companies, cd => cd.CompanyId, c => c.CompanyID, (cd, c) => new { cd, c })
    .Where(q => uniques.Any(id => id == q.cd.CompanyId)).Where(q => q.cd.EventId == eventId)
    .Select(s => new ExhibitorModel()
    {
        Id = s.cd.Id,
        EventId = s.cd.EventId,
    }).ToListAsync(); 

Snipped for brevity, but I have this same code in another method (mostly) and then the methods go separate ways in terms of the unit of work, so I am trying to do something like this:

private ExhibitorModel MarshallExhibitorModel(CompanyDescriptions cd, Company c)
{
    return new ExhibitorModel()
    {
        Id = cd.Id,
        EventId = cd.EventId,
    };
}

And then be able to use that in both Select()s, but I am unsure of how to form that syntax.

In this line

.Select(s => new ExhibitorModel()

How can I say call MarshallExhibitorModel and pass in s.companydescription and s.company ?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
andrewb
  • 2,995
  • 7
  • 54
  • 95
  • The method needs to be called client side (as it can't be converted to SQL). For an example of doing this, see [Using Function in Select Clause of Entity Framework Query](https://stackoverflow.com/a/6430213/1364358) – Tone Oct 16 '20 at 04:11
  • Is there a good reason why you aren't using a navigation property between Company & CompanyDescriptions? Having an anonymous type in the query complicates the answer. – Jeremy Lakeman Oct 16 '20 at 04:40

3 Answers3

3

Here you have to do expression expanding. It means that you have to preprocess Expression Tree before feeding LINQ Provider with the query. Easiest way to do that is using LINQKit

Create function which returns Lambda Expression with the same parameters types:

public static Expression<Func<CompanyDescriptions, Company, ExhibitorModel>> MarshallExhibitorModel()
{
    return (cd, c) => ExhibitorModel()
    {
        Id = cd.Id,
        EventId = cd.EventId,
    };
}

And then use this function in your query. Don't forget to call AsExpandable() at least once in the query.

var exhibitors = await Db.CompanyDescriptions
    .Join(Db.Companies, cd => cd.CompanyId, c => c.CompanyID, (cd, c) => new { cd, c })
    .Where(q => uniques.Any(id => id == q.cd.CompanyId)).Where(q => q.cd.EventId == eventId)
    .AsExpandable() // important
    .Select(s => MarshallExhibitorModel().Invoke(s.cd, s.c))
    .ToListAsync();

Even better you can use such function in different situations:

    ...
    .AsExpandable() // important
    .Select(s => new SomeDTO 
       { 
          Exhibitor = MarshallExhibitorModel().Invoke(s.cd, s.c))
       }
    .ToListAsync();

This approach also can be used for generating complex reusable predicates which depend on parameters.

Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32
1

First, including the anonymous type (cd, c) => new { cd, c } would make code-reuse almost impossible. It looks like you only reference cd.Id, cd.CompanyId and cd.EventId. You could define an explicit type with these fields. Since you're only referring to CompanyDescriptions, it doesn't look like you need the join at all in this case. But even if you did, I am wondering why you aren't using navigation properties.

Ignoring that, you are then calling IQueryable<>.Select(), which takes an argument of type Expression<Func<,>>. This forces the C# compiler to construct an expression tree instead of compiling your lambda directly to a Func<,>.

You can define both an Expression<Func<,>> and a Func<,> from the same source;

    public static Expression<Func<CompanyDescriptions,ExhibitorModel>> expression = 
        cd => new ExhibitorModel()
        {
            Id = cd.Id,
            EventId = cd.EventId,
        };
    public static Func<CompanyDescriptions,ExhibitorModel> func = expression.Compile();

Just make sure you use the right one for IQueryable<>.Select(expression) vs IEnumerable<>.Select(func).

Jeremy Lakeman
  • 9,515
  • 25
  • 29
-1

Just call function inside select. Add static in MarshallExhibitorModel() function too.

.Select(s => MarshallExhibitorModel(s.cd, s.c)
Asherguru
  • 1,687
  • 1
  • 5
  • 10
  • 3
    Don't do that at home or even at work. Instead of selecting two ids, two full entities will be transferred to the client. Better to delete this answer. – Svyatoslav Danyliv Oct 16 '20 at 09:43