Małe sprostowanie. Nie chcę zapisywać (update) pojedynczych właściwości np. Nazwisko lub sama Miejscowość. Chodziło mi raczej o grupę właściwości, wręcz "składowe" obiekty typu Adres składające się na obiekt Person.
Ad 2p1 - Definitywnie nie zgodzę się z Tobą jakoby podwójna walidacja (w dwóch warstwach) była złem. Wręcz przeciwnie. Walidacja w aplikacji to odciążenie (sito) serwera BD. Nie ma sensu zawracać głowy silnikowi bazy danych i generować niepotrzebny ruch pomiędzy serwerem i aplikacją. Komplikacja? Nie koniecznie. Ważne jest aby w warstwie DB walidatory były szczelne. jak zapomni się o czymś w aplikacji to DB zwróci info. Gdzie widzisz problem wydajności skoro to aplikacja w modelu RichClient?
- Tak mam coś takiego jak RAISEERROR :)
Ok może trochę ważniejszego kodu wkleję. Na początek z bazy danych. Nie pytaj dlaczego taka a nie inna koncepcja i budowa tabel oraz procedur. Takie założenia a opis i wytłumaczenie tego zajęłoby wieki :)
na przykładzie słownika krajów:
-- ****************************************************************************
-- Procedura: sp_InsCountry
-- ****************************************************************************
--
-- Wstawienie nowego rekordu do tabeli country
-- oraz w razie potrzeby zaktualizowanie poprzednika
-- Procedura zwraca kod 0 lub inny oraz IDR nowo wstawionego rekordu
IF EXISTS (SELECT 1 FROM [sys].[procedures] WHERE [name] = N'sp_InsCountry')
DROP PROCEDURE [pap].[sp_InsCountry]
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [pap].[sp_InsCountry]
@p_current_user [pap].[idr],
@p_ido [pap].[ido],
@p_name nvarchar(100),
@p_code2 nvarchar(2),
@p_code3 nvarchar(3),
@p_nationality nvarchar(100),
@p_ue bit,
@p_status tinyint,
@p_system bit,
@p_new_idr [pap].[idr] OUTPUT
WITH ENCRYPTION
AS
DECLARE -- Zmienne pomocnicze
@ErrorCode int,
@ErrorMsg nvarchar(100),
@OldIdr [pap].[idr],
@Log nvarchar(1000)
SET NOCOUNT ON
BEGIN TRANSACTION INSERT_COUNTRY
-- Inicjalizacja zmiennych
SET @ErrorCode = 0;
SET @ErrorMsg = N'';
SET @OldIdr = 0;
-- Pobieranie nowego IDR'a
EXEC @ErrorCode = [pap].[sp_GenIdr] @p_current_user, @p_ido, @p_new_idr OUTPUT
IF (@ErrorCode <> 0)
BEGIN
SET @ErrorCode = 1
SET @ErrorMsg = N'Błąd generowania identyfikatora rekordu';
GOTO Quit
END
-- Przechowanie Idr'a (później będzie on wykorzystany do usunięcia rekordu z widoku)
-- Domyślnie OldIdr = 0. Jeśli wstawiamy nowy rekord to OldIdr będzie dalej = 0
-- W przeciwnym wypadku w zmiennej OldIdr będzie Idr rekordu który chcemy zmienić.
SELECT TOP 1 @OldIdr = [idr] FROM [pap].[act_available_country]
WHERE [ido] = @p_ido
--IF (@OldIdr IS NULL)
-- BEGIN
-- SET @ErrorCode = 2;
-- SET @ErrorMsg = N'Brak rekordu w widoku';
-- GOTO Quit
-- END
BEGIN TRY
-- Chwilowe usunięcie rekordu z widoku act_country aby
-- nie naruszyć PK, FK oraz wstawić nowy zaktualizowany rekord do tabeli
-- Operacja wykona się jeśli dokonujemy zmiany istniejących danych
IF (@OldIdr <> 0)
UPDATE [pap].[country] SET [next_idr] = @OldIdr
WHERE [idr] = @OldIdr
INSERT INTO [pap].[act_country] (
[idr],
[ido],
[name],
[code2],
[code3],
[nationality],
[ue],
[status],
[system]
)
VALUES (
@p_new_idr,
@p_ido,
@p_name,
@p_code2,
@p_code3,
@p_nationality,
@p_ue,
@p_status,
@p_system
)
-- Korekta (widok)
IF (@OldIdr <> 0)
UPDATE [pap].[country] SET [next_idr] = @p_new_idr
WHERE [idr] = @OldIdr
END TRY
BEGIN CATCH
SET @ErrorCode = 9
SET @ErrorMsg = N'Błąd zapisu.';
GOTO Quit
END CATCH
SET NOCOUNT OFF
Quit:
IF (@ErrorCode <> 0)
BEGIN
ROLLBACK TRANSACTION INSERT_COUNTRY
SET @Log = @ErrorMsg + N' (' + @p_name + N')'
EXEC [pap].[sp_WriteLog] @p_current_user, 2, N'sp_InsCountry', @Log
RETURN @ErrorCode
END
ELSE
BEGIN
COMMIT TRANSACTION INSERT_COUNTRY
RETURN 0
END
GO
i procedura do dodania nowej pozycji do słownika
-- ****************************************************************************
-- Procedura: sp_AddCountry
-- ****************************************************************************
--
-- Dodanie nowej pozycji (kraju)
-- Procedura zwraca kod 0 lub inny
IF EXISTS (SELECT 1 FROM [sys].[procedures] WHERE [name] = N'sp_AddCountry')
DROP PROCEDURE [pap].[sp_AddCountry]
GO
CREATE PROCEDURE [pap].[sp_AddCountry]
@current_user [pap].[login],
@name nvarchar(100),
@code2 nvarchar(2),
@code3 nvarchar(3),
@nationality nvarchar(100),
@ue bit,
@status tinyint
WITH ENCRYPTION
AS
DECLARE -- Zmienne pomocnicze
@ErrorCode int,
@ErrorMsg nvarchar(100),
@NewIdr [pap].[idr],
@NewIdo [pap].[ido],
@Log nvarchar(1000)
SET NOCOUNT ON
BEGIN TRANSACTION ADD_COUNTRY
-- Inicjalizacja zmiennych
SET @ErrorCode = 0;
SET @ErrorMsg = N'';
-- Sprawdzanie uprawnień
EXEC @ErrorCode = [pap].[sp_CheckUserRight] @current_user, N'PAP', N'ADD_COUNTRY'
IF (@ErrorCode <> 0)
BEGIN
SET @ErrorCode = 1;
SET @ErrorMsg = N'Brak uprawnień';
GOTO Quit
END
-- Sprawdzanie czy kraj już taki istnieje (nazwa) w aktualnie dostępnej liście
IF EXISTS(SELECT 1 FROM [pap].[act_available_country] WHERE UPPER([name]) = UPPER(@name))
BEGIN
SET @ErrorCode = 6
SET @ErrorMsg = N'Wybrana nazwa kraju istnieje już w bazie danych';
GOTO Quit
END
-- Sprawdzanie czy kod2 kraju już taki istnieje (kod występuje ale w innej pozycji (ido))
IF EXISTS(SELECT 1 FROM [pap].[act_available_country] WHERE [code2] = @code2)
BEGIN
SET @ErrorCode = 6
SET @ErrorMsg = N'Wybrany kod (2 znaki) kraju istnieje już w bazie danych';
GOTO Quit
END
-- Sprawdzanie czy kod3 kraju już taki istnieje (kod występuje ale w innej pozycji (ido))
IF EXISTS(SELECT 1 FROM [pap].[act_available_country] WHERE [code3] = @code3)
BEGIN
SET @ErrorCode = 6
SET @ErrorMsg = N'Wybrany kod (3 znaki) kraju istnieje już w bazie danych';
GOTO Quit
END
-- Sprawdzanie nazwy kraju (po usunięciu spacji)
IF (RTRIM(LTRIM(@name)) = '')
BEGIN
SET @ErrorCode = 7;
SET @ErrorMsg = N'Brak nazwy kraju (nazwa)';
GOTO Quit
END
-- Sprawdzanie kodu2 kraju (po usunięciu spacji)
IF (RTRIM(LTRIM(@code2)) = '')
BEGIN
SET @ErrorCode = 11;
SET @ErrorMsg = N'Brak kodu kraju (2 znaki)';
GOTO Quit
END
-- Sprawdzanie kodu3 kraju (po usunięciu spacji)
IF (RTRIM(LTRIM(@code3)) = '')
BEGIN
SET @ErrorCode = 11;
SET @ErrorMsg = N'Brak kodu kraju (3 znaki)';
GOTO Quit
END
-- Pobieranie kolejnego wolnego numeru IDO i sprawdzenie czy nie przekroczyliśmy
-- maksymalnej liczby pozycji w slowniku systemu.
SELECT @NewIdo = MAX([ido]) FROM [pap].[act_country]
IF (@NewIdo IS NOT NULL)
IF (@NewIdo >= 99999999)
BEGIN
SET @ErrorCode = 2
SET @ErrorMsg = N'Przekroczona maksymalna liczba pozycji w słowniku krajów'
GOTO Quit
END
ELSE
SET @NewIdo = @NewIdo + 1
ELSE
SET @NewIdo = 0
-- Wstawienie rekordu do tabeli kraj
EXEC @ErrorCode = [pap].[sp_InsCountry]
@p_current_user = @current_user,
@p_ido = @NewIdo,
@p_name = @name,
@p_code2 = @code2,
@p_code3 = @code3,
@p_nationality = @nationality,
@p_ue = @ue,
@p_status = @status,
@p_system = 0,
@p_new_idr = @NewIdr OUTPUT
IF (@ErrorCode <> 0)
BEGIN
SET @ErrorCode = 3
SET @ErrorMsg = N'Błąd wstawiania nowego rekordu słownika krajów';
GOTO Quit
END
SET NOCOUNT OFF
Quit:
IF (@ErrorCode <> 0)
BEGIN
ROLLBACK TRANSACTION ADD_COUNTRY
SET @Log = @ErrorMsg + N' (' + @name + N')'
EXEC [pap].[sp_WriteLog] @current_user, 2, N'sp_AddCountry', @Log
RAISERROR(@ErrorMsg, 16, 1)
RETURN @ErrorCode
END
ELSE
BEGIN
COMMIT TRANSACTION ADD_COUNTRY
SET @Log = N'Dodanie kraju: (' + @name + N')'
EXEC [pap].[sp_WriteLog] @current_user, 0, N'sp_AddCountry', @Log
RETURN 0
END
GO
To tylko poglądowe przykłady. I nie są jeszcze w 100% optymalne etc. Mogą się wydać Ci bardzo nietypowe i nieoptymalne, ale wierz mi są bardzo przemyślane
Chociaż to jest dość sztuczny problem, należałoby raczej zadbać aby aktualizacja schematu bazy danych zawsze wymagała nowej wersji aplikacji.
No i jest to sprawdzane podczas uruchamiania aplikacji. Nie ma sensu sprawdzać tego w każdej procedurze bo to dopiero negatywnie wpłynęłoby na wydajność systemu.
Niemniej jednak są takie przypadki że ktoś nie zamknie jednej z końcówek, a dodatkowo ta końcówka nie zostanie zaktualizowana przy aktualizacji bazy danych i aplikacji na innych stacjach - wtedy mamy zonk.
ad2 p2.
No i tu niestety nie będzie błędu zgłaszanego przez DB, bo model bazy danych celowo nie zakłada FK/PK na większości relacji. Podyktowane innymi warunkami. Słownik ten jest tylko formą podpowiedzi. Ok. ale ten punkt zostawmy bo nie chce się skupiać nad sprawami dla mnie nie istotnymi.
Ty chyba oczekujesz cudu. Skoro nie chcesz wykorzystać ORMa, który zrobiłby całą magię za Ciebie, to musisz niestety zaimplementować tę magię sam. Założę się, że to pierwsze byłoby łatwiejsze i tańsze niż to drugie.
O jakiej magii i CRE'mie mówisz :) ? Przypominam że CRM kosztuje nie tylko jako produkt ale i wdrożenie oraz serwis + aktualizacja. Inną kwestią jest to że nie zawsze da się dostosować CRM'a do swoich "nietypowych" wymagań biznesowych.
PS. A jak widziałeś w/w screen'y to jak twoim zdaniem powinna wyglądać obsługa zapisu obiektu kontrahent do bazy (nowego oraz edytowanego)?