dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.74k stars 3.18k forks source link

Translate GetType() for entity types with inheritance #13424

Closed subprime closed 2 years ago

subprime commented 6 years ago

In my queries i want achive that all vehicles except the prototype vehicles are returned, which has more properties. The query is build with OfType() but it seems that this has no influence of the behaviour.

public abstract class BaseVehicle
{
    public int Id { get; set; }
}

public class Vehicle : BaseVehicle
{
    public string VehicleIdentificationNumber { get; set; }
}

public class VehiclePrototype : Vehicle
{
    public DateTimeOffset? PressReleaseAt { get; set; }
}

Is this normal? What is the right way to filtrer the entities?

Further technical details

EF Core version: 2.1 Database Provider: Microsoft.EntityFrameworkCore.SqlServer Operating system: Windows 10 IDE: Visual Studio 2017 Pro 15.8.4

ajcvickers commented 6 years ago

@subprime The semantics of OfType is "that type or any derived type", like is or as in C#. This is not an EF thing, but rather how OfType is defined in LINQ.

That being said, I don't know the best way to get just entities of a specific type in EF Core. I tried:

context.Set<BaseVehicle>().Where(v => v.GetType() == typeof(Vehicle)).Cast<Vehicle>().ToList();

but that evaluates on the client. This:

context.Set<BaseVehicle>().Where(v => EF.Property<string>(v, "Discriminator") == nameof(Vehicle)).Cast<Vehicle>().ToList();

works, but it's a bit obscure.

@smitpatel Can you advise?

subprime commented 6 years ago

Hey @ajcvickers thanks for the fast reply. I know what you mean but it's wierd. My interpretion of OfType was to get the concrete type instead of the derived types because they have different endpoints in my api... 👎

ajcvickers commented 6 years ago

@subprime Can you be more specific about what is weird about it? This is the way OfType is defined in the base class libraries for .NET. If you feel this is wrong, then probably the compiler team is the best place to give that feedback.

smitpatel commented 6 years ago

@subprime - Please post your query.

ajcvickers commented 6 years ago

@smitpatel Not sure which query you want, but here are the three I tested, with the repro code below.

Using OfType:

context.Set<BaseVehicle>().OfType<Vehicle>()
SELECT [b].[Id], [b].[Discriminator], [b].[VehicleIdentificationNumber], [b].[PressReleaseAt]
FROM [BaseVehicle] AS [b]
WHERE [b].[Discriminator] IN (N'VehiclePrototype', N'Vehicle')

Using GetType:

context.Set<BaseVehicle>()
    .Where(v => v.GetType() == typeof(Vehicle)).Cast<Vehicle>()
SELECT [v].[Id], [v].[Discriminator], [v].[VehicleIdentificationNumber], [v].[PressReleaseAt]
FROM [BaseVehicle] AS [v]
WHERE [v].[Discriminator] IN (N'VehiclePrototype', N'Vehicle')

Using discriminator:

context.Set<BaseVehicle>()
    .Where(v => EF.Property<string>(v, "Discriminator") == nameof(Vehicle)).Cast<Vehicle>()
SELECT [v].[Id], [v].[Discriminator], [v].[VehicleIdentificationNumber], [v].[PressReleaseAt]
FROM [BaseVehicle] AS [v]
WHERE [v].[Discriminator] IN (N'VehiclePrototype', N'Vehicle') AND ([v].[Discriminator] = N'Vehicle')

Full code:

public abstract class BaseVehicle
{
    public int Id { get; set; }
}

public class Vehicle : BaseVehicle
{
    public string VehicleIdentificationNumber { get; set; }
}

public class VehiclePrototype : Vehicle
{
    public DateTimeOffset? PressReleaseAt { get; set; }
}

public class BloggingContext : DbContext
{
    private static readonly LoggerFactory Logger
        = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) });

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseLoggerFactory(Logger)
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<BaseVehicle>();
        modelBuilder.Entity<Vehicle>();
        modelBuilder.Entity<VehiclePrototype>();
    }
}

public class Program
{
    public static async Task Main()
    {
        using (var context = new BloggingContext())
        {
            await context.Database.EnsureDeletedAsync();
            await context.Database.EnsureCreatedAsync();

            context.Add(new Vehicle());
            context.Add(new VehiclePrototype());
            context.SaveChanges();
        }

        using (var context = new BloggingContext())
        {
            var vehicles = context.Set<BaseVehicle>().Where(v => v.GetType() == typeof(Vehicle)).Cast<Vehicle>().ToList();
        }
    }
}
subprime commented 6 years ago

@smitpatel which query you want?

smitpatel commented 6 years ago

In my queries i want achive that all vehicles except the prototype vehicles are returned

OfType is certainly not the linq operator which maps to that behavior. Hence, I am having hard time understanding what is exact linq query you are running which uses OfType and pulls all data which you don't want.

smitpatel commented 6 years ago

Posting your original attempted query and description would help us get you in write direction about what query should be written to work efficiently with EF.

smitpatel commented 6 years ago

Triage decision: v.GetType() == typeof(Vehicle) should get converted to discriminator predicate when used with TPH mapping.

subprime commented 6 years ago

@smitpatel, sorry i was in holiday... Can you shortly explain what this means?

v.GetType() == typeof(Vehicle) should get converted to discriminator predicate when used with TPH mapping.

smitpatel commented 6 years ago

We will convert v.Getype() == typeof(Vehicle) to EF.Property(v, "Discriminator) == "Vehicle" automatically to do server evaluation.

Xriuk commented 1 week ago

Are there any plans to convert GetType() in a select query? To allow querying only the type of a given TPH entity. It should be fairly simple.

roji commented 1 week ago

@Xriuk using GetType() in queries works - this is what this issue is about. If something isn't working for you, please open a new issue with a minimal, runnable code sample that shows what you're trying to do.