Can I access the discriminator value in TPH mapping with Entity Framework 4 CTP5
Asked Answered
F

8

20

Using Entity Framework 4 CTP5 Code First and this example

Is it possible to access the discriminator value?

I would like to use it in a projection like

context.BillingDetails.Select(x => new { Number = x.Number, DiscrimitatorValue = /* how do I get the discriminator value? */ });

From this post I understand the discriminator cannot be mapped to a property but is there any other way of accessing it?

Fortier answered 24/12, 2010 at 11:6 Comment(4)
no - you can't. the discriminator is excluded from the model. What's the reason for wanting it in a projection? Can you give an example requirement/query your trying to achieve?Seabolt
@Seabolt I want to project my query to a new type that contains information from joined tables but if I do that I will loose my original type information for the record. The returned objects will be of my new type and I will not be able to identify their original type. I was hoping I could project the discriminator value to the new type and thus solving the issueFortier
same issue.. want to select all records and their types. maybe anyone knows a workaround?Outfight
EF's support for TPH frankly sucks. It's simply not supported in any of the public mapping APIs. You can get to the mapping through some classes, but the properties and even types are often internal, making it impossible to use. It's in the EdmType's MetadataProperties collection entry named Configuration, then under the internal property SubTypeMappingConfigurations you can find the discriminators, and eventually their values buried in yet more properties declared "internal". Absolutely useless. Why would I ever bother migrating to EF Core, which has worse than zero support for this.Rizo
F
8

After further information from Morteza Manavi in the comments of his post the simple answer is no

you should be aware that the discriminator column is used internally by Code First and you cannnot read/write its values from an inheritance mapping standpoint.

To access the discriminator I would have to execute a SqlQuery against the database or change my mapping strategy.

Fortier answered 4/1, 2011 at 14:42 Comment(1)
Yeah, execute SqlQuery to get at the discriminator and then do what with it? You can't get to the discriminator values, and even if you could, then what... map every subtype manually? No way.Rizo
F
25

I may be late to the game on this one, but I just added a getter property to the base class that returned the name of the current type:

public string DiscriminatorValue {
    get {
        return this.GetType().Name;
    }
}

Since by default EF is going to use this same value for the Discriminator field, they will match up.

Foote answered 11/6, 2013 at 16:44 Comment(2)
Unless you override it ;-)Personality
Am I missing something here? This won't work in IQueryable.Select, which is what OP asked for. NotSupportedException: The specified type member 'DiscriminatorValue' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported..Aricaarick
O
14

In EF Core 2.1 (I haven't checked previous versions) it's enough to add Discriminator to the base abstract class as private set property. It will be mapped with adequate value.

public abstract class Entity
{
    public int Id { get; set; }
    public string Discriminator { get; private set; }
}

EF by itself will automatically insert appropriate discriminator value to the database and will automatically set it to an object on read.

Oller answered 26/7, 2018 at 11:59 Comment(3)
But can you read back this base type? You can get 'at' the discriminator value this way, but what use it it? Still have no way of reading a TPH type from a query like 'select * from TPHTable' or a sproc or anything useful. Just get the 'cannot instantiate abstract class' error. This is the best EF Core 2.1 can do? Total lack of any support of TPH types, just like EF6, and even dropping support that EF6 had for TPT and TPC altogether? Wow. Bye.Rizo
Regarding previous versoins of EF, I can confirm this doesn't work in EF6. It will just try to create a second Discriminator1 column.Aricaarick
You can add a property with the name you gave to the discriminator in EF Core, example: ...HasDiscriminator<string>("Type")Armagnac
F
8

After further information from Morteza Manavi in the comments of his post the simple answer is no

you should be aware that the discriminator column is used internally by Code First and you cannnot read/write its values from an inheritance mapping standpoint.

To access the discriminator I would have to execute a SqlQuery against the database or change my mapping strategy.

Fortier answered 4/1, 2011 at 14:42 Comment(1)
Yeah, execute SqlQuery to get at the discriminator and then do what with it? You can't get to the discriminator values, and even if you could, then what... map every subtype manually? No way.Rizo
P
5

Reason aside, I recently ran into the same problem but believe this is still relevant for v4 of the EF Framework.

First, create a view which selects the discriminator value into two columns.

create view dbo.vw_BillingDetail
as
    select BillingDetailId, DiscriminatorValue, DiscriminatorValue as DiscriminatorValue2 from dbo.BillingDetail
go

Secondly, map the view to your entity during context creation:

modelBuilder
    .Entity<BillingDetail>()
    .HasKey(n => n.BillingDetailId)
    .Map(map =>
    {
        map.ToTable("vw_Person");
    })

Thirdly, define your discriminator mapping for your derived class using one of the columns in your view:

.Map<MyDerivedBillingDetail>(map =>
{
    map.Requires("DiscriminatorValue2").HasValue("YourValue");
})

Finally, define a getter and a private setter for the other discriminator column in your view with the DatabaseGenerated annotation set as Computed to prevent EF from updating/inserting for this field:

class BillingDetail
{
    public BillingDetailId { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DiscriminatorValue { get; private set; }
}

You can change the private setter to be protected and set this value explicitly during the construction of your derived entities so that the discriminator has a value prior to being persisted:

class MyDerivedBillingDetail : BillingDetail
{
    public MyDerivedBillingDetail()
    {
        this.DiscriminatorValue = "MyValue";
    }
}
Pisa answered 10/12, 2015 at 18:8 Comment(0)
H
3

To expand on @Michael Black's answer for Entity Framework Core 2.1 (earlier? tested in 2.1.4)

You can use any property name, database field name and data type you want.

Create a property:

[Column("foo_type_id")]
class Foo {
    public FooTypesEnum TypeId {get; set;}
}

Then in your context class with the fluent API via modelBuilder:

modelBuilder.Entity<Foo>(b => {
    b.HasDiscriminator(foo => foo.TypeId)
        .HasValue<SubFooA>(FooTypesEnum.SubFooA)
        .HasValue<SubFooB>(FooTypesEnum.SubFooB);
});

This is really useful if you need to build composable queries that e.g., group on the discriminator, etc.

Hirsh answered 18/10, 2018 at 21:53 Comment(0)
H
1

Why don't you use the following query instead?

 var q = con.BillingDetails.OfType<BankAccount>().ToList();
Holmen answered 24/12, 2010 at 14:12 Comment(3)
The above is just an example. Where I would like to use it requires a projection and I would like to know what the original type was. Plus I would just like to improve my knowledge.Fortier
@Holmen can I use con.BillingDetails.OfType<baseclass>().ToList() I want only list of "base class" objects.Accordant
@yogendarji If BillingDetail is an abstract class, there could not be instances of this class and con.BillingDetails.OfType<baseclass>().ToList() or con.BillingDetails.ToList() do not make sense. If BillingDetail is not abstract, try the query con.BaseClass.Where(t => !(t is FirstChildClass || t is SecondChildClass || ... || t is LastChildClass)).ToList(), i.e. con.BillingDetails.Where(t => !(t is BankAccount || t is CreditCard)).ToList().Holmen
A
0

You can add a property with the name you gave to the discriminator in EF Core. Example:

In DBContext:

...HasDiscriminator<string>("Type")..

In base class do:

public string Type { get; private set; }
Armagnac answered 23/7, 2021 at 11:49 Comment(0)
S
-1

What worked for me was retrieving the Discriminator column via .HasComputedColumnSql() following the steps below:

  1. Add a property to access the value e.g
    public string EntityType{ get; set; }
  2. Configure this property to map to an sql user defined function
    entity.Property(e => e.EntityType).HasComputedColumnSql("dbo.GetEntityType([Id])")
  3. Define a user defined function to retrieve the value:
    CREATE FUNCTION dbo.GetEntityType (@AccountId UNIQUEIDENTIFIER)
    RETURNS NVARCHAR(MAX) AS
    BEGIN
        -- do stuff with other tables
        DECLARE @returnvalue NVARCHAR(MAX);
    
        SELECT @returnvalue =  EntityType
        FROM dbo.Entitites
        WHERE Id= @Id
    
        RETURN(@returnvalue);
    
    END
    
  4. Do migrations and database update in the usual manner.
Squinteyed answered 13/4, 2023 at 9:53 Comment(3)
Unnecessary over-engineering. In EF-core you can simply access the discriminator property.Oria
The problem with that is you won't be able to use it in IQueryable.Select, you will get the error \ NotSupportedException: The specified type member 'DiscriminatorValue' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.Squinteyed
That's the old EF6. But you show EF core syntax.Oria

© 2022 - 2024 — McMap. All rights reserved.