改变"键入"在层次结构中

时间:2018-02-16 08:36:19

标签: c# sql-server entity-framework

TL / DR - 有没有办法强制EF Core允许我更新Discriminator列?

我第一次使用Entity Framework Core并试图实现一个简单的系统,我可以管理3种类型的客户端。其中两种类型的类型值本身只有显着差异(type1type2)。第三种类型(special)只有一组扩展的可能关系。

因此,在数据库中我创建了一个表来存储核心数据:

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])
)

在我的代码中,我创建了这些模型:

  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; }
  }

对于具有更多可能关系的客户的一个子类型:

  public class SpecialClient : Client
  {
    public List<Service> Services { get; set; }
    //And more
  }

我最初在OnModelCreating中将其映射为:

  modelBuilder.Entity<Clients.Client>()
    .HasDiscriminator<string>("ProtocolType")
    .HasValue<Clients.SpecialClient>("special");

但最终只查询了数据库类型specialClient - 而不是我想要的。我假设EF只使用基类来表示&#34;所有其他值&#34;但事实并非如此。所以我试过了:

  modelBuilder.Entity<Clients.Client>()
    .HasDiscriminator<string>("ProtocolType")
    .HasValue<Clients.SpecialClient>("special")
    .HasValue<Clients.Client>("type1")
    .HasValue<Clients.Client>("type2");

但这实际上只取了上一个提供的值,只会查询数据库specialtype2。所以,我终于接受了我需要引入一个没有额外功能的冗余额外类,这样我才能让映射器满意:

public class Type2Client : Client {}

  modelBuilder.Entity<Clients.Client>()
    .HasDiscriminator<string>("ProtocolType")
    .HasValue<Clients.SpecialClient>("special")
    .HasValue<Clients.Client>("type1")
    .HasValue<Clients.Type2Client>("type2");

终于 - EF很开心,一切都很好,我可以继续我的生活。但是,下一个挑战是,客户可以更改协议

所以,我加载了一个客户端。我改变了它的协议类型。我救了它:

      _context.Update(client);
      await _context.SaveChangesAsync();

EF生成这个SQL:

SET NOCOUNT ON;
UPDATE [Clients].[Clients] SET [ClientLogoUrl] = @p0, [ClientName] = @p1
WHERE [ClientId] = @p2;
SELECT @@ROWCOUNT;

Harumph。 SET列没有ProtocolType。是否有一些狡猾的方法来使这项工作?

1 个答案:

答案 0 :(得分:2)

您需要更改鉴别器列的默认“保存后”行为。默认情况下,鉴别器列的AfterSaveBehavior设置为Throw。这意味着如果设置或更改了显式值,则在尝试保存更改时会抛出异常。这也意味着Update不会将此类属性标记为已修改,因为这会导致SaveChanges期间出现异常。

因此,在您的情况下,将保存行为更改为Save的鉴别器列(当然,在您创建此列鉴别器后执行此操作):

modelBuilder.Entity<Client>()
   .Property(c => c.ProtocolType).Metadata.AfterSaveBehavior = PropertySaveBehavior.Save;

它应该像你期望的那样工作。