Seguint amb la sèrie de la clsDades aquesta és la funció que s'encarrega d'esborrar un registre a la BBDD.
Fa servir la SP DEL per fer-ho. Així que ha de fer servir la variable ParamsDEL per informar-ne els paràmetres. Normalment aquests paràmetres són la primary key (PK), de tal manera que només s'esborra un registre. Existeix en 2 sabors diferents, un que rep un DataRow i un altra que rep un objecte Dictionary (on les Keys són els noms dels camps i els Values el valor).
Igual que la funció INS, aquesta també invalida la cache.
Com sempre el codi, primer el que rep el Dictionary:
Seguint amb la sèrie de la clsDades aquesta és la funció que s'encarrega d'actualitzar un registre a la BBDD.
Fa servir la SP UPD per fer-ho. Així que ha de fer servir la variable ParamsUPD per informar-ne els paràmetres. Existeix en 2 sabors diferents, un que rep un DataRow i un altra que rep un objecte Dictionary (on les Keys són els noms dels camps i els Values el valor).
Igual que la funció INS, aquesta també invalida la cache.
Com sempre el codi, primer el que rep el Dictionary:
Seguint amb la sèrie de la clsDades aquesta és la funció que s'encarrega d'insertar un registre a la BBDD.
Fa servir la SP INS per fer-ho. Així que ha de fer servir la variable ParamsINS per informar-ne els paràmetres. Existeix en 2 sabors diferents, un que rep un DataRow i un altra que rep un objecte Dictionary (on les Keys són els noms dels camps i els Values el valor). Ambdues quan hi ha una identitat en la taula on inserten recuperen el valor que s'ha generat.
Ja posats que hi ha informació sobre si un camp admet nulls o no, es fa aquesta verificació. El que no es mira és el tipus de dades del valor que passa per actualitzar.
Resaltar que aquesta funció invalida la cache de dades, aleshores si la clase fa servir la cache i està activada la auto-neteja aleshores buida la cache. Pot ser que no interessi netejar la cache cada cop, per exemple si hi ha molts inserts seguits, aleshores cal posar el "Autonetejar" a fals.
El codi de la versió DataRow:
publicvoid INS(DataRow DR)
{
SqlCommand SqlComm = newSqlCommand();
SqlParameter SqlParam;
Int64 ID = 0;
bool LocalConn;
SqlConnection Conn = null;
if (SQLConn == null)
{
LocalConn = true;
Conn = newSqlConnection(CadConnBBDD);
Conn.Open();
}
else
{
LocalConn = false;
Conn = SQLConn;
}
SqlComm.Connection = Conn;
if (SQLTrans != null) SqlComm.Transaction = SQLTrans;
SqlComm.CommandType = CommandType.StoredProcedure;
SqlComm.CommandText = spINS;
foreach (clsCamp Camp in ParamsINS)
{
SqlParam = CreaParametre(Camp, true);
if (DR.Table.Columns.Contains(Camp.NomCamp))
{
if (!DR.IsNull(Camp.NomCamp))
{
if (Camp.Nulable) SqlParam.Value = DBNull.Value;
else
{
if (!Camp.Identitat)
{
Exception Ex = newException("El camp " + Camp.NomCamp + " no admet valors nulls");
throw Ex;
}
}
}
else SqlParam.Value = DR[Camp.NomCamp];
}
else
{ //Potser la taula té menys columnes que camps hi ha a la BBDDif (Camp.Nulable) SqlParam.Value = DBNull.Value;
else
{
if (!Camp.Identitat)
{
Exception Ex = newException("El camp " + Camp.NomCamp + " no admet valors nulls");
throw Ex;
}
}
}
SqlComm.Parameters.Add(SqlParam);
}
SqlComm.ExecuteNonQuery();
if (TeCache && AutoNetejaCache)
{
ClearCache();
}
if (LocalConn)
{
Conn.Close();
Conn.Dispose();
Conn = null;
}
if (TeIdentitat)
{
//Cal vigilar que la columna identitat no sigui ReadOnly//DR.Table.Columns[Identitat].ReadOnly = false;
ID = Int64.Parse(SqlComm.Parameters[Prefix + Identitat].Value.ToString());
DR[Identitat] = ID;
}
DR.AcceptChanges();
}
La versió que fa servir el Dictionary retorna el ID generat en cas que hagi identitat.
public Int64 INS(Dictionary<string, string> Params)
{
SqlCommand SqlComm = newSqlCommand();
SqlParameter SqlParam;
Int64 ID = 0;
bool LocalConn;
SqlConnection Conn = null;
if (SQLConn == null)
{
LocalConn = true;
Conn = newSqlConnection(CadConnBBDD);
Conn.Open();
}
else
{
LocalConn = false;
Conn = SQLConn;
}
SqlComm.Connection = Conn;
if (SQLTrans != null) SqlComm.Transaction = SQLTrans;
SqlComm.CommandType = CommandType.StoredProcedure;
SqlComm.CommandText = spINS;
foreach (clsCamp Camp in ParamsINS)
{
SqlParam = CreaParametre(Camp, true);
if (Params.Keys.Contains<string>(Camp.NomCamp))
{
if (string.IsNullOrEmpty(Params[Camp.NomCamp]))
{
if (Camp.Nulable) SqlParam.Value = DBNull.Value;
else
{
Exception Ex = newException("El camp " + Camp.NomCamp + " no admet valors nulls");
throw Ex;
}
}
else SqlParam.Value = Params[Camp.NomCamp];
}
else
{
if (Camp.Nulable) SqlParam.Value = DBNull.Value;
else
{
if (!Camp.Identitat)
{
Exception Ex = newException("El camp " + Camp.NomCamp + " no admet valors nulls");
throw Ex;
}
}
}
SqlComm.Parameters.Add(SqlParam);
}
SqlComm.ExecuteNonQuery();
if (TeCache && AutoNetejaCache)
{
ClearCache();
}
if (LocalConn)
{
Conn.Close();
Conn.Dispose();
Conn = null;
}
if (TeIdentitat) ID = Int64.Parse(SqlComm.Parameters[Prefix + Identitat].Value.ToString());
else ID = 0;
return ID;
}
#30/12/2013 16:36 Programació C# Autor: Alex Canalda
Aquesta funció pertany a la clsDades i funciona igual que la GET però limita el nombre de files retornades a les que se li passen per paràmetre.
Perque és necessària? Suposem que tenim un procés que ha de processar (valgui la redundància) 50.000 registres, això ho retorna el GET sense problemes. Però quan porta 10.000 processats en una aplicació web dóna un timeout. Llavors és necessari processar de 5.000 en 5.000 registres. Durant el procés el registres poden canviar d'estat (per exemple) i així evitar tornar a ser seleccionats a la següent crida del GETTOP. Cada cop que es processa un conjunt de registres es pot enviar a la UI un missatge indicant que encara queden per processar, la UI pot actualitzar alguna barra de progrés o similar i llestos (haurà fet un COUNT per saber el total).
La funció té els mateixos paràmetres que el GET amb un afegit que és el número de registres a retornar.
Aquesta funció de la clsDades s'encarrega de transformar els paràmetres informats a paràmetres SqlServer i invocar a la stored procedure GET, obtenint els resultats en un taula (objecte DataTable). Per fer-ho fa servir la funció de creació de paràmetres amb la informació de la variable ParamsGET que és tipus clsCamp (es pot veure un exemple aquí.).
Si no està informat l'objecte connexió a BBDD en crea una nova fent servir l'string CadConn, i la tanca posteriorment (es a dir, per un accés puntual es pot fer servir directament). Si, al contrari, ja hi ha un objecte connexió creat el fa servir sense modificar-ne l'estat. Això és important ja que si es fan servir múltiples objectes de la clsDades i es vol que tots facin servir la mateixa connexió, sense anar obrint i tancant, caldrà informar-los la connexió (i de forma anàloga la transacció) abans de fer servir l'objecte. Com sempre poso el codi per veure que fa.
publicDataTable GET(Dictionary<string, string> Params)
{
SqlDataReader Reader = null;
DataTable Result = newDataTable();
bool LocalConn;
SqlConnection Conn = null;
//SQLConn és una variable SqlConnection de la clase, si no està instanciada cal crear-laif (SQLConn == null)
{
LocalConn = true;
Conn = newSqlConnection(CadConnBBDD);
Conn.Open();
}
else
{
LocalConn = false;
Conn = SQLConn;
}
SqlCommand SqlComm = newSqlCommand();
SqlParameter SqlParam;
SqlComm.Connection = Conn;
if (SQLTrans != null) SqlComm.Transaction = SQLTrans;
SqlComm.CommandType = CommandType.StoredProcedure;
SqlComm.CommandText = spGET;
//Es creen tots els paràmetres i informant els valors dels que existeixenforeach (clsCamp Camp in ParamsGET)
{
SqlParam = CreaParametre(Camp, false);
if (Params.Keys.Contains<string>(Camp.NomCamp))
{
if (string.IsNullOrEmpty(Params[Camp.NomCamp])) SqlParam.Value = DBNull.Value;
else SqlParam.Value = Params[Camp.NomCamp];
}
else SqlParam.Value = DBNull.Value;
SqlComm.Parameters.Add(SqlParam);
}
Reader = SqlComm.ExecuteReader();
Result.Load(Reader);
Reader.Close();
if (LocalConn)
{
Conn.Close();
Conn.Dispose();
Conn = null;
}
return Result;
}
#17/12/2013 16:42 Programació C# Autor: Alex Canalda
Dins de la clsDades hi ha aquesta és una petita funció que s'encarrega de crear un paràmetre de SQLServer a partir de la informació guardada en un objecte clsCamp. L'única variació que té és en el INSERT, que quan es tracta d'una identitat cal recuperar el valor que es genera. Per això en aquest cas es posa el paràmetre de sortida (output). Aquesta funció es fa servir en la resta de funcions que veurem, les que invoquen a les seves corresponents stored procedures. Poso el codi a continuació, es pot seguir fàcilment.
privateSqlParameter CreaParametre(clsCamp Camp, bool INSERT)
{
SqlParameter SqlParam;
SqlParam = newSqlParameter();
//Si és un insert interessa recuperar el valor de la identitat, el paràmetre ha de ser output.if (INSERT)
{
if ((TeIdentitat) && (Camp.NomCamp == Identitat)) SqlParam.Direction = ParameterDirection.Output;
else SqlParam.Direction = ParameterDirection.Input;
} //Tots els altres sempre input.else SqlParam.Direction = ParameterDirection.Input;
SqlParam.ParameterName = Prefix + Camp.NomCamp;
switch (Camp.Tipus)
{
caseTipus.bit:
SqlParam.SqlDbType = SqlDbType.Bit;
break;
caseTipus.tint:
SqlParam.SqlDbType = SqlDbType.Int;
break;
caseTipus.bigint:
SqlParam.SqlDbType = SqlDbType.BigInt;
break;
caseTipus.nvarchar:
SqlParam.SqlDbType = SqlDbType.NVarChar;
SqlParam.Size = Camp.Longitud;
break;
caseTipus.date:
SqlParam.SqlDbType = SqlDbType.Date;
break;
caseTipus.time:
SqlParam.SqlDbType = SqlDbType.Time;
break;
caseTipus.datetime:
SqlParam.SqlDbType = SqlDbType.DateTime;
break;
caseTipus.nchar:
SqlParam.SqlDbType = SqlDbType.NChar;
SqlParam.Size = Camp.Longitud;
break;
caseTipus.dec:
SqlParam.SqlDbType = SqlDbType.Decimal;
SqlParam.Precision = Camp.Precisio;
SqlParam.Scale = Camp.Escala;
break;
caseTipus.chr:
SqlParam.SqlDbType = SqlDbType.Char;
SqlParam.Size = Camp.Longitud;
break;
caseTipus.varchar:
SqlParam.SqlDbType = SqlDbType.VarChar;
SqlParam.Size = Camp.Longitud;
break;
}
return SqlParam;
}
#17/12/2013 16:07 Programació C# Autor: Alex Canalda
La clase clsDades, que veurem en detall, conté els mètodes per invocar les SP i les declaracions de variables que fan servir aquests mètodes. En canvi una classe derivada només té les inicialitzacions d'aquestes variables. El programa que genera la capa de dades crea automàticament aquestes inicialitzacions. Es a dir que el següent tros de programa està generat, no picat. Poso un exemple qualsevol, és només indicatiu.
Com es pot veure picar això a mà seria mortal, per aquest motiu hi ha un software que ho genera. Els paràmetres de les SP els obté de les pròpies SP, es a dir, han d'estar generades prèviament, abans de generar aquesta classe. La resta de llistes les obté a partir dels camps de la taula que estan al software generador. Si es marca un camp per serialitzar apareixerà a la llista de "Serialitzar", si es marca per deserialitzar... ja es veu la idea. Llavors la capa d'accés a BBDD es compon de la clsDades i un munt de classes derivades.
#13/12/2013 13:34 Programació C# Autor: Alex Canalda
En aquesta clase, com cobreix molta funcionalitat, hi ha parts que només serveixen per projectes web i en canvi per projectes d'un altra tipus (per exemple WinForms) donen errors de compilació (per que no hi ha el HttpRequest). Per això he fet servir la compilació condicional, típica.
Aquesta compilació condicional afecta a la serialització, deserialització ja que aquests mètodes no es fan servir en projectes no web. Per això es defineix una constant (o símbol) a Projecte -> Propietats -> Build -> Conditional Compilation symbols. Cal vigilar de definir aquest símbol en totes les configuracions (Debug, Release...).
#11/12/2013 11:55 Programació C# Autor: Alex Canalda
La clsDades conté les declaracions de les diferents variables que fa servir. La gran majoria són arrays de clsCamps. Poso el codi comentat on es veu clarament que fa cada un d'aquestes variables. En les classes que hereten de la clsDades s'inicialitzen les variables amb els valors corresponents a una taula concreta de la BBDD.
publicclass clsDades
{
/// <summary>/// Prefix dels paràmetres de les stored procedures, /// útil per distingir-los de variables declarades dins de la SP./// </summary>publicstring Prefix = "p";
/// <summary>/// Paràmetres del GET, també són params del GETTOP/// </summary>public clsCamp[] ParamsGET;
/// <summary>/// Paràmetres del INS/// </summary>public clsCamp[] ParamsINS;
/// <summary>/// Paràmetres del UPD/// </summary>public clsCamp[] ParamsUPD;
/// <summary>/// Paràmetres del DEL/// </summary>public clsCamp[] ParamsDEL;
/// <summary>/// Paràmetres del QUERY/// </summary>public clsCamp[] ParamsQUERY;
/// <summary>/// Camps que retorna el GET i que seràn columnes del DataTable resultant/// </summary>public clsCamp[] CampsResultat;
/// <summary>/// Cadena de connexió a BBDD/// </summary>publicstring CadConnBBDD;
/// <summary>/// Nom de la SP que fa el GET, p. ex: CLIENTS_GET/// </summary>publicstring spGET;
/// <summary>/// Nom de la SP que fa el INS, p. ex: CLIENTS_INS/// </summary>publicstring spINS;
/// <summary>/// Nom de la SP que fa el UPD, p. ex: CLIENTS_UPD/// </summary>publicstring spUPD;
/// <summary>/// Nom de la SP que fa el DEL, p. ex: CLIENTS_DEL/// </summary>publicstring spDEL;
/// <summary>/// Nom de la SP que fa el QUERY, p. ex: CLIENTS_QUERY/// </summary>publicstring spQUERY;
/// <summary>/// Nom de la SP que fa el COUNT, p. ex: CLIENTS_COUNT/// </summary>publicstring spCOUNT;
/// <summary>/// Objecte conexió a BBDD. Útil per si es vol fer servir/// tota l'estona la mateixa conexió, sense obrir i tancar cada cop./// </summary>publicSqlConnection SQLConn = null;
/// <summary>/// Objecte que conté la transacció. És opcional fer-ne servir./// </summary>public SqlTransaction SQLTrans = null;
/// <summary>/// Indica que la taula té un camp identat/// </summary>publicbool TeIdentitat = false;
/// <summary>/// Indica si la taula fa servir una cache de JSON/// </summary>publicbool TeCache = false;
/// <summary>/// Indica si un INS/DEL/UPD netegen la cache automàticament/// </summary>publicbool AutoNetejaCache = true;
/// <summary>/// Nom de la taula/// </summary>publicstring NomTaula = "";
/// <summary>/// Nom del camp identitat/// </summary>publicstring Identitat = "";
/// <summary>/// Llista dels camps que es serialitzen per enviar al navegador/// </summary>public clsCamp[] Serialitzar = null;
/// <summary>/// Llista de camps que es deserialitzen quan arriben del navegador/// </summary>public clsCamp[] Deserialitzar = null;
//Canviar de projecte en projecte,
//ha de tenir el parametre Taula varchar(50) i el parametre URL varchar(500)privatestring SP_Cache = "DI_EXP_CACHE_DEL";
}
#11/12/2013 11:27 Programació C# Autor: Alex Canalda
En el desenvolupament d'aplicacions web cal una infraestructura per accedir a les dades a la BBDD, es a dir les stored procedures (SP) i també cal una forma fàcil per invocar-les. I a ser possible que NO sigui muntar les crides a mà. Per això he muntat la clsDades.
La clsDades és la classe principal que conté tots els métodes d'accés a BBDD. S'encarrega d'invocar les SP en funció de la definició concreta per cada taula. Es per això que la resta de classes hereden d'aquesta, i l'amplien definint amb detall els diferents paràmetres de cada SP.
Per tan la clsDades, de fet, conté els mètodes, mentre que la definició la tenen les classes particulars corresponents a cada taula. Per això la clsDades conté la declaració de les llistes de paràmetres i la inicialització es troba separada.
El primer que cal definir són les classes de suport que fa servir la clsDades, es a dir la classe Tipus i la classe clsCamp. Concretament la clsDades conté vectors d'objectes clsCamp destinats a construir els paràmetres de les SP. Per això detallaré aquestes classes primer, a demés que són força simples.
La clase Tipus indica el tipus (valgui la redundància) de BBDD, es farà servir per indicar el tipus en la clase Camp i després en el codi queda més clar.
publicstaticclass Tipus
{
publicconstint bit = 1;
publicconstint tint = 2;
publicconstint bigint = 3;
publicconstint nvarchar = 4;
publicconstint datetime = 5;
publicconstint nchar = 6;
publicconstint dec = 7;
publicconstint varchar = 8;
publicconstint chr = 9;
publicconstint date = 10;
publicconstint time = 11;
}
La classe camp també és força senzilla, conté informació d'un camp de BBDD.