Assign Mapped Object to Expression Result in LINQ to Entities
Asked Answered
H

1

0

I have the following child object that we use an expression to map our 'entity' to our 'domain' model. We use this when specifically calling our ChildRecordService method GetChild or GetChildren:

    public static Expression<Func<global::Database.Models.ChildRecord, ChildRecord>> MapChildRecordToCommon = entity => new ChildRecord
    {
        DateTime = entity.DateTime,
        Type = entity.Type,
    };

    public static async Task<List<ChildRecord>> ToCommonListAsync(this IQueryable<global::Database.Models.ChildRecord> childRecords)
    {
        var items = await
            childRecords.Select(MapChildRecordToCommon).ToListAsync().EscapeContext();

        return items;
    }

    public async Task<List<ChildRecord>> GetChildRecords()
    {
        using (var uow = this.UnitOfWorkFactory.CreateReadOnly())
        {
            var childRecords= await uow.GetRepository<IChildRecordRepository>().GetChildRecords().ToCommonListAsync().EscapeContext();

            return childRecords;
        }
    }

So that all works just fine. However we have another object that is a parent to that child, that in SOME cases, we also wish to get the child during the materialisation and mapping process.

In other words the standard object looks as such:

    private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommonBasic = (entity) => new Plot
    {
        Id = entity.Id,
        Direction = entity.Direction,
        Utc = entity.Utc,
        Velocity = entity.Velocity,
    };

However what I also want to map is the Plot.ChildRecord property, using the expression MapChildRecordToCommon I have already created. I made a second expression just to test this:

    private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommonAdvanced = (entity) => new Plot
    {
        ChildRecord = MapChildRecordToCommon.Compile() (entity.ChildRecord)
    };

This fails:

System.NotSupportedException
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

Is there a way to reuse my existing expression for ChildRecord, to materialise the object of ChildRecord (ie. one to one/singular not multiple) on the Plot object? I think my trouble is caused by there being just one object and being unable to use the .Select(Map) method. I am not too great at expressions and have hit a wall with this.

For reference, there are actually up to 5 or 6 other child objects on the "Plot" object that I also want to make expressions for.

Hawaiian answered 3/1, 2020 at 16:15 Comment(0)
H
0

I resolved this by using the third party library LinqKit.

The library allowed the use of 2 methods, .AsExpandable() (which allows for the expressions to properly compile and be invoked as I understand), and .Invoke() as an extension method to an expression, rather than calling Expression.Invoke(yourexpression). I included a null check just in case.

My code now looks as follows:

public static async Task<List<Plot>> ToCommonListAsync(this IQueryable<global::Database.Models.Plot> plots)
{
    var items = await
        plots.AsExpandable().Select(MapPlotToCommon).ToListAsync().EscapeContext();

    return items;
}

private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommon = (entity) => new Plot
{
    Id = entity.Id,
    Direction = entity.Direction,
    Utc = entity.Utc,
    Velocity = entity.Velocity,
    ChildRecord = entity.ChildRecord != null ? MapChildRecordToCommon.Invoke(entity.ChildRecord) : default
};

public static Expression<Func<global::Database.Models.ChildRecord, ChildRecord>> MapChildRecordToCommon = entity => new ChildRecord
{
    DateTime = entity.DateTime,
    Type = entity.Type,
};
Hawaiian answered 7/1, 2020 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.