Aquest troç de codi es correspon al botó ADD del Generador. El codi està dividit en dos parts, una del botó pròpiament dit i l'altra de l'operació. El codi del botó està en el codi del formulari (no podria estar en un altra lloc), mentre que el codi que genera la sentència SQL està en la clsCamp. Primer el codi del formulari, bàsicament és un IF que distingeix on enviar l'operació, si a un servidor de BBDD o bé a un fitxer. Quan és l'opcio de BBDD cal obrir i tancar connexió.
Ara els dos mètodes de la clsCamp (1 sobrecarregat a 2) amb l'operació ALTER TABLE ADD.... La clsCamp té tota la informació necessària per muntar la sentència.
La part del Generador que s'encarrega de gestionar els camps d'una taula no és gaire complicada. Aquests camps són els que retorna el GET (SELECT).
Nom: Nom del camp.
Taula: Nom de la taula on està el camp, si hi ha JOINs és possible que no tots els camps siguin de la mateixa taula.
As: Pot passar que camps de diferents taules tinguin el mateix nom, aleshores cal distingir-los posant un altra nom.
Format: només cal posar-ho en camps que permetin ser formatats, dates o números decimals. Els formats més típics són: dd/MM/yyyy HH:mm:ss, o HH:mm, o {0:0.00}.
Tipus: tipus de dada del camp, si es vol modificar cal fer-ho amb el client de BBDD que toqui. Si es vol actualitzar amb el nou tipus (es modifica la longitud del camp o algo i cal reflectir-ho al Generador) cal fer servir el botó "Ob. Tipus".
Serialize: indica que aquest camp s'envia al navegador. Post sobre la serialització.
Deserialize: indica que quan en el navegador es dona al botó "Guardar" es recupera el valor del camp per alimentar a la SP UPD. Post sobre la deserialització.
PK: indica que aquest camp forma part de la clau primària (Primary Key).
Identitat: si el camp està marcat el camp és una identitat.
Nulable: el camp admet NULLs.
Sortable: només es fa servir pels GRIDs, indica que la columna serà ordenable ASC/DESC.
Columna grid: vol dir que pertany a una columna del Grid. Es possible que si no es fan servir Grids per la taula en concret no hi hagi cap camp marcat com columna grid. Aleshores la SP QUERY no es genera.
Index: només indica que aquest camp té un índex associat, òbviament un índex d'un únic camp.
ADD/DROP/INDEX/ALTER: generen contra la BBDD el codi SQL de ADD COLUMN, DROP COLUMN, crear un index on un ALTER TABLE ALTER COLUMN.
El tros de la pantalla descrit.
#28/04/2014 12:19 Programació C# Autor: Alex Canalda
Query: Aquí es pot escollir el mètode per generar la SP QUERY, segons els diferents criteris de volum de dades, número de paràmetres de filtrat...
Sincronitza: aquest botó actualitza la informació que hi ha en el Generador respecte el que hi ha a la BBDD. Es a dir funciona BBDD -> Generador. Un cop sincronitzat la BBDD i el Generador tenen la mateixa definició de dades. És típic afegir un camp a la BBDD, aleshores es fa sincronitza, genera storeds i genera classe, per aquest ordre.
Genera storeds: amb la informació del Generador es generen totes les storeds amb les condicions indicades.
Crea taula: amb la informació del Generador crea la taula a la BBDD. Això és útil quan es crea la taula en un entorn i es vol passar a un altra. Seria funcionar Generador -> BBDD.
Crea indexs: es crean els indexos que tingui informats el Generador, també útil per migrar entre entorns.
Genera classe: amb la informació del Generador i obtenint informació de les Storeds Procedures (SP) es genera la classe derivada corresponent. Si s'actualitzen les SP cal primer generar-les i després regenerar la classe, amb aquest ordre.
Generar pantalla: crea una pantalla bàsica, amb taules HTML, etc...
Editar pantalla: si ja hi ha una pantalla generada on s'ha estat treballant afegint controls a la pantalla.
Un dels passos que fa el Generador de classes és obtenir les taules d'una BBDD. És una tasca molt senzilla. S'executa una SQL contra la taula "sysobjects" i llestos. Després es carreguen en un combo els resultats. Com sempre el codi:
btnCarregaTaules.Enabled = false;
string SQL;
DataTable TBL;
try
{
SqlConnection CONN = newSqlConnection();
CONN.ConnectionString = GestorTaules.ConnString;
CONN.Open();
SQL = "SELECT name FROM sysobjects WHERE xtype='u' ORDER BY name";
TBL = Utils.ExecuteSQL(SQL, CONN);
foreach (DataRow DR in TBL.Rows)
{
cboTaules.Items.Add(DR["name"].ToString());
}
CONN.Close();
}
catch (Exception E)
{
MessageBox.Show("Error: " + E.Message);
}
btnCarregaTaules.Enabled = true;
#14/04/2014 16:12 Programació C# SQLServer Autor: Alex Canalda
El C# té un munt de paraules reservades i cada dia que passa m'adono de quantes en desconec. Fins ara feia servir const per definir constants, però clar, constants no hi ha tantes, ja que només es poden definir quan es declaren, algo com:
publicconststring foo = "foo";
Llavors quan s'intenta fer qualsevol modificació del valor salta un error. Algo com això:
Doncs a vegades volem que una variable sigui tingui un valor que un cop assignat no es pugui modificar, en aquest cas el const no ens pot ajudar, perque el valor s'assigna en temps de compilació. Per això està el readonly, que permet assignar un valor en temps d'execució.
publicclass MyClass
{
publicreadonlystring foo = "foo";
public MyClass(string abc)
{
foo = abc; //Aquí NO hi ha error.
}
}
En canvi l'assignació fora del constructor no està permesa i sí dóna error. Si afegim aquest mètode a la classe anterior no funciona.
Les propietats readonly només es poden assignar en la declaració (com les constants) i en el constructor. Però si un objecte deriva del que conté les propietats readonly aleshores si s'intenta accedir a aquestes, encara que sigui en el constructor de l'objecte derivat, no ens deixa. Això és així per que quan s'executa el constructor de l'objecte derivat el contructor de l'objecte base ja s'ha executat. Aleshores cal una forma que quan s'invoca al constructor de l'objecte derivat es pugui invocar al base. Es fa així:
publicclass MyClass
{
publicreadonlystring foo = "foo";
public MyClass(string abc)
{
foo = abc;
}
}
publicclass MyExtClass : MyClass
{
public MyExtClass(string abc1, string abc2) : base(abc1) //Correcte!!!
{
foo = abc2; //Error el constructor base ja s'ha executat
}
}
#07/04/2014 17:36 Programació C# Autor: Alex Canalda
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.
Capçalera del Generator v2.0
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
Quan ja vaig fer la primera versió de generador de classes ràpidament es va quedar curta. Tenia que fer més coses, suportar més casos, conforme han passat projectes fent servir el generador s'han arreglat més bugs... En l'estat actual ja m'ajuda a trobar errors en el disseny, encara que podria ser més perfecte detectant coses com, que en un ASHX no hi ha el mètode corresponent a un autocomplete que s'ha definit en la part client (però això ja és molt pijo).
El que sí ha empitjorat és l'usabilitat, ara fa tantes coses que l'interface ha quedat plagat d'opcions. Tinc pendent de modificar-lo per fer un interface més amigable, separat en varies finestres, com si fos un assistent, enlloc de tot apretat com està ara. Però clar, amb el tema dels interfaces en HighDPI encara estic pensant com fer-ho.
Ara doncs començaré a desgranar el que hi ha fet, el com funciona ja ho he explicat, ara cal explicar com es generen les coses automàticament.
Segueixo veient cosetes del HTML5, aquest cop és un atribut dels camps input que es diu placeholder. Consisteix en posar un text dins d'un input de tipus text que quan l'usuari comenci a escriure desapareix. Abans això es feia amb Javascript, ara està en el llenguatge. Millor un exemple, el codi seria:
<inputname="valor"id="valor"type="text"placeholder="Entra aqui el valor" />
I quedaria algo com:
Exemple de input amb placeholder
També és curiós que el comportament no és exactament igual en tots els navegadors. En IE11 tot just el input rep el focus el text desapareix. En Firefox no, desapareix quan l'usuari comença a escriure. El valor del placeholder no s'envia mai al servidor, es a dir, si es fa un HTTP POST d'un camp buit amb placeholder aquest valor no forma part del que s'envia al servidor.
#20/03/2014 16:51 Programació HTML/CSS Autor: Alex Canalda
Quan faig un SSIS (Sql Server Integration Service) per mi, vull que quan acabi m'envii un mail amb el resultat de l'execució, tan si ha anat bé com si ha anat malament.
El primer pas és configurar un mail de BBDD.Crear un perfil nou.S'han de posar les dades típiques, usuari, pwd, servidor de correu.Cal fer-lo públic i determinat.Cal afegir un operador.És important que coincideixi el mail amb el que s'ha creat previament.Al pla de manteniment cal afegir una tasca d'avisar operador i ajuntar amb el resultat OK de la última...I també de totes les altres tasques si van malament que envii un correu (una altra tasca d'aviso operador amb un assumpte KO).
#16/03/2014 00:26 Programació SQLServer Autor: Alex Canalda