Linq e modifica di una Primary Key

Ipotizziamo di avere le tabelle TestPadri con la colonna ID come Primary Key (PK) e TestFigli con la colonna ID come PK.

image

Inoltre si supponga di avere configurato sul DBMS (per esempio SQL Server la relazioni in modo da modificare a cascata i record figli.

image

Si supponga poi di voler modificare la proprietà ID sulla tabella TestPadri, per come è stata configurata la relazione la proprietà Padre sulla tabella TestFigli verrebbe modificata automaticamente.

image image

Se proviamo a modificare la proprietà ID sulla tabella TestPadri tramite il seguente codice Linq to SQL otteniamo però un’eccezione.

Try
    Using context As New DBTestDataContext
        Dim padre = context.TestPadris.Single(Function(p) p.ID = 2)

        padre.ID = 3

        context.SubmitChanges()
    End Using
Catch ex As Exception
    MsgBox(ex.Message, , “Errore”)
End Try

image

La cosa non cambia anche utilizzando Linq to Entities:

Try
    Using context As New TestEntities
        Dim padre = context.TestPadri.Single(Function(p) p.ID = 2)

        padre.ID = 3

        context.SaveChanges()
    End Using
Catch ex As Exception
    MsgBox(ex.Message, , “Errore”)
End Try

image

Questo perché nel modello a oggetti di Linq l’entità è identificata da una PK, ciò significa che per assegnare una PK diversa occorre avere un’entità diversa.

Tradotto in codice questo significa che nel nostro caso per modificare la proprietà ID sulla tabella TestPadri occorre eseguire le seguenti operazioni:

  1. Creare una nuova entità di TestPadri a cui verrà assegnato il nuovo ID e su cui verranno impostati i valori dell’entità originale sulle altre proprietà .
  2. Spostare le entità di TestFigli dall’entità originale di TestPadri alla nuova entità creata.
  3. Eliminare l’entità originale di TestPadri.

Di seguito il codice Linq to SQL per eseguire l’operazione:

    Using context As New DBTestDataContext
        Dim padreOriginale = context.TestPadris.Single(Function(p) p.ID = 2)
        Dim padreNuovo = New Test.TestPadri()

        padreNuovo.ID = 3
        padreNuovo.Descrizione = padreOriginale.Descrizione
        context.TestPadris.InsertOnSubmit(padreNuovo)

        Dim figliCount = padreOriginale.TestFiglis.Count

        For index = figliCount – 1 To 0 Step -1
            Dim figlio = padreOriginale.TestFiglis(index)
            padreOriginale.TestFiglis.RemoveAt(index)
            padreNuovo.TestFiglis.Add(figlio)
        Next

        context.TestPadris.DeleteOnSubmit(padreOriginale)

        context.SubmitChanges()
    End Using
Catch ex As Exception
    MsgBox(ex.Message)
End Try

Ovviamente però quello che verrà eseguito sul database non sarà un semplice Update sulla colonna ID del record della tabella TestPadri, ma bensì un INSERT di un nuovo record nella tabella TestPadri, delle UPDATE sui record figli nella tabella TestFigli e una DELETE del record originale nella tabella TestPadri

SELECT [t0].[ID], [t0].[Descrizione]
FROM [dbo].[TestPadri] AS [t0]
WHERE [t0].[ID] = @p0
— @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [2]

SELECT [t0].[ID], [t0].[Padre], [t0].[Descrizione]
FROM [dbo].[TestFigli] AS [t0]
WHERE [t0].[Padre] = @p0
— @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [2]

INSERT INTO [dbo].[TestPadri]([ID], [Descrizione])
VALUES (@p0, @p1)
— @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [4]
— @p1: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Padre 2]

UPDATE [dbo].[TestFigli]
SET [Padre] = @p3
WHERE ([ID] = @p0) AND ([Padre] = @p1) AND ([Descrizione] = @p2)
— @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [3]
— @p1: Input Int (Size = -1; Prec = 0; Scale = 0) [2]
— @p2: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Figlio 1 di Padre 2]
— @p3: Input Int (Size = -1; Prec = 0; Scale = 0) [4]

UPDATE [dbo].[TestFigli]
SET [Padre] = @p3
WHERE ([ID] = @p0) AND ([Padre] = @p1) AND ([Descrizione] = @p2)
— @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [4]
— @p1: Input Int (Size = -1; Prec = 0; Scale = 0) [2]
— @p2: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Figlio 2 di Padre 2]
— @p3: Input Int (Size = -1; Prec = 0; Scale = 0) [4]

DELETE FROM [dbo].[TestPadri] WHERE ([ID] = @p0) AND ([Descrizione] = @p1)
— @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [2]
— @p1: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Padre 2]

Ovvero non viene utilizzata l’impostazione sulla relazione tra TestPadri e TestFigli di aggiornamento a cascata.

L’alternativa può essere quella di inviare direttamente il comando SQL di aggiornamento tramite il metodo ExecuteCommand, che di fatto come rimportato su MSDN nasce appunto per gestire situazioni particolari per cui Linq mal si presta:

“This method is a pass-through mechanism for cases where LINQ to SQL does not adequately provide for a particular scenario.”

Di seguito il codice Linq to SQL per eseguire l’operazione:

Try
    Using context As New DBTestDataContext
        context.ExecuteCommand(“UPDATE TestPadri SET ID={0} WHERE ID={1}”, 3, 2)
    End Using
Catch ex As Exception
    MsgBox(ex.Message)
End Try