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?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
David Glenn
  • 24,412
  • 19
  • 74
  • 94
  • 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? – RPM1984 Dec 25 '10 at 06:18
  • 2
    @RPM1984 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 issue – David Glenn Jan 04 '11 at 14:50
  • 1
    same issue.. want to select all records and their types. maybe anyone knows a workaround? – maxlego Sep 13 '12 at 07:40
  • 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. – Triynko Mar 20 '19 at 04:23

8 Answers8

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.

Paul
  • 12,392
  • 4
  • 48
  • 58
  • 3
    Unless you override it ;-) – Kugel Feb 05 '14 at 03:54
  • 4
    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.`. – Rudey May 14 '19 at 09:55
13

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.

Michael Black
  • 151
  • 1
  • 4
  • 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. – Triynko Mar 20 '19 at 04:27
  • 2
    Regarding previous versoins of EF, I can confirm this doesn't work in EF6. It will just try to create a second `Discriminator1` column. – Rudey May 14 '19 at 09:57
  • 1
    You can add a property with the name you gave to the discriminator in EF Core, example: ...HasDiscriminator("Type") – MarchalPT Jul 23 '21 at 11:46
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.

David Glenn
  • 24,412
  • 19
  • 74
  • 94
  • 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. – Triynko Mar 20 '19 at 04:25
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";
    }
}
user978139
  • 579
  • 4
  • 16
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.

steamer25
  • 9,278
  • 1
  • 33
  • 38
1

Why don't you use the following query instead?

 var q = con.BillingDetails.OfType<BankAccount>().ToList();
ocuenca
  • 38,548
  • 11
  • 89
  • 102
Devart
  • 119,203
  • 23
  • 166
  • 186
  • 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. – David Glenn Dec 24 '10 at 16:08
  • @Devart can I use `con.BillingDetails.OfType().ToList()` I want only list of "base class" objects. – Yogen Darji Apr 21 '20 at 12:12
  • @yogendarji If BillingDetail is an abstract class, there could not be instances of this class and con.BillingDetails.OfType().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(). – Devart Apr 23 '20 at 15:58
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; }
MarchalPT
  • 1,268
  • 9
  • 28
-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.
  • Unnecessary over-engineering. In EF-core you can simply access the discriminator property. – Gert Arnold Apr 13 '23 at 10:18
  • 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.` – Gift Ntokozo Mavuso Apr 14 '23 at 06:16
  • That's the old EF6. But you show EF core syntax. – Gert Arnold Apr 14 '23 at 06:35