9

Zaimplementowałem symulowaną konstrukcję dziedziczenia tabel w moim SQL-Server na podstawie artykułu Implementing Table Inheritance in SQL Server."Właściwość zależna w ReferentialConstraint jest odwzorowana na kolumnę generowaną przez sklep." na utrwalonej kolumnie obliczeniowej (najpierw EntityFramework DB)

Poza tym, że używają prostych relacji od 1 do 0 ... 1, tworzysz kolejne ograniczenie do tabeli typów, która wymienia wszystkie możliwe typy dzieci tabeli bazowej, jak wyjaśniono w artykule w akapicie "Modelowanie One-to- Albo więzy ".

Każda z tabel podrzędnych zawiera pole TYPE, które ma ComputedColumnSpecification z utrzymywaną liczbą reprezentującą identyfikator typu w tabeli typów. Ze względu na to, że pole TYPE jest częścią ograniczenia, upewni się, że do podstawowego zbioru danych można utworzyć tylko jedno dziecko.

Dla lepszego zrozumienia stworzyłem przykładową bazę danych, która służy do opisu problemu za pomocą pasującego rozwiązania ASP.NET. Replikować problem w swoim środowisku lokalnym, należy utworzyć bazę danych o nazwie „BOISKO” przed wykonaniem tego skryptu:

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 

tabela USER jest tabela bazowa i stoły NATURAL_USER i JURIDICAL_USER są jej dzieci. USER_T to tabela typów USER.

Teraz w mojej aplikacji ASP.NET z wykorzystaniem EntityFramework 6 Staram się utworzyć nowego użytkownika w następujący sposób:

using (PLAYGROUNDEntities model = new PLAYGROUNDEntities()) 
{ 
    USER user = new USER(); 
    user.Username = "admin"; 
    user.Password = "RANDOMHASH#123456"; 
    user.Email = "[email protected]"; 

    user.NATURAL_USER = new NATURAL_USER(); 
    user.NATURAL_USER.BirthDate = new DateTime(1980, 01, 01); 

    model.USER.Add(user); 
    model.SaveChanges(); 
} 

I na model.SaveChanges(); otrzymuję wyjątek:

na utrzymaniu Właściwość w ReferentialConstraint jest odwzorowywana na kolumnę generowaną przez sklep. Kolumna: "TYP".

Roztwór próbki:.. https://dl.dropboxusercontent.com/u/55589036/zzzOther/Playground.zip (przykładowy kod znajduje się w Page_Load z Default.aspx.cs

Rozumiem The EntityFramework próbuje ustawić pole kolumny i nie, bo to sklep generowane (utrwalone) to nawet dzieje, kiedy ustawić user.NATURAL_USER.TYPE = 1;.

próbowałem przesłonić OnModelCreating dołączyć własne reguły i określają zarówno TYPE kolumny jako Computed, ale OnModelCreating nigdy nie nazywa, bo robię EDMX - po i chcę się do tego trzymać.

Tak więc ten model encji jest generowany na podstawie bazy danych i chciałbym go zachować w ten sposób, a także nie chcę edytować żadnego kodu, kiedy za każdym razem aktualizuję mój model.

Ponadto, myślę, że koncepcja dziedziczenia tabeli jest bardzo dobrze zaimplementowana w warstwie bazy danych, ponieważ nie używa wyzwalaczy. Chcę go nie uruchamiać.

Jak mogę rozwiązać ten problem?

+0

Dlaczego po prostu nie używasz EF z własnym dziedziczeniem (TPT lub TPH)? Czy masz ograniczenie projektu dotyczące korzystania z DB First? – bubi

+0

@bubi Podejścia TPT lub TPH są tylko systemami dziedziczenia na EF i nadal pozwalają na ominięcie go w bazie danych przy użyciu zwykłego SQL. TPT/TPH wymagają od mnie budowania asocjacji w projektancie po ponownym utworzeniu pliku EDMX. Proszę mnie poprawić, jeśli się mylę. – modiX

+0

TPT i TPH są podejściami [ściśle] związanymi z Code First. Jeśli pracujesz na istniejącej bazie danych (DB First + EDMX) nie jest dobrym podejściem. Twoja sprawa to TPH (pojedyncza tabela + pole dyskryminatora), ale jeśli DB + EDMX jest standardem ograniczającym, EF TPH nie jest dobrym podejściem. – bubi

Odpowiedz

2

zrobiłem straszny błąd podczas wdrażania podejścia, ale to działało wcześniej. Zablokowałem przypadkowo FK_NATURAL_USER___USER___TYPEVALIDATION.

Powinien być zbudowany jak ograniczenie więzów FK_JURIDICAL_USER___USER___TYPEVALIDATION.

EF jest w stanie obsłużyć utrwalone kolumny. Problem polegał na tym, że próbował napisać do PK z numeru [USER_T], który nie powinien być w ogóle wyjątkiem ograniczenia.

Przepraszam wszystkich ludzi, którzy zmarnowali na to czas.

2

Nic nie wiem o EF, ale utworzyłem twoje kolumny TYPE jako normalne kolumny, nie obliczone, nietrwałe.

Następnie ustawię ich domyślną wartość na wymaganą wartość i dodam ograniczenie na CHECK, aby upewnić się, że nie można go zmienić.

Reszta twojego skryptu T-SQL, w którym ustawienia kluczy obcych pozostają takie same.

Na przykład dla NATURAL_USER to będzie wyglądać następująco:

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