Jamie
Jamie

Reputation: 168

Linq select into model and set properties

In .NET Core 2.X, I was able to use this code below:

var bookings = await db.Tasks
        .Where(c => c.ClientId == clientId && c.IsDeleted == false && c.Start > startOfThisMonth && c.End < endOfThisMonth)
        .OrderBy(x => x.Start)
        .Select(x => new SpecialTaskVm(new TaskViewModel(x, null))
        {
            client = x.Client,
            carer = x.Booking.SingleOrDefault(b => b.SlotNumber == 1).Carer,
            carer2 = x.Booking.SingleOrDefault(bk => bk.SlotNumber == 2).Carer
        })
        .ToListAsync();

However the same code in .net core 3.X results in this error:

System.InvalidOperationException: When called from 'VisitMemberInit', rewriting a node of type 'System.Linq.Expressions.NewExpression' must return a non-null value of the same type. Alternatively, override 'VisitMemberInit' and change it to not visit children of this type.

I could really do with selecting in the way I do above as each model does some modification to some properties and each model is used elsewhere separately.

I am also trying to avoid a foreach as it seems that would be inefficient.

I have tried passing the properties I need to set, into the model and setting them in the model like that. Same error occurs.

//This action method will return data for current month.
var startOfThisMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
var endOfThisMonth = new DateTime(DateTime.Now.Year, DateTime.Now.AddMonths(1).Month, 1);

var bookings = await db.Tasks
        .Where(c => c.ClientId == clientId && c.IsDeleted == false && c.Start > startOfThisMonth && c.End < endOfThisMonth)
        .OrderBy(x => x.Start)
        .Select(x => new SpecialTaskVm(new TaskViewModel(x, null))
        {
            client = x.Client,
            carer = x.Booking.SingleOrDefault(b => b.SlotNumber == 1).Carer,
            carer2 = x.Booking.SingleOrDefault(bk => bk.SlotNumber == 2).Carer
        })
        .ToListAsync();

I expect for the list of tasks to be returned in the form of List<SpecialTaskVm> with Client, Carer and Carer2 set.

Upvotes: 2

Views: 4850

Answers (1)

DavidG
DavidG

Reputation: 118937

It's a bit unusual to use a constructor and object initialisation syntax in the same code, to me that's already a code smell.

If I were you, I would create an intermediate list that only gets values from the database, then project that data into your SpecialTaskVm objects. For example:

// First get the data from the database in a simple form we can parse through later
var bookingData = await db.Tasks
        .Where(c => c.ClientId == clientId && c.IsDeleted == false && c.Start > startOfThisMonth && c.End < endOfThisMonth)
        .OrderBy(x => x.Start)
        .Select(x => new // Use an anonymous type
        {
            Client = x.Client,
            Carer = x.Booking.SingleOrDefault(b => b.SlotNumber == 1).Carer,
            Carer2 = x.Booking.SingleOrDefault(bk => bk.SlotNumber == 2).Carer
        })
        .ToListAsync();

// Now we massage the data into a format we can use
var bookings = bookingData
        .Select(x => new SpecialTaskVm(new TaskViewModel(x, null))
        {
            client = x.Client,
            carer = x.Carer,
            carer2 = x.Carer2
        })
        .ToList();

Additionally, I would potentially recommend changing the SpecialTaskVm constructor (or add a new one) to include the new fields.

Upvotes: 3

Related Questions