Efcore: EntityMaterializerSource is not applied to projections

Created on 26 Nov 2019  路  6Comments  路  Source: dotnet/efcore

In EntityFramework Core 2, where EntityMaterializerSource was a public class in Internal namespace, it was applied not only for entity types, but also for projections, and the following program referencing EF Core 2.2.6 prints "2 2":

``` C#
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.DependencyInjection;

class Program
{
static void Main(string[] args)
{
using (var db = new Context())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();

        db.Add(new Entity { Data = 1 });
        db.SaveChanges();

        var entity = db
            .Entities
            .AsNoTracking()
            .FirstOrDefault();

        var projection = db
            .Entities
            .Select(e => new { e.Data })
            .FirstOrDefault();

        Console.WriteLine($"{entity.Data} {projection.Data}");
    }
}

}

public class CustomMaterializerSource : EntityMaterializerSource
{
public override Expression CreateReadValueExpression(Expression valueBufferExpression, Type type, int index, IPropertyBase property)
{
var value = base.CreateReadValueExpression(valueBufferExpression, type, index, property);
if (type == typeof(int))
{
value = Expression.Add(value, Expression.Constant(1));
}
return value;
}
}

public class Context : DbContext
{
public DbSet Entities { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlite("filename=test.db")
        .ReplaceService<IEntityMaterializerSource, CustomMaterializerSource>();
}

}

public class Entity
{
public int Id { get; set; }
public int Data { get; set; }
}

```

In EF Core 3 the behavior was changed, and now the program (with necessary changes to constructor of CustomMaterializerSource and usings) prints "2 1".

closed-by-design customer-reported

All 6 comments

EntityMaterializerSource is class in .Internal namespace. It can be changed in between minor releases too. Custom projection does not use EntityMaterializerSource. It was never guaranteed to use. Further, modifying EntityMaterializerSource like this can cause issues when dealing with tracked entities.

In EF Core 2, it could be used, for example, for setting DateTimeKind for all DateTimes that come from the database, both in entity objects and in projections. It worked and was convenient enough, at least more convenient than the methods described in comments to #10784. The new version of the service is less useful, since it applies only partially and creates differences between entities and projections.

I do not understand the intended purpose of the class. It is not in Internal namespace now, but application code is still not supposed to override it?

It is not intended usage of the class. The service as its named EntityMaterializerSource is about materializing entities only so it is not guaranteed to be used for non-entity types. If you are using a value converter for any property and you project it out in LINQ query without entity, it will still apply value converter.

at least more convenient than the methods described in comments to #10784.

It may be more convenient but incorrect usage of the service.

This has just tripped us up massively on upgrading to EF Core 3.

If this class is meant to be internal, make it internal.

@hisuwh the class is in the Internal namespace, and since EF Core 3.0 there's also an analyzer (on by default) that will flag usage of such types. We don't make types actually internal since there are sometimes reasonable scenarios where they're needed in order to work around issues (at least until a better solution is found).

Well we were using it for what we believed to be reasonable scenario to workaround issues but this suddenly stopped working with EF Core 3.0.
We have treatwarningsaserrors enabled and did not not get a build error for this so not sure the analyzer worked.

We have fixed this now by using value converters but this was discovered in live which caused us significant issues.

Was this page helpful?
0 / 5 - 0 ratings