Nel precedente post, abbiamo visto come creare un nuovo oggetto di tipo DataSet attraverso la definizione di un XML Schema. Oggi vediamo come aggiungere dei constraint alle tabelle presenti nel DataSet.
Il primo constraint che possiamo aggiungere è uno UniqueConstraint, che specifica che una determinata colonna dev'essere unica; nel nostro caso, prendendo sempre spunto dalla struttura della tabella creata nel precedente post, assegnamo il constraint alla colonna ID.

UniqueConstraint idPk = new UniqueConstraint("idPk", ds.Tables[0].Columns["ID"]);

ds.Tables[0].Constraints.Add(idPk);
ds.Tables[0].Columns["ID"].Unique = true;
ds.Tables[0].Columns["ID"].AllowDBNull = false;

Abbiamo cosi' specificato che la colonna ID dev'essere unica e che non può essere nulla (proprietà AllowDBNull). Se infatti proviamo ad aggiungere due record con lo stesso valore per la colonna ID, verrà sollevata un'eccezione di tipo System.Data.ConstraintException, mentre se proviamo ad inserire una riga senza assegnare nessun valore alla colonna in questione, verrà sollevato un'eccezione di tipo System.Data.NoNullAllowedException.

Per inserire invece un constraint di chiave primaria, dobbiamo settare il valore della proprietà PrimaryKey, della classe DataTable, passandogli la colonna, o le colonne, che vogliamo come chiave primaria della taballa.

ds.Tables[0].PrimaryKey = new DataColumn[]{ds.Tables[0].Columns["ID"]};

Questo tipo di constraint può essere settato anche dallo schema XML inserendo un elemento "key" all'interno dell'elemento che rappresenta il DataSet:

<xsd:key name="myDataKey1" msdata:PrimaryKey="true">

<xsd:selector xpath=".//Clienti" />
<xsd:field xpath="ID" />
</xsd:key>

Un altro tipo di constraint molto utile è l'AutoIncrement, che specifica l'incremento automatico di una colonna di tipo int. Per creare una colonna con questo tipo di constraint, bisogna settare a true la proprietà autoincrement della classe DataColumn, decidere il valore di incremento e il valore iniziale da cui far partire l'incremento.

DataColumn incrementColumn = new DataColumn("Increment");

incrementColumn.DataType = System.Type.GetType("System.Int32");
incrementColumn.ReadOnly = true;
incrementColumn.AutoIncrement = true;
incrementColumn.AutoIncrementSeed = 0;
incrementColumn.AutoIncrementStep = 1;
ds.Tables[0].Columns.Add(incrementColumn);

L'ultimo tipo di constraint è invece il vincolo di chiave esterna. Questo vincolo viene utilizzato per collegare tra loro varie tabelle attraverso i rispettivi valori di chiave primaria e chiave esterna. Risulta inoltre molto utile durante le operazioni di aggiornamento e cancellazione di record in quanto è possibile specificare se applicare le modifiche fatte su un record a cascata (cascade) nei record collegati, se non applicargli alcuna modifica (none), se settare i dati collegati a null (setNull) o se settargli un valore di default (setDefault).

ForeignKeyConstraint fk = new ForeignKeyConstraint("fk",

ds.Tables[0].Columns["PK"], ds.Tables[1].Columns["FK"]);
fk.DeleteRule = Rule.Cascade;
fk.UpdateRule = Rule.SetNull;
ds.Tables[0].Constraints.Add(fk);

Una proprietà molto importante della classe ForeignKeyConstraint è la proprietà AcceptRejectRule che consente di specificare il comportamento delle righe figlie al modificarsi delle proprietà AcceptChanges o RejectChanges della riga padre; anche in questo caso le opzioni sono o cascade o none.

Sia chiaro però, che tutte queste proprietà e operazioni che possiamo effettuare su di un oggetto di tipo DataSet, per customizzarlo a nostro piacimento, le possiamo prendere in automatico da query su eventuali basi di dati, specificando nella chiamata del metodo Fill della classe OleDbDataAdapter (o SqlDataAdapter) che il tipo di schema da applicare al DataSet dev'essere mappato dal database (SchemaType.Mapped).