TL/DR - Is there a way to force EF Core to allow me to update a Discriminator column?
I'm playing around with Entity Framework Core for the first time and trying to implement a simple system where I have Clients of 3 types to manage. Two of these types are only meaningfully different in their type value itself (type1 and type2). The third type (special) just has an extended set of possible relationships.
So, in the database I created a single table to store the core data:
CREATE TABLE [Clients].[Clients](
[ClientId] [varchar](30) NOT NULL,
[ClientName] [varchar](100) NOT NULL,
[ProtocolType] [varchar](30) NOT NULL,
[ClientLogoUrl] [varchar](max) NULL,
CONSTRAINT [PK_Clients_Clients] PRIMARY KEY ([ClientId]),
CONSTRAINT [UQ_Clients_Client_Names] UNIQUE ([ClientName]),
CONSTRAINT [UQ_Clients_Client_Protocol_XRef] UNIQUE ([ClientId],[ProtocolType]),
CONSTRAINT [FK_Clients_Client_Protocol] FOREIGN KEY([ProtocolType])
REFERENCES [Clients].[Protocols] ([ProtocolType])
)
In my code I then created these models:
public class Client
{
public string ClientId { get; set; }
public string ClientName { get; set; }
public string ProtocolType { get; set; }
public string ClientLogoUrl { get; set; }
public Protocol Protocol { get; set; }
}
And for the one subtype of Clients that have more possible relationships:
public class SpecialClient : Client
{
public List<Service> Services { get; set; }
//And more
}
I initially had this mapped in OnModelCreating as:
modelBuilder.Entity<Clients.Client>()
.HasDiscriminator<string>("ProtocolType")
.HasValue<Clients.SpecialClient>("special");
But this ended up querying the database only for protocol types special and Client - not what I wanted. I'd assumed EF would just use the base class for "all other values" but it did not. So then I tried:
modelBuilder.Entity<Clients.Client>()
.HasDiscriminator<string>("ProtocolType")
.HasValue<Clients.SpecialClient>("special")
.HasValue<Clients.Client>("type1")
.HasValue<Clients.Client>("type2");
But this effectively only took the last supplied value and would only query the database for special and type2. So, I finally accepted that I'd need to introduce a redundant extra class with no extra functionality just so that I could make the mapper happy:
public class Type2Client : Client {}
And:
modelBuilder.Entity<Clients.Client>()
.HasDiscriminator<string>("ProtocolType")
.HasValue<Clients.SpecialClient>("special")
.HasValue<Clients.Client>("type1")
.HasValue<Clients.Type2Client>("type2");
Finally - EF is happy, all is good, I can move on with my life. However, the next challenge is, clients can change protocols.
So, I load a client. I change its protocol type. I save it:
_context.Update(client);
await _context.SaveChangesAsync();
And EF generates this SQL:
SET NOCOUNT ON;
UPDATE [Clients].[Clients] SET [ClientLogoUrl] = @p0, [ClientName] = @p1
WHERE [ClientId] = @p2;
SELECT @@ROWCOUNT;
Harumph. No SET for the ProtocolType column. Is there some cunning way to make this work?
You need to change default "after save" behavior of discriminator column. By default, discriminator column has AfterSaveBehavior set to Throw. That means that if explicit value is set, or changed - exception is thrown when trying to save changes. That also means Update will not mark such property as modified, because that will lead to exception during SaveChanges anyway.
So in your case change save behavior to Save for discriminator column (do that after you made this column discriminator of course):
modelBuilder.Entity<Client>()
.Property(c => c.ProtocolType).Metadata.AfterSaveBehavior = PropertySaveBehavior.Save;
And it should work as you expect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With