Per implementar la cache en la clsDades calen diferents parts: la taula, les stored procedures (SP) i el codi per consultar i invalidar parts de la cache.
En el primer post ja he parlat de la taula, les SP són les normals per qualsevol taula (GET, INS, DEL, UPD), excepte que com no hi ha camps del grid no es genera la SP QUERY per la taula CACHE.
Cal centrar-se doncs en la consulta a la cache i com s'invalida un conjunt de registres de la cache quan hi ha una operació de modificació de dades (UPD, DEL, INS). La consulta de la cache consisteix en modificar la funció que genera els grids en la clsHelper. Aquesta consisteix en el següent codi:
if (TA.TeCache)
{
URL.AppendFormat("{0}{1}", Ordenacio.ToString(), SentitOrdre.ToString());
URL.AppendFormat("{0}{1}", Req["rows"], Req["page"]);
ParamsCache.Add("Taula", TA.NomTaula);
ParamsCache.Add("URL", URL.ToString());
ParamsCache.Add("GridID", GridID);
TBL = Cache.GET(ParamsCache);
if (TBL.Rows.Count != 0)
{
//Cache hit
Result = TBL.Rows[0]["JSON"].ToString();
return Result;
}
}
Si es dóna un cache miss (no es troben les dades a la cache) es fa un processat normal, però al final de tot es grava el resultat a la cache.
//Es un MISS cal fer un insert a la cacheif (TA.TeCache)
{
Params.Clear();
Params.Add("Taula", TA.NomTaula);
Params.Add("URL", URL.ToString());
Params.Add("GridID", GridID);
Params.Add("JSON", Result);
Cache.INS(Params);
}
return Result;
Fins aqui és el que tracta l'obtenció de dades de la cache, ara toca la invalidació de la cache. Això es fa dins de la clsDades. Es pot configurar per que la clsDades per cada INS/UPD/DEL automàticament buidi la cache cada cop o cridar al mètode de neteja de cache explícitament des de la capa superior. A les funcions INS/UPD/DEL es posa el següent tros de codi:
if (TeCache && AutoNetejaCache)
{
ClearCache();
}
La clsDades té la funció ClearCache per tal que es pugui netejar explícitament la cache.
Dins del desenvolupament web hi ha un punt que a vegades no es té en compte quan es desenvolupa: la quantitat de dades a tractar en una taula. Quan aplicació entra en manteniment és habitual rebre peticions de que en algun punt l'aplicació va lenta, per que quan es va desenvolupar no es va valorar correctament l'estructura d'aquell punt i ara es pateixen les conseqüències.
Igualment quan hi ha un munt de dades el rendiment tard o d'hora es resenteix, és pot posar un hardware més potent, línies amb més capacitat però el pressupost marca el límit. És aleshores quan s'han de buscar alternatives. En el cas de la clsDades consisteix en activar la caché per una taula en concret. Normalment quan s'executa una stored procedure QUERY contra una taula amb milions de registres aquesta triga el seu temps, per molt que estiguin ben posats els indexos, total per extreure 20-50 registres d'entre milions. Aquests registres després s'empaqueten en JSON i s'envien al navegador de l'usuari. La caché es dedica a guardar els JSONs més usats, de tal manera que
Per aconseguir això calen varies coses:
Una taula on guardar el JSON, la taula que es diu CACHE.
Stored procedures per accedir a aquesta taula.
Interceptar en el codi les peticions dels grids per tal de veure si cal consultar la cache.
Interceptar en el codi els INS/UPD/DEL que invaliden la cache.
Començaré per taula cache que té la següent estructura:
Taula: camp que guarda quina taula estem cachejant.
Pantalla: pantalla on està el grid que estem cachejant.
Grid: ID del grid que estem cachejant.
Pag: pàgina del grid.
NumRegs: número de registres de la pàgina.
SortOrder: ordre del grid.
Filtre: filtre aplicat per obtenir aquests registres. Normalment el trec de la URL i el poso aqui tal com raja.
El codi de creació de la taula:
CREATETABLE [dbo].[CACHE](
[Taula] [nvarchar](20) NOT NULL,
[Pantalla] [nvarchar](20) NOT NULL,
[Grid] [nvarchar](20) NOT NULL,
[Pag] [int] NOT NULL,
[NumRegs] [int] NOT NULL,
[SortOrder] [nvarchar](30) NOT NULL,
[Filtre] [nvarchar](200) NOT NULL,
[Resultat] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_DI_EXP_CACHE] PRIMARY KEY CLUSTERED
(
[Taula] ASC,
[Pantalla] ASC,
[Grid] ASC,
[Pag] ASC,
[NumRegs] ASC,
[SortOrder] ASC,
[Filtre] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
La resta de temes els tractaré en el següent post.
#13/03/2014 16:48 Programació C# SQLServer Autor: Alex Canalda
Tots els posts de programació d'aquest blog dedicats a SQLServer o a la clsDades o als ASHX fan referència a la mecanització. I això en que consisteix? La resposta és en treballar més en menys temps, o fer que un programa programi en lloc meu... Es tracta de treure la part repetitiva de la programació i que la faci un programa.
És hora d'anar veient amb detall que és això de la mecanització, com funciona la eina que genera codi i fins a on arriba.
Una mica d'història (les batalletes de l'avi): quan vaig començar amb la v0.1 tenia stored procedures fetes amb Oracle. Un company les muntava i jo tenia que fer les invocacions. Eren SP molt grosses i em moria fent les invocacions, sortien errors com: "tipo o número de parámetros incorrectos" i clar posat a revisar 80 i pico paràmetres i veient que no m'hagués equivocat en cap tipus i/o longitud.
Amb aquest panorama desolador vaig fer un programa sense interface d'usuari on tot estava "hardcoded" (posat a foc al codi font) i modificant el programa et generava la classe on hi havia la crida a la SP de torn.
El projecte va acabar i amb l'experiència acumulada vaig anar a pel següent. Aquí el company que feia les SP no hi era, les tenia que fer jo. I llavors ja era letal, així que vaig afegir l'opció al programa que donada una taula generés unes SP bàsiques. Però clar era molt rudimentari modificar el codi font d'un programa per generar SPs i classes. Aleshores vaig començar a treballar en un interface rudimentari d'usuari. Ja era un avanç per que podia guardar en fitxers XML informació sobre la generació de SP, classes, etc...
Primera versió amb interface del generador.
Posteriorment vaig veure que per motius de rendiment calia fer més ajustos, que a la pantalla es podien fer més coses, més opcions a tot arreu... és quan vaig fer el Generador v2. La v2 ja ha passat per uns quants projectes i està més polida amb temes de rendiment, bugs, ... Però ja l'explicaré més endavant.
A vegades fent un webservice m'he trobat amb la necessitat d'enviar objectes, o en interfaces JSON fer objectes per enviar-los. En ambdós casos és necessari convertir un DataRow a un objecte. O un DataTable a una llista d'objectes.
Com sempre he afegit a la clsDades les funcions pertinents. El primer que cal entendre és la reflection que és obtenir informació d'objectes (propietats, tipus de dades, longituds) en temps d'execució i actuar en conseqüència.
La primera funció que converteix una taula a una llista d'objectes és molt restrictiva, ja que els noms de les columnes dels registres han de coincidir amb el nom de les propietats del objecte. També el tipus ha de coincidir, encara que el més recomanable és que ambdós siguin cadenes de text (strings).
publicList<T> ToList<T>(DataTable tbl) where T : new()
{
List<T> theList = newList<T>();
T item;
List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
foreach (DataRow DR in tbl.Rows)
{
item = CrearItem<T>(DR, properties);
theList.Add(item);
}
return theList;
}
/// <summary>/// Crea un objecte a partir d'un DataRow. /// L'objecte destí ha de tenir els mateixos noms a las propietats i tipus que els camps a la BBDD./// </summary>/// <typeparam name="T">El tipus de l'objecte a crear</typeparam>/// <param name="row">El DataRow</param>/// <param name="properties">Les propietats de l'objecte</param>/// <returns>L'objecte</returns>public T CrearItem<T>(DataRow row, List<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (PropertyInfo property in properties)
{
property.SetValue(item, row[property.Name], null);
}
return item;
}
Aquesta funció és molt limitada, llavors he fet una altra que inclou els Mappings. Es a dir que les columnes de la taula poden tenir un nom diferent del de les propietats de l'objecte que es crea. Aprofitant la informació que es dels camps es pot fer una conversió de tipus, sempre i quan siguin tipus compatibles el de la columna i el de la propietat de l'objecte. El codi canvia força:
/// <summary>/// Converteix una taula a una llista d'objectes/// </summary>/// <typeparam name="T">El tipus de l'objecte a crear</typeparam>/// <param name="tbl">La taula a convertir</param>/// <param name="Mappings">Mapeig entre nom de propietat i camp del registre</param>/// <returns>La llista d'objectes</returns>publicList<T> ToList<T>(DataTable tbl, Dictionary<string, string> Mappings) where T : new()
{
List<T> theList = newList<T>();
T item;
List<PropertyInfo> properties = typeof(T).GetProperties().ToList();
foreach (DataRow DR in tbl.Rows)
{
item = CrearItem<T>(DR, properties, Mappings, true);
theList.Add(item);
}
return theList;
}
/// <summary>/// Crea un objecte a partir d'un DataRow./// L'objecte destí ha de tenir els mateixos tipus que els camps a la BBDD donat el mapping o bé strings./// </summary>/// <typeparam name="T">El tipus de l'objecte a crear</typeparam>/// <param name="DR">El DataRow</param>/// <param name="properties">Les propietats de l'objecte</param>/// <param name="Mappings">Mapeig entre nom de propietat i camp del registre</param>/// <param name="UseCampInfo">Parametre per distingir la sobrecàrrega</param>/// <returns></returns>public T CrearItem<T>(DataRow DR, List<PropertyInfo> properties,
Dictionary<string, string> Mappings, bool UseCampInfo) where T : new()
{
T item = new T();
DateTime ValorData;
Decimal ValorDecimal;
clsCamp Camp;
foreach (PropertyInfo property in properties)
{
if (Mappings.ContainsKey(property.Name))
{
Camp = BuscaCamp(Mappings[property.Name]);
if (Camp == null)
{
thrownewException("Camp " + Mappings[property.Name] + " no trobat");
}
else
{
if (!DR.IsNull(Camp.NomCamp))
{
switch (Camp.Tipus)
{
case Tipus.date:
if (property.PropertyType == Type.GetType("System.String"))
{
ValorData = (DateTime)DR[Camp.NomCamp];
if (string.IsNullOrEmpty(Camp.Format)) property.SetValue(item,
ValorData.ToShortDateString(), null);
else property.SetValue(item, ValorData.ToString(Camp.Format), null);
}
else
{
property.SetValue(item, DR[Camp.NomCamp], null);
}
break;
case Tipus.datetime:
if (property.PropertyType == Type.GetType("System.String"))
{
ValorData = (DateTime)DR[Camp.NomCamp];
if (string.IsNullOrEmpty(Camp.Format)) property.SetValue(item,
ValorData.ToString("dd/MM/yyyy HH:mm"), null);
else property.SetValue(item, ValorData.ToString(Camp.Format), null);
}
else
{
property.SetValue(item, DR[Camp.NomCamp], null);
}
break;
case Tipus.time:
if (property.PropertyType == Type.GetType("System.String"))
{
ValorData = (DateTime)DR[Camp.NomCamp];
if (string.IsNullOrEmpty(Camp.Format)) property.SetValue(item,
ValorData.ToString("HH:mm"), null);
else property.SetValue(item, ValorData.ToString(Camp.Format), null);
}
else
{
property.SetValue(item, DR[Camp.NomCamp], null);
}
break;
case Tipus.dec:
if (property.PropertyType == Type.GetType("System.String"))
{
ValorDecimal = (Decimal)DR[Camp.NomCamp];
if (string.IsNullOrEmpty(Camp.Format)) property.SetValue(item,
ValorDecimal.ToString(), null);
else property.SetValue(item, ValorDecimal.ToString(Camp.Format), null);
}
else
{
property.SetValue(item, DR[Camp.NomCamp], null);
}
break;
default:
if (property.PropertyType == Type.GetType("System.String"))
property.SetValue(item, DR[Camp.NomCamp].ToString(), null);
else property.SetValue(item, DR[Camp.NomCamp], null);
break;
}
}
}
}
}
return item;
}
private clsCamp BuscaCamp(string pCamp)
{
for (int i = 0; i < CampsResultat.Length; i++)
{
if (CampsResultat[i].NomCamp == pCamp)
{
return CampsResultat[i];
}
}
returnnull;
}
#06/03/2014 11:46 Programació C# Autor: Alex Canalda
Un sitemap és un llistat de URLs que té un format definit per la Sitemap.org i que s'envia als motors de cerca per tal que les indexin. El seu format és el següent:
He fet un petit programa en C# que genera aquest XML, he vist exemples on es fan servir objectes XMLDocument i es van afegint nodes fills etc... Donat que no és gaire complicat jo ho fet en cadenes de text directament. Apart d'això només s'ha de posar el Content-Type que toca: text/xml El codi a continuació
Hi ha moltes maneres de fer això, la normal és fer servir una collation (ordre alfabètic) diferent al de la BBDD i l'altra fent servir "[" i "]" (expressions regulars).
La collation fa que les consultes quedin algo com:
SELECT *
FROM Begudes
WHERE
Name COLLATE Latin1_general_CI_AI Like'%cafe%'COLLATE Latin1_general_CI_AI
Això trobarà "cafè", "café"... Però a mi no m'agrada gaire. Prefereixo un altra mètode.
Es tracta de substituir totes les vocals de la cerca per [aàáAÀÁ], [eèéEÈÉ]... quedaria:
SELECT *
FROM Begudes
WHERE
Name Like'%c[aàáAÀÁ]f[eèéEÈÉ]%'
No he trobat dades de rendiment ni recomanacions, tampoc en els llocs on ho he necessitat he vist que funcionés malament. El que sí he necessitat és una funció que em substitueixi les vocals per l'expressió regular. L'he afegit a la clsDades.
/// <summary>/// Donat un string que formarà part d'un LIKE el torna ACCENT INSENSITIVE/// </summary>/// <param name="Valor">L'string a convertir</param>/// <returns>L'string convertit</returns>privatestring FerAccentInsesitive(string Valor)
{
string Result = Valor;
string a = "[aàáAÀÁ]";
string e = "[eèéEÈÉ]";
string i = "[iìíIÌÍïÏ]";
string o = "[oòóOÒÓ]";
string u = "[uùúUÙÚüÜ]";
Result = Result.Replace("a", "_a_");
Result = Result.Replace("e", "_e_");
Result = Result.Replace("i", "_i_");
Result = Result.Replace("o", "_o_");
Result = Result.Replace("u", "_u_");
Result = Result.Replace("à", "_à_");
Result = Result.Replace("è", "_è_");
Result = Result.Replace("ì", "_ì_");
Result = Result.Replace("ò", "_ò_");
Result = Result.Replace("ù", "_ù_");
Result = Result.Replace("á", "_á_");
Result = Result.Replace("é", "_é_");
Result = Result.Replace("í", "_í_");
Result = Result.Replace("ó", "_ó_");
Result = Result.Replace("ú", "_ú_");
Result = Result.Replace("A", "_A_");
Result = Result.Replace("E", "_E_");
Result = Result.Replace("I", "_I_");
Result = Result.Replace("O", "_O_");
Result = Result.Replace("U", "_U_");
Result = Result.Replace("À", "_À_");
Result = Result.Replace("È", "_È_");
Result = Result.Replace("Ì", "_Ì_");
Result = Result.Replace("Ò", "_Ò_");
Result = Result.Replace("Ù", "_Ù_");
Result = Result.Replace("Á", "_Á_");
Result = Result.Replace("É", "_É_");
Result = Result.Replace("Í", "_Í_");
Result = Result.Replace("Ó", "_Ó_");
Result = Result.Replace("Ú", "_Ú_");
//----------------------------------------
Result = Result.Replace("_a_", a);
Result = Result.Replace("_e_", e);
Result = Result.Replace("_i_", i);
Result = Result.Replace("_o_", o);
Result = Result.Replace("_u_", u);
Result = Result.Replace("_à_", a);
Result = Result.Replace("_è_", e);
Result = Result.Replace("_ì_", i);
Result = Result.Replace("_ò_", o);
Result = Result.Replace("_ù_", u);
Result = Result.Replace("_á_", a);
Result = Result.Replace("_é_", e);
Result = Result.Replace("_í_", i);
Result = Result.Replace("_ó_", o);
Result = Result.Replace("_ú_", u);
Result = Result.Replace("_A_", a);
Result = Result.Replace("_E_", e);
Result = Result.Replace("_I_", i);
Result = Result.Replace("_O_", o);
Result = Result.Replace("_U_", u);
Result = Result.Replace("_À_", a);
Result = Result.Replace("_È_", e);
Result = Result.Replace("_Ì_", i);
Result = Result.Replace("_Ò_", o);
Result = Result.Replace("_Ù_", u);
Result = Result.Replace("_Á_", a);
Result = Result.Replace("_É_", e);
Result = Result.Replace("_Í_", i);
Result = Result.Replace("_Ó_", o);
Result = Result.Replace("_Ú_", u);
return Result;
}
#03/03/2014 11:27 Programació C# SQLServer Autor: Alex Canalda
Aquesta és fàcil, el DEL és una sentència curta. Es tracta de construir un DELETE amb la clau primària informada (PK). El codi és força curt, encara que hi ha una part algo llarga per les taules que no fan servir identitats com PK i potser tenen una PK composta per alguns camps. Disponible en la clsDades.
publicstring DEL_Builder(Dictionary<string, string> DR)
{
DateTime ValorDateTime;
StringBuilder SB = newStringBuilder();
bool Primer = true;
SB.AppendFormat("DELETE FROM {0} WHERE ", NomTaula);
if (TeIdentitat)
{
SB.AppendFormat("{0}={1}", Identitat, DR[Identitat]);
}
else
{
foreach (clsCamp Camp in CampsResultat)
{
if (Camp.PK)
{
if (!Primer) SB.Append(" AND ");
else Primer = false;
switch (Camp.Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, DR[Camp.NomCamp].Replace("'", "''"));
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DateTime.Parse(DR[Camp.NomCamp]);
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, FDateSQL(ValorDateTime));
break;
caseTipus.dec:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp].Replace(",", "."));
break;
caseTipus.bit:
if (DR[Camp.NomCamp].ToString().ToLower() == "true") SB.AppendFormat("{0}=1, ", Camp.NomCamp);
else SB.AppendFormat("{0}=0", Camp.NomCamp);
break;
default:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp]);
break;
}
}
}
}
return SB.ToString();
}
publicstring DEL_Builder(DataRow DR)
{
DateTime ValorDateTime;
StringBuilder SB = newStringBuilder();
bool Primer = true;
SB.AppendFormat("DELETE FROM {0} WHERE ", NomTaula);
if (TeIdentitat)
{
SB.AppendFormat("{0}={1}", Identitat, DR[Identitat]);
}
else
{
foreach (clsCamp Camp in CampsResultat)
{
if (Camp.PK)
{
if (!Primer) SB.Append(" AND ");
else Primer = false;
switch (Camp.Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, DR[Camp.NomCamp].ToString().Replace("'", "''"));
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DR.Field<DateTime>(Camp.NomCamp);
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, FDateSQL(ValorDateTime));
break;
caseTipus.dec:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp].ToString().Replace(",", "."));
break;
caseTipus.bit:
if (DR[Camp.NomCamp].ToString().ToLower() == "true") SB.AppendFormat("{0}=1, ", Camp.NomCamp);
else SB.AppendFormat("{0}=0", Camp.NomCamp);
break;
default:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp]);
break;
}
}
}
}
return SB.ToString();
}
#28/02/2014 11:21 Programació C# SQLServer Autor: Alex Canalda
Aquestes funcions s'encarreguen de muntar un SQL amb un UPDATE per actualitzar un registre de la taula. Si no hi ha stored procedures es pot recórrer a això. Com sempre en dos sabors, un amb DataRow i un amb Dictionary. Quan es munta el WHERE el les taules que tenen identitat normalment serà fàcil, si no cal suportar que hi hagi una clau primària (PK) composta, per això cal fer un bucle per posar tots els camps i valors de la PK. Disponible a la clsDades
publicstring UPD_Builder(Dictionary<string, string> DR)
{
StringBuilder SB = newStringBuilder();
DateTime ValorDateTime;
bool Primer = true;
SB.AppendFormat("UPDATE {0} SET ", NomTaula);
for (int i = 0; i < CampsResultat.Length; i++)
{
if (!CampsResultat[i].PK)
{
if (!DR.ContainsKey(CampsResultat[i].NomCamp))
{
thrownewException("La fila no té el camp " + CampsResultat[i].NomCamp);
}
if (string.IsNullOrWhiteSpace(DR[CampsResultat[i].NomCamp]))
{
if (!CampsResultat[i].Nulable)
thrownewException("Camp " + CampsResultat[i].NomCamp + " no admet valors NULLs");
else
{
SB.AppendFormat("{0}=NULL, ", CampsResultat[i].NomCamp);
}
}
else
{
switch (CampsResultat[i].Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.AppendFormat("{0}='{1}', ",
CampsResultat[i].NomCamp, DR[CampsResultat[i].NomCamp].Replace("'", "''"));
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DateTime.Parse(DR[CampsResultat[i].NomCamp]);
SB.AppendFormat("{0}='{1}', ",
CampsResultat[i].NomCamp, FDateSQL(ValorDateTime));
break;
caseTipus.dec:
SB.AppendFormat("{0}={1}, ",
CampsResultat[i].NomCamp, DR[CampsResultat[i].NomCamp].Replace(",", "."));
break;
caseTipus.bit:
if (DR[CampsResultat[i].NomCamp].ToLower() == "true")
SB.AppendFormat("{0}=1, ", CampsResultat[i].NomCamp);
else SB.AppendFormat("{0}=0, ", CampsResultat[i].NomCamp);
break;
default:
SB.AppendFormat("{0}={1}, ", CampsResultat[i].NomCamp, DR[CampsResultat[i].NomCamp]);
break;
}
}
}
}
SB.Remove(SB.Length - 2, 2);
//La clau primaria
SB.Append(" WHERE ");
if (TeIdentitat)
{
SB.AppendFormat("{0}={1}", Identitat, DR[Identitat]);
}
else
{
foreach (clsCamp Camp in CampsResultat)
{
if (Camp.PK)
{
if (!Primer) SB.Append(" AND ");
else Primer = false;
switch (Camp.Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, DR[Camp.NomCamp].Replace("'", "''"));
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DateTime.Parse(DR[Camp.NomCamp]);
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, FDateSQL(ValorDateTime));
break;
caseTipus.dec:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp].Replace(",", "."));
break;
caseTipus.bit:
if (DR[Camp.NomCamp].ToString().ToLower() == "true")
SB.AppendFormat("{0}=1, ", Camp.NomCamp);
else SB.AppendFormat("{0}=0", Camp.NomCamp);
break;
default:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp]);
break;
}
}
}
}
return SB.ToString();
}
publicstring UPD_Builder(DataRow DR)
{
StringBuilder SB = newStringBuilder();
DateTime ValorDateTime;
bool Primer = true;
SB.AppendFormat("UPDATE {0} SET ", NomTaula);
for (int i = 0; i < CampsResultat.Length; i++)
{
if (!CampsResultat[i].PK)
{
if (!DR.Table.Columns.Contains(CampsResultat[i].NomCamp))
{
thrownewException("La fila no té el camp " + CampsResultat[i].NomCamp);
}
if (DR.IsNull(CampsResultat[i].NomCamp))
{
if (!CampsResultat[i].Nulable)
thrownewException("Camp " + CampsResultat[i].NomCamp + " no admet valors NULLs");
else
{
SB.AppendFormat("{0}=NULL, ", CampsResultat[i].NomCamp);
}
}
else
{
switch (CampsResultat[i].Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.AppendFormat("{0}='{1}', ", CampsResultat[i].NomCamp,
DR[CampsResultat[i].NomCamp].ToString().Replace("'", "''"));
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DR.Field<DateTime>(CampsResultat[i].NomCamp);
SB.AppendFormat("{0}='{1}', ", CampsResultat[i].NomCamp, FDateSQL(ValorDateTime));
break;
caseTipus.dec:
SB.AppendFormat("{0}={1}, ", CampsResultat[i].NomCamp,
DR[CampsResultat[i].NomCamp].ToString().Replace(",", "."));
break;
caseTipus.bit:
if (DR[CampsResultat[i].NomCamp].ToString().ToLower() == "true")
SB.AppendFormat("{0}=1, ", CampsResultat[i].NomCamp);
else SB.AppendFormat("{0}=0, ", CampsResultat[i].NomCamp);
break;
default:
SB.AppendFormat("{0}={1}, ", CampsResultat[i].NomCamp, DR[CampsResultat[i].NomCamp]);
break;
}
}
}
}
SB.Remove(SB.Length - 2, 2);
//La clau primaria
SB.Append(" WHERE ");
if (TeIdentitat)
{
SB.AppendFormat("{0}={1}", Identitat, DR[Identitat]);
}
else
{
foreach (clsCamp Camp in CampsResultat)
{
if (Camp.PK)
{
if (!Primer) SB.Append(" AND ");
else Primer = false;
switch (Camp.Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, DR[Camp.NomCamp].ToString().Replace("'", "''"));
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DR.Field<DateTime>(Camp.NomCamp);
SB.AppendFormat("{0}='{1}'", Camp.NomCamp, FDateSQL(ValorDateTime));
break;
caseTipus.dec:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp].ToString().Replace(",", "."));
break;
caseTipus.bit:
if (DR[Camp.NomCamp].ToString().ToLower() == "true") SB.AppendFormat("{0}=1, ", Camp.NomCamp);
else SB.AppendFormat("{0}=0", Camp.NomCamp);
break;
default:
SB.AppendFormat("{0}={1}", Camp.NomCamp, DR[Camp.NomCamp]);
break;
}
}
}
}
return SB.ToString();
}
#27/02/2014 15:50 Programació C# SQLServer Autor: Alex Canalda
Per raons que no arribo a entendre a vegades hi ha projectes on el client no vol stored procedures. O potser es requereix fer una migració de dades puntual i no es volen deixar un munt d'stored procedures que ningú farà servir més endevant. Aleshores tota la feina del generador d'storeds no serveix, hi ha que fer servir una altra tècnica.
Per això he mogut uns quants mètodes del generador d'stored a la clsDades, així la clsDades genera SQL sobre la marxa. Es a dir, aprofitant la informació sobre els camps de la taula de la que disposa una clase derivada (nom, longitud i tipus de les columnes) la clsDades ha incorporat unes funcions que generen inserts, updates i deletes sobre la marxa.
Estan disponibles en dos sabors, la que admet DataRow i la que admet Dictionary's, són calcades amb petites modificacions. Començaré pel constructor d'INSerts i deixaré per altres posts els UPD i DEL.
publicstring INS_Builder(Dictionary<string, string> DR, bool IdentityInsert)
{
DateTime ValorDateTime;
StringBuilder SB = newStringBuilder();
SB.AppendFormat("INSERT INTO {0} (", NomTaula);
for (int i = 0; i < CampsResultat.Length; i++)
{
if (!CampsResultat[i].PK || IdentityInsert)
{
SB.Append(CampsResultat[i].NomCamp + ", ");
}
}
//Treiem l'ultima , i espai
SB.Remove(SB.Length - 2, 2);
SB.Append(") VALUES (");
for (int i = 0; i < CampsResultat.Length; i++)
{
if (!CampsResultat[i].PK || IdentityInsert)
{
if (!DR.ContainsKey(CampsResultat[i].NomCamp))
{
thrownewException("La fila no té el camp " + CampsResultat[i].NomCamp);
}
if (string.IsNullOrWhiteSpace(DR[CampsResultat[i].NomCamp]))
{
if (!CampsResultat[i].Nulable)
thrownewException("Camp " + CampsResultat[i].NomCamp + " no admet valors NULLs");
else
{
SB.Append("NULL, ");
}
}
else
{
switch (CampsResultat[i].Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.Append("'" + DR[CampsResultat[i].NomCamp].Replace("'", "''") + "', ");
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DateTime.Parse(DR[CampsResultat[i].NomCamp]);
SB.Append("'" + FDateSQL(ValorDateTime) + "', ");
break;
caseTipus.dec:
SB.Append(DR[CampsResultat[i].NomCamp].Replace(",", ".") + ", ");
break;
caseTipus.bit:
if (DR[CampsResultat[i].NomCamp].ToLower() == "true" ) SB.Append("1, ");
else SB.Append("0, ");
break;
default:
SB.Append(DR[CampsResultat[i].NomCamp] + ", ");
break;
}
}
}
}
SB.Remove(SB.Length - 2, 2);
SB.Append(")");
return SB.ToString();
}
publicstring INS_Builder(DataRow DR, bool IdentityInsert)
{
DateTime ValorDateTime;
Boolean ValorBool;
StringBuilder SB = newStringBuilder();
SB.AppendFormat("INSERT INTO {0} (", NomTaula);
for (int i = 0; i < CampsResultat.Length; i++)
{
if (!CampsResultat[i].PK || IdentityInsert)
{
SB.Append(CampsResultat[i].NomCamp + ", ");
}
}
//Treiem l'ultima , i espai
SB.Remove(SB.Length - 2, 2);
SB.Append(") VALUES (");
for (int i = 0; i < CampsResultat.Length; i++)
{
if (!CampsResultat[i].PK || IdentityInsert)
{
if (!DR.Table.Columns.Contains(CampsResultat[i].NomCamp))
{
thrownewException("La fila no té el camp " + CampsResultat[i].NomCamp);
}
if (DR.IsNull(CampsResultat[i].NomCamp))
{
if (!CampsResultat[i].Nulable)
thrownewException("Camp " + CampsResultat[i].NomCamp + " no admet valors NULLs");
else
{
SB.Append("NULL, ");
}
}
else
{
switch (CampsResultat[i].Tipus)
{
caseTipus.nchar:
caseTipus.nvarchar:
caseTipus.chr:
caseTipus.varchar:
SB.Append("'" + DR[CampsResultat[i].NomCamp].ToString().Replace("'", "''") + "', ");
break;
caseTipus.datetime:
caseTipus.date:
caseTipus.time:
ValorDateTime = DR.Field<DateTime>(CampsResultat[i].NomCamp);
SB.Append("'" + FDateSQL(ValorDateTime) + "', ");
break;
caseTipus.dec:
SB.Append(DR[CampsResultat[i].NomCamp].ToString().Replace(",", ".") + ", ");
break;
caseTipus.bit:
ValorBool = DR.Field<Boolean>(CampsResultat[i].NomCamp);
if (ValorBool) SB.Append("1, ");
else SB.Append("0, ");
break;
default:
SB.Append(DR[CampsResultat[i].NomCamp].ToString() + ", ");
break;
}
}
}
}
SB.Remove(SB.Length - 2, 2);
SB.Append(")");
return SB.ToString();
}
Cal tenir en compte que si després d'un INSERT es vol obtenir el valor d'una columna identitat cal fer servir la funció IDENT_CURRENT. Si es volen agrupar varies sentències SQL s'haurà de fer en un StringBuilder on es vagin acumulant. Les versions de INS/UPD que reben un Dictionary encaixen amb el Deserialitzador que retorna també un Dictionary.
#27/02/2014 11:51 Programació C# SQLServer Autor: Alex Canalda
Hi ha dues paraules que són molt fashion i queden molt bé quan es parla de programació:
Mockup: maqueta. En programació es un prototipus molt bàsic. Per fer "mockups" es poden fer servir papers i bolígrafs o un software al respecte, com per exemple el de Balsamiq. El trobo molt útil per dibuixar formularis d'aplicacions.
Scaffolding: andami. En programació un andami es refereix a la infraestructura bàsica necessària per fer funcionar una pantalla, però sense lògica de negoci. Diguem que la clsDades i les stored procedures associades fan això, scaffolding.
#26/02/2014 17:20 Programació Anglès Software Autor: Alex Canalda