How to use expression to assign property in LINQ Select new

Viewed 704

I have the following situation in a generic class:

IQueryable<TQuote> query = GetQuotes(ctx)

where ctx is a database context and GetQuotes returns DbSet<TQuote>.

Then somewhere down the code the query is executed. Its simplified form is as follows:

var list = await query
    .Select(v => new
    {
        v.Id,
        TimeZone = v.Property != null ? (int?)v.Property.TimeZone : null
        // and about 10 more simple properties like v.Id above
    }
    .ToListAsync();

where Property is a navigation property (to another table) and TimeZone is just a regular database column / property of type int? in C#.

Everything worked until I tried to use that generic class with the entity, which does not have a navigation property Property. So, I would like to replace the "hard coded" expression v.Property != null ? (int?)v.Property.TimeZone : null by an abstract member of the class and then override it differently for different TQuote types.

I tried something along this signature:

protected abstract Expression<Func<TQuote, int?>> GetTimeZone();

but then if I use it in LINQ (either directly or assigning to some variable first), then signature of TimeZone also changes to Expression<...> and if I try to ...Compile().Invoke(v), then LINQ complains that LINQ to entities does not support that.

I saw this answer: Create a Dynamic Linq to EF Expression to Select IQueryable into new class and assign properties However, it builds the whole selector by hands and given that I have total of 16 properties that would be a pain on the neck to create and maintain. So, I wonder if I can do something about this TimeZone only but leave the rest in a simple LINQ form as above.

Is it possible and if yes, then how exactly?

1 Answers

Try nuget LINQKit, https://github.com/scottksmith95/LINQKit.

Below is my attempt. LinqKit provides the AsExpandable() and Invoke(v) methods. I made up properties like Area.TimeZone and Region.RegionalTimeZone.

using LinqKit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

class QuoteResult
{
    public int Id { get; set; }
    public int? TimeZone { get; set; }
    public string Note { get; set; }
}

abstract class QuoteHelper<TQuote> where TQuote : Quote
{
    protected abstract Expression<Func<TQuote, int?>> GetTimeZone { get; }

    public IEnumerable<QuoteResult> GetQuoteResults(
        EfExpressionPropertyDbContext ctx)
    {
        IQueryable<TQuote> query = GetQuotes(ctx);
        var getTimeZone = GetTimeZone;

        var list = query
            .AsExpandable()
            .Select(v => new QuoteResult
            {
                Id = v.Id,
                TimeZone = getTimeZone.Invoke(v),
                Note = v.Note
                // and about 10 more simple properties like v.Id above
            })
            .ToList();
        return list;
    }

    public IQueryable<TQuote> GetQuotes(
        EfExpressionPropertyDbContext ctx)
    {
        return ctx.Set<TQuote>();
    }
}

class CommonQuoteHelper : QuoteHelper<CommonQuote>
{
    protected override Expression<Func<CommonQuote, int?>> GetTimeZone
    {
        get { return q => q.Area != null ? (int?)q.Area.TimeZone : null; }
    }
}

class PeculiarQuoteHelper : QuoteHelper<PeculiarQuote>
{
    protected override Expression<Func<PeculiarQuote, int?>> GetTimeZone
    {
        get { return q => q.Region != null ? (int?)q.Region.RegionalTimeZone : null; }
    }
}
Related