Normalment faig servir el fitxer de configuració de .NET de torn, el web.config o el que toqui. Però cada cop més em trobo amb que no puc posar allí, d'una forma fàcil i entenedora els valors que vull fer servir en una aplicació. I sempre amb por de si trencaré algun punt vital, o si ho posaré correctament...
Per aquests motius he fet la classe clsConfig. Aquí puc guardar els ajustos ("settings") de l'aplicació. Es tracta de posar un fitxer XML "config.xml" amb els valors dins de la següent forma:
Després cal una classe per llegir aquest fitxer. La clsConfig també permet grabar-hi valors, per exemple en una aplicació d'escriptori que fa servir carpetes és habitual guardar la carpeta que fa servir l'usuari (el valor de l'últim cop que l'ha fet servir). A continuació el codi corresponent:
publicclass clsConfig
{
privateDictionary<string, string> Config = newDictionary<string, string>();
publicreadonlystring FitxerConfiguracio = "config.xml";
publicbool Error { get; set; }
publicstring txtError { get; set; }
public clsConfig(string pFitxerConfiguracio)
{
if(!string.IsNullOrEmpty(pFitxerConfiguracio)) FitxerConfiguracio = pFitxerConfiguracio;
}
publicstring Ver()
{
return"1.3 - 20/05/2013";
}
publicstring GetVal(string Name)
{
string Valor = "";
if (Config.ContainsKey(Name))
{
Valor = Config[Name];
Error = false;
txtError = "";
}
else
{
Valor = "";
Error = true;
txtError = "'" + Name + "' no encontrado en la configuración";
}
return Valor;
}
publicvoid SetVal(string Name, string Value)
{
if (Config.ContainsKey(Name))
{
Config[Name] = Value;
Error = false;
txtError = "";
}
else
{
Error = true;
txtError = "'" + Name + "' no encontrado en la configuración";
}
}
publicvoid CreateVal(string Name, string Value)
{
if (Config.ContainsKey(Name))
{
Error = true;
txtError = "'" + Name + "' ya creado en la configuración";
}
else
{
Config.Add(Name, Value);
}
}
publicvoid Save()
{
string path = Application.ExecutablePath;
XmlDocument Doc = newXmlDocument();
XmlNode Root;
XmlNode Element;
XmlNode Nom;
XmlNode Valor;
Doc.LoadXml("<?xml version=\"1.0\" standalone=\"yes\"?><root></root>");
Root = Doc.SelectSingleNode("descendant::root");
foreach (KeyValuePair<String, String> Param in Config)
{
Element = Doc.CreateNode(XmlNodeType.Element, "element", "");
Nom = Doc.CreateNode(XmlNodeType.Element, "name", "");
Valor = Doc.CreateNode(XmlNodeType.Element, "value", "");
Nom.InnerText = Param.Key;
Valor.InnerText = Param.Value;
Element.AppendChild(Nom);
Element.AppendChild(Valor);
Root.AppendChild(Element);
}
path = Path.GetDirectoryName(path);
path += "\\" + FitxerConfiguracio;
Doc.Save(path);
}
public void Initialize()
{
string path = Application.ExecutablePath;
XmlDocument XMLDoc = newXmlDocument();
XmlNodeList Nodes;
XmlNode Node;
string Nom = "", Valor = "";
try
{
Error = false;
txtError = "";
path = Path.GetDirectoryName(path);
path += "\\" + FitxerConfiguracio;
XMLDoc.Load(path);
Nodes = XMLDoc.SelectNodes("descendant::element");
if (Nodes != null)
{
foreach (XmlNode N in Nodes)
{
Node = N.SelectSingleNode("descendant::name");
if (Node != null) Nom = Node.InnerText;
else Nom = "";
Node = N.SelectSingleNode("descendant::value");
if (Node != null) Valor = Node.InnerText;
else Valor = "";
Config.Add(Nom, Valor);
}
}
}
catch (Exception E)
{
txtError = E.Message;
Error = true;
}
}
}
Després es fa servir així:
clsConfig Conf = newclsConfig();
Conf.Initialize(); //Important inicialitzar per carregar els valors de la configuració
CadConn = Conf.GetVal("CadConn");
#03/04/2014 10:33 Programació C# Autor: Alex Canalda
El Generador és una aplicació Winforms (amb els seus pros i contres), però no fa servir BBDDs per guardar la seva informació. Fa servir fitxers XML. En projectes grans que hi ha diferents entorns Desenvolupament - Test - Producció el que faig és mantenir un fitxer per cada entorn. Quan la versió del software canvia d'entorn el que faig és renombrar el fitxer a l'entorn que toca i actualitzar la cadena de connexió. El propi generador de codi té el botó de "verificar esquema" que informa dels canvis necessaris per que la nova versió sigui compatible amb la BBDD, és molt útil per detectar els camps que s'han afegit o modificat a les taules en un evolutiu. D'aquesta forma es garanteix que les stored procedures funcionin bé.
Al no tenir BBDD el generador el que fa és carregar el fitxers XML a objectes, aquests objectes es posen com a DataSource de controls BindingSource. Els controls BindingSource al seu temps permeten lligar-se als controls del formulari i mostrar les dades fent servir la propietat DataBinding del control.
La classe principal que guarda tota la informació del projecte és la clsGestorTaules.
publicclass clsGestorTaules
{
/// <summary>/// Taules que es gestionen amb el generador/// </summary>publicList<clsTaula> Taules { get; set; }
/// <summary>/// Fitxer del que s'ha fet Load, es fa servir en el save/// </summary>publicstring FitxerCarregat { get; set; }
/// <summary>/// Carpeta del projecte/// </summary>publicstring ProjectFolder { get; set; }
/// <summary>/// Subcarpeta dins del projecte on es guarden/// les clases de dades (on està la clsDades)/// </summary>publicstring DataFolder { get; set; }
/// <summary>/// En el cas que el projecte tingui formularis en/// HTML, carpeta on es guarden/// </summary>publicstring HTMLFolder { get; set; }
/// <summary>/// Carpeta on es guarden els Javascripts/// </summary>publicstring JSFolder { get; set; }
/// <summary>/// Ruta del fitxer master/// </summary>publicstring MasterFile { get; set; }
/// <summary>/// Si no es generen els scripts contra BBDD, fitxer/// on es guarden els scripts./// </summary>publicstring ScriptFile { get; set; }
/// <summary>/// Carpeta on estan els fitxers XML del generador/// </summary>publicstring CarpetaFitxers { get; set; }
/// <summary>/// NameSpace de l'aplicació que es genera/// </summary>publicstring NameSpace { get; set; }
/// <summary>/// Cadena de connexió a BBDD/// </summary>publicstring ConnString { get; set; }
public clsGestorTaules()
{
Taules = newList<clsTaula>();
}
[.. Funcions de load/save..]
}
Aquestes propietats es corresponen als controls de la capçalera del Generador.
El Namespace és el que fa servir l'aplicació que s'està desenvolupant. El valor es fa servir quan es generen les classes derivades, també quan es generen pantalles.
Els radiobuttons destí indica si el SQL que genera el programa va directament a la BBDD o bé a un fitxer d'script. Això és útil quan no es té accés a l'entorn on s'ha de desplegar l'aplicació (per exemple producció) i s'han d'enviar els fitxers per que algú altra els executi.
Carpeta del projecte és la carpeta on està l'aplicació que s'està desenvolupant. Ha de tenir les carpetes de "dades", js i HTML. La carpeta dades és on està la clsDades i on es deixen els fitxers de les classes derivades. A la carpeta JS es deixen els javascripts i en la HTML les pantalles. El valor de MasterFile es fa servir per projectes web que facin servir una master page. És útil ja que permet posar tots els scripts de l'aplicació junts i comuns per tota l'aplicació, es fa servir quan es generen pantalles.
El desplegable de taules inicialment està buit, s'omple amb el botó de "Carregar taules". Amb aquest botó es carreguen les taules de la BBDD, és el pas previ a generar una taula. Generar una taula vol dir crear un objecte taula, i afegir-lo a la llista de taules del gestor. A aquest objecte taula se li afegeix tota la informació relativa a camps (tipus, longituds,...) i indexos. Un cop està generada la taula es pot començar a treballar amb els camps, però això ho veurem en un altra post.
Així mateix el botó "Generar totes les Storeds" serveix tal com indica el seu nom per actualitzar les storeds de totes les taules, es generen tots els GET, INS, UPD, ... de totes les taules.
#02/04/2014 14:30 Programació C# Autor: Alex Canalda
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
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