En la llibreria que crida a SQL Server que m'he fet falta la crida a Stored Procedures, i estic mirant de com fer-ho. He trobat llocs on fan d'una manera, hi ha per tots els gustos. El que he trobat és una forma un xic arriscada: la que fa servir el tipus "object" de C#. Primer cal veure que és el "boxing/unboxing".
int i = 123;
// La següent linia "boxes" (encapsa) i.object o = i;
Cal observar que encara que sigui un "object" la variable conserva el tipus, i també que ocupa un espai de memòria diferent a la variable que a encapsat. Es a dir, que si "i" modifica el seu valor el "object" no. Igualment un cop des-encapsada si es modifica l'object no es modifica la variable on s'ha assignat el seu valor.
int i = 123; // un valorobject o = i; // encapsatint j = (int)o; // des-encapsat
Cal vigilar molt que quan es el des-encapsat la variable a la que es fa l'assignació ha de ser del mateix tipus que la original, si no es produeix una InvalidCastException.
I els SQL Parameters? Doncs resulta que els SqlParameter tenen un constructor que té els següents paràmetres: Constructor (String, Object). On el "string" és el nom del paràmetre i el "object" és el valor. El constructor dedueix el tipus del paràmetre a partir del tipus de l'objecte encapsat. I ho fa seguint la següent taula:
Tipus de la variable
Tipus a la BBDD
Boolean
Bit
Byte
TinyInt
byte[]
VarBinary (cal vigilar si la longitud de l'array supera la longitud del varbinary)
Char
No suportat
DateTime
DateTime
DateTimeOffset
DateTimeOffset (només a partir de SQL Server 2008 inclòs)
Decimal
Decimal
Double
Float
Single
Real
Guid
UniqueIdentifier
Int16
SmallInt
Int32 (els int normals)
Int
Int64
BigInt
String
NVarChar (cal vigilar si la longitud de l'string supera la longitud del nvarchar)
TimeSpan
Time (només a partir de SQL Server 2008 inclòs)
Aleshores és molt perillós passar un object al constructor del SqlParameter perquè es fàcil despistar-se i que no encaixi amb el tipus de base de dades i com està "boxed" no hi ha una visió del que està passant exactament.
#13/02/2015 13:32 Programació C# SQLServer Autor: Alex Canalda
Un cop està preparada tota la configuració necessària per fer la invocació només queda aquesta pròpiament dita. En l'aplicació també hi han els botons de "Load" i "Save" que el que fan és escriure els valors dels controls en un fitxer XML i recuperar els valors, no cal que els expliqui...
Abans de pròpiament fer la invocació a Webservice, cal configurar l'accés. S'ha de tenir en compte que la URL pot ser HTTPS, pot haver-hi un proxy previ per accedir al servidor web o potser el servidor web fa servir autenticació bàsica HTTP.
Aquests 3 punts requereixen una configuració prèvia a la invocació.
HTTPS: Per acceptar connexions HTTPS el EndPoint local ha de reconèixer el certificat com a vàlid. Per això cal afegir un validador de certificats (només cal afegir-lo un cop), que en aquesta eina per fer proves és un validador que els accepta tots. Per això en l'event Form_Load es posa un call back a la rutina que fa l'acceptació.
Si es vol fer l'autenticació bàsica HTTP, cal fer:
//Afegir una capçalera HTTP a la crida, l'string es fa a partir de la//clase de més a sota, req és una variable de tipus HttpRequest.if (HTTPBasic != null)
{
string auth = HTTPBasic.CreateAuthorization();
req.Headers.Add("Authorization", "Basic " + auth);
}
publicclass clsHTTPBasicAuthorization
{
publicstring Domini { get; set; }
publicstring Usr { get; set; }
publicstring Pwd { get; set; }
/// <summary>/// Retorna un string per posar en header authorization d'una petició HTTP/// </summary>/// <param name="domini">Sense HTTP ni el subdomini , només google.es. Es opcional, es pot passar ""</param>/// <param name="userName">Usuari</param>/// <param name="password">Pwd</param>/// <returns>L'string en Base64 que s'ha de posar en totes les peticions</returns>publicstring CreateAuthorization()
{
string auth = "";
if (!string.IsNullOrWhiteSpace(Domini)) auth = Domini + @"\";
auth += Usr + ":" + Pwd;
auth = Convert.ToBase64String(Encoding.Default.GetBytes(auth));
return auth;
}
}
Finalment si es vol fer servir un proxy cal afegir la configuració del mateix a la crida:
publicclass clsProxy
{
publicstring ProxyURL { get; set; }
publicstring ProxyUsr { get; set; }
publicstring ProxyPwd { get; set; }
publicstring ProxyPort { get; set; }
public WebProxy GetProxy()
{
if (string.IsNullOrWhiteSpace(ProxyURL)) returnnull;
else
{
WebProxy Pxy = new WebProxy();
UriBuilder UriBld = new UriBuilder(ProxyURL);
if(!string.IsNullOrWhiteSpace(ProxyPort)) UriBld.Port = int.Parse(ProxyPort);
Pxy.Address = UriBld.Uri;
if (!string.IsNullOrWhiteSpace(ProxyUsr))
{
Pxy.Credentials = new NetworkCredential(ProxyUsr, ProxyPwd);
}
else Pxy.UseDefaultCredentials = true;
return Pxy;
}
}
}
//Després a la HttpRequest cal fer:
req.Proxy = pProxy.GetProxy();
#11/02/2015 17:37 Programació C# Autor: Alex Canalda
A vegades haig d'invocar Webservices de formes rares, aleshores ensopegues amb les llibreries i eines que et proporciona l'arquitectura que fas servir. Per exemple potser l'eina WSDL del .NET no et genera les classes correctament, o bé el Webservice fa servir WS-Addressing, o ...
I es que de fet, si un s'ho para a pensar el SOAP no és res més que XML, i text al fi i al cap. I el que es diu "invocar a un Webservice" no és res més que HTTP POST contra una URL d'un servidor web. Com a molt, i principal diferència amb una petició HTTP normal s'ha d'afegir una capçalera HTTP, el SOAPAction en SOAP 1.1, en 1.2 no cal posar-la.
Aleshores quan un vol invocar a un Webservice el que ha de fer és ajuntar un text, un XML, i fer un POST. Donat que això és senzill he fet un programa per fer tests ràpids de Webservices, es el SOAP Poster.
El SOAP Poster és una aplicació Winforms que amb un interface simple permet enviar un text a una URL. Amb alguns detalls, com per exemple si s'ha de fer servir un proxy, o HTTP-Basic Authentication o si cal fer proves de concurrència hi ha un número que indica la quantitat d'invocacions. El codi està directament posat als events dels controls. El veure'm en detall en futurs posts.
#11/02/2015 11:26 Programació C# Autor: Alex Canalda
En una aplicació a vegades cal un petit fitxer de configuració, actualitzable etc... llavors amb la clsConfig hi ha prou. A vegades cal guardar més dades, més estructurades, aleshores és quan es fan servir BBDD, ja que un fitxer XML té un mal rendiment guardant dades (s'ha de llegir sencer per modificar-lo). Però tampoc cal un SQL Server si no es vol més que guardar unes quantes dades, ni pagar llicències ni instal·lar res.
Per això he estat buscant un BBDD local que es pugui enganxar a un programet, i he trobat que el SQLServerCE (made by Microsoft) s'ajusta força a aquestes necessitats. L'únic problema que té és que Microsoft sembla que no el vol mantenir més ja que suposo que estaria menjant-se vendes de petits SQLServers. També he llegit que és possible que el torni a posar al VisualStudio (encara que això ja és més un rumor). El cas és que la última versió del SQLServer CE és força recent i s'integra perfectament amb el VisualStudio 2013 si es sap com.
El primer que cal fer és instal·lar les eines del SQLServer CE (el "ToolBox"), amb això al menú "TOOLS" ens apareix una nova opció que ens permet crear BBDD de SQLServerCE, veure els camps, executar scripts, ...
Un cop les eines estan instal·lades cal poder accedir des de programa a la BBDD. Aquí és on m'ha costat més per que no està del tot clar. El que cal fer és copiar la DLL del SQLServer CE a la carpeta del projecte que estem desenvolupant. Normalment aquesta carpeta està a: "C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Private" (s'ha de copiar sencera, amb subcarpetes). Un cop copiada la carpeta, cal posar a l'arrel del projecte les carpetes "amd64" i "x86" (aquestes dues carpetes són subcarpetes de la "Private"). Totes les DLLs han d'estar marcades com "Copiar si es modifiquen" en l'acció "Copiar al directori de sortida" (es fa fent "Propiedades" sobre la DLL). Un cop fet això es posa la "Referència" a la DLL "System.Data.SqlServerCe.dll". Queda així:
A partir d'aquest moment ja es pot fer servir el SQLServer CE en VisualStudio 2013.
#31/05/2014 16:08 Programació C# SQLServer Autor: Alex Canalda
A vegades és necessari comparar objectes i s'ha de proporcionar a altres classes alguna forma de fer aquesta comparació. Per aquest motiu s'ha d'implementar l'interficie IEquatable.
En el Generador això és necessari per que treballa amb llistes d'objectes. Les llistes tenen un mètode força interessant que es diu "Contains", que indica si una llista té dins seu un objecte com el que es passa per paràmetre, però això necessita que la classe que defineix els objectes de la llista tingui implementat el IEquatable. Veiem el codi, a la classe clsTaula hi ha el mètode que afegeix camps a la taula:
publicvoid AddCamp(clsCamp Camp)
{
if (Camps == null) Camps = newList<clsCamp>();
if (!Camps.Contains<clsCamp>(Camp)) Camps.Add(Camp);
}
Aleshores cal que la classe clsCamp implementi el IEquatable:
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.
#28/04/2014 12:19 Programació C# Autor: Alex Canalda
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