Ho implementato un costrutto di ereditarietà di tabelle simulato nel mio SQL Server basato sull'articolo Ereditarietà della tabella di implementazione in SQL Server .
Oltre al fatto di utilizzare semplici relazioni da 1 a 0 ... 1, si crea un altro vincolo per una tabella di tipi che elenca tutti i possibili tipi di bambini della tabella di base, come spiegato nell'articolo nel paragrafo "Modellazione dei vincoli da uno a due".
Ciascuna delle tabelle figlio contiene un campo TYPE con ComputedColumnSpecification con un numero persistente che rappresenta l'ID del tipo nella tabella dei tipi. A causa del fatto che il campo TYPE fa parte del vincolo, si accerterà che solo un bambino possa essere creato nel set di dati di base.
Per una migliore comprensione, ho creato un database di esempio che viene utilizzato per descrivere il problema con una soluzione ASP.NET corrispondente. Per replicare il problema nel tuo ambiente locale, crea un database chiamato "PLAYGROUND" prima di eseguire questo script:
USE [PLAYGROUND]
GO
/****** Object: Table [dbo].[USER] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[USER](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TYPE__ID] [int] NOT NULL,
[Enabled] [bit] NOT NULL,
[Username] [nvarchar](32) NOT NULL,
[Password] [nchar](32) NOT NULL,
[Email] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[NATURAL_USER] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[NATURAL_USER](
[ID] [int] NOT NULL,
[TYPE] AS ((1)) PERSISTED NOT NULL,
[BirthDate] [date] NOT NULL,
CONSTRAINT [PK_NATURAL_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object: Table [dbo].[JURIDICAL_USER] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[JURIDICAL_USER](
[ID] [int] NOT NULL,
[TYPE] AS ((2)) PERSISTED NOT NULL,
[CompanyName] [nvarchar](256) NOT NULL,
[RegistrationNo] [nvarchar](max) NOT NULL,
[Description] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_LEGAL_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object: Table [dbo].[USER_T] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[USER_T](
[ID] [int] IDENTITY(1,1) NOT NULL,
[TYPE] [nvarchar](32) NOT NULL,
CONSTRAINT [PK_USER_T] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Index [IX_USER] ******/
ALTER TABLE [dbo].[USER] ADD CONSTRAINT [IX_USER] UNIQUE NONCLUSTERED
(
[Username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
/****** Object: Index [PK_USER_TYPE] ******/
CREATE UNIQUE NONCLUSTERED INDEX [PK_USER_TYPE] ON [dbo].[USER]
(
[ID] ASC,
[TYPE__ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** Object: Index [IX_USER_T] ******/
ALTER TABLE [dbo].[USER_T] ADD CONSTRAINT [IX_USER_T] UNIQUE NONCLUSTERED
(
[TYPE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** TYPE DATA ******/
SET IDENTITY_INSERT [dbo].[USER_T] ON
GO
INSERT [dbo].[USER_T] ([ID], [TYPE]) VALUES (2, N'JURIDICAL_USER')
GO
INSERT [dbo].[USER_T] ([ID], [TYPE]) VALUES (1, N'NATURAL_USER')
GO
SET IDENTITY_INSERT [dbo].[USER_T] OFF
GO
/****** Contraints ******/
ALTER TABLE [dbo].[JURIDICAL_USER] WITH CHECK ADD CONSTRAINT [FK_JURIDICAL_USER___USER] FOREIGN KEY([ID])
REFERENCES [dbo].[USER] ([ID])
GO
ALTER TABLE [dbo].[JURIDICAL_USER] CHECK CONSTRAINT [FK_JURIDICAL_USER___USER]
GO
ALTER TABLE [dbo].[JURIDICAL_USER] WITH CHECK ADD CONSTRAINT [FK_JURIDICAL_USER___USER___TYPEVALIDATION] FOREIGN KEY([ID], [TYPE])
REFERENCES [dbo].[USER] ([ID], [TYPE__ID])
GO
ALTER TABLE [dbo].[JURIDICAL_USER] CHECK CONSTRAINT [FK_JURIDICAL_USER___USER___TYPEVALIDATION]
GO
ALTER TABLE [dbo].[NATURAL_USER] WITH CHECK ADD CONSTRAINT [FK_NATURAL_USER___USER] FOREIGN KEY([ID])
REFERENCES [dbo].[USER] ([ID])
GO
ALTER TABLE [dbo].[NATURAL_USER] CHECK CONSTRAINT [FK_NATURAL_USER___USER]
GO
ALTER TABLE [dbo].[NATURAL_USER] WITH CHECK ADD CONSTRAINT [FK_NATURAL_USER___USER___TYPEVALIDATION] FOREIGN KEY([TYPE])
REFERENCES [dbo].[USER_T] ([ID])
GO
ALTER TABLE [dbo].[NATURAL_USER] CHECK CONSTRAINT [FK_NATURAL_USER___USER___TYPEVALIDATION]
GO
ALTER TABLE [dbo].[USER] WITH CHECK ADD CONSTRAINT [FK_USER___USER_T] FOREIGN KEY([TYPE__ID])
REFERENCES [dbo].[USER_T] ([ID])
GO
ALTER TABLE [dbo].[USER] CHECK CONSTRAINT [FK_USER___USER_T]
GO
USE [master]
GO
ALTER DATABASE [PLAYGROUND] SET READ_WRITE
GO
La tabella USER
è la tabella di base e le tabelle NATURAL_USER
e JURIDICAL_USER
sono i suoi figli. USER_T
è la tabella dei tipi di USER
.
Ora, nella mia applicazione ASP.NET che utilizza EntityFramework 6, provo a creare un nuovo utente nel seguente modo:
using (PLAYGROUNDEntities model = new PLAYGROUNDEntities())
{
USER user = new USER();
user.Username = "admin";
user.Password = "RANDOMHASH#123456";
user.Email = "admin@example.org";
user.NATURAL_USER = new NATURAL_USER();
user.NATURAL_USER.BirthDate = new DateTime(1980, 01, 01);
model.USER.Add(user);
model.SaveChanges();
}
E su model.SaveChanges();
Ottengo l'eccezione:
Una proprietà dipendente in un ReferentialConstraint è mappata a una colonna generata dal negozio. Colonna: 'TYPE'.
Soluzione campione: https://dl.dropboxusercontent.com/u/55589036/zzzOther/Playground.zip (il codice di esempio si trova nel Page_Load
di Default.aspx.cs
.
Capisco, EntityFramework cerca di impostare il campo della colonna e fallisce, perché è generato dal negozio (persistente). Questo succede anche quando imposto user.NATURAL_USER.TYPE = 1;
.
Ho provato a sovrascrivere OnModelCreating
per allegare la mia regola e definire entrambe le colonne TYPE
come Computed
, ma OnModelCreating
non viene mai chiamato, perché eseguo EDMX-after e voglio attenermi a questo.
Quindi, questo modello di entità viene generato in base al database e mi piacerebbe tenerlo in questo modo, in più non voglio modificare alcun codice quando aggiorno il mio modello di nuovo, ogni volta.
Inoltre, penso che il concetto di ereditarietà delle tabelle sia implementato molto bene sul livello del database, perché non utilizza i trigger. Voglio tenerlo senza trigger.
Come posso risolvere questo problema?
Ho commesso un errore terribile quando ho implementato l'approccio, ma ha funzionato prima. Ho incasinato il vincolo FK_NATURAL_USER___USER___TYPEVALIDATION
, per errore.
Dovrebbe essere stato creato come il vincolo FK_JURIDICAL_USER___USER___TYPEVALIDATION
.
L'EF è in grado di gestire le colonne persistenti. Il problema è che ha tentato di scrivere sul PK di [USER_T]
che non dovrebbe assolutamente prescindere dal vincolo.
Mi dispiace per tutte le persone che hanno perso tempo in questo.
Non so nulla di EF, ma creerei le tue colonne TYPE
come colonne normali, non calcolate, non persistenti.
Quindi imposterei il loro valore predefinito sul valore richiesto e aggiungere un vincolo CHECK
per assicurarmi che non possa essere modificato.
Il resto del tuo script T-SQL in cui la configurazione delle chiavi esterne rimane la stessa.
Ad esempio, per NATURAL_USER
sarebbe simile a questo:
CREATE TABLE [dbo].[NATURAL_USER](
[ID] [int] NOT NULL,
[TYPE] [int] NOT NULL,
[BirthDate] [date] NOT NULL,
CONSTRAINT [PK_NATURAL_USER] PRIMARY KEY CLUSTERED
(
[ID] ASC
))
GO
ALTER TABLE [dbo].[NATURAL_USER] WITH CHECK
ADD CONSTRAINT [CK_NATURAL_USER] CHECK (([TYPE]=(1)))
GO
ALTER TABLE [dbo].[NATURAL_USER]
CHECK CONSTRAINT [CK_NATURAL_USER]
GO
ALTER TABLE [dbo].[NATURAL_USER]
ADD CONSTRAINT [DF_NATURAL_USER_TYPE] DEFAULT ((1)) FOR [TYPE]
GO