La funzione getJSON non prevede una callback in caso di errore, per permettere comunque di gestire un'eccezione lato server ho utilizzato il seguente metodo:
Lato client ho utilizzato il seguente script:
Appunti di programmazione
L'opportunità di salvare files sul database è stato ed è ancora argomento di
discussione.
Personalmente non penso che in assoluto sia giusto o sbagliato decidere di
mantenere i contenuti di files all'interno del
database piuttosto che sul file system, ritengo che siano le esigenze
applicative che ci portano ad effettuare una scelta piuttosto che un'altra.
Detto questo vorrei illustrare un metodo per la gestione dei file utilizzabile
nel caso si decida di mantenere i files sul database.
Quanto descritto di seguito nasce dalla seguente situazione:
A livello di sviluppo ho deciso inoltre di inserire le seguenti condizioni:
Allegato al post trovate un progetto in cui è riportata la soluzione adottata suddivisa in due versioni. La prima versione risolve il problema ma soffre di un problema di efficenza legato al mancato lazy loading dei contenuti del file, mentre nella seconda questo problema si risolve.
La situzione descritta prevede un'entità di tipo MessageInfo contenente un insieme di allegati rappresentati dall'entità AttachmentInfo.
La prima soluzione introduce le due entità indicate in precedenza:
Per quanto riguarda il mapping di NH è stato effettuato su 3 tabelle, gestendo il lazy loading della collezione di attachment per i messaggi:
<class name="MessageInfoV1" table="message_v1" polymorphism="explicit">
<id name="MessageId" >
<generator class="assigned"/>
</id>
<property name="Sender" length="32" />
<property name="Recipient" length="32" />
<property name="MessageText" length="1024"/>
<bag name="Attachments" table="message_attachments_v1" lazy="true" cascade="all-delete-orphan">
<key column="MessageId"/>
<many-to-many column="AttachmentId" class="AttachmentInfoV1"/>
</bag>
</class>
<class name="AttachmentInfoV1" table="attachment_v1" polymorphism="explicit">
<id name="AttachmentId" >
<generator class="assigned"/>
</id>
<property name="FileName" length="256" />
<property name="Content" type="binary"/>
</class>
Questa soluzione è però soggetta ad un problema, se si vuole recuperare un messaggio ed elencare i nomi dei files allegati a meno di non definire una query od un criteria ad hoc si ottiene il caricamento di tutto l'attachment compreso il contenuto.
Per esempio il seguente codice.
message = s.Get<Model.Solution1.MessageInfoV1>(messageId);
message.MessageText = "Messaggio di test modificato";
foreach (var attachment in message.Attachments)
Console.WriteLine("Attachment filename \"{0}\" (Id: {1})", attachment.FileName, attachment.AttachmentId);
risulta in un caricamento di tutti i contenuti dei files anche se non necessari.
DEBUG NHibernate.SQL - SELECT messageinf0_.MessageId as MessageId5_0_, messageinf0_.Sender as Sender5_0_, messageinf0_.Recipient as Recipient5_0_, messageinf0_.MessageText as MessageT4_5_0_ FROM message_v1 messageinf0_ WHERE messageinf0_.MessageId=?p0;
DEBUG NHibernate.SQL - SELECT attachment0_.MessageId as MessageId1_, attachment0_.AttachmentId as Attachme2_1_, attachment1_.AttachmentId as Attachme1_4_0_, attachment1_.FileName as FileName4_0_, attachment1_.Content as Content4_0_ FROM message_attachments_v1 attachment0_ left outer join attachment_v1 attachment1_ on attachment0_.AttachmentId=attachment1_.AttachmentId WHERE attachment0_.MessageId=?p0;
Ovviamente il medesimo problema si manifesta anche in fase di update degli attachment.
Per evitare il problema descritto occorre introdurre il lazy loading del contenuto del file.
Per fare questo è necessario gestire una entità apposita da associare uno ad uno con l'entità dell'attachment.
Il nuovo object model è il seguente:

Per rendere praticamente identica l'interfaccia dell'entità AttachmentInfo la proprietà AttachmentContent è segnata come protect, mentre mantengo la proprietà Content che fa riferimento a AttachmentContent.Content, in questo modo la gestione dei contenuti tramite una entità ad-hoc viene nascosta agli utilizzatori della libreria.
protected virtual AttachmentContentInfoV2 AttachmentContent { get; set; }
public virtual byte[] Content
{
get { return AttachmentContent.Content; }
}Per quanto riguarda il mapping viene utilizzato un mapping one-to-one per la proprietà AttachmentContent ovviamente con il lazy loading abilitato.
<class name="AttachmentInfoV2" table="attachment_v2" polymorphism="explicit">
<id name="AttachmentId" >
<generator class="assigned"/>
</id>
<property name="FileName" length="256" />
<one-to-one name="AttachmentContent" class="AttachmentContentInfoV2"
lazy="proxy" cascade="all-delete-orphan" />
</class>
<class name="AttachmentContentInfoV2" table="attachment_content_v2" >
<id name="AttachmentId" >
<generator class="assigned"/>
</id>
<property name="Content" type="binary"/>
</class>
Con questa nuova soluzione l'interrogazione dell'elenco dei file allegati risulta nella seguente query:
DEBUG NHibernate.SQL - SELECT messageinf0_.MessageId as MessageId2_0_, messageinf0_.Sender as Sender2_0_,
messageinf0_.Recipient as Recipient2_0_, messageinf0_.MessageText as MessageT4_2_0_
FROM message_v2 messageinf0_ WHERE messageinf0_.MessageId=?p0;
DEBUG NHibernate.SQL - SELECT attachment0_.MessageId as MessageId1_, attachment0_.AttachmentId as Attachme2_1_,
attachment1_.AttachmentId as Attachme1_0_0_, attachment1_.FileName as FileName0_0_
FROM message_attachments_v2 attachment0_
left outer join attachment_v2 attachment1_
on attachment0_.AttachmentId=attachment1_.AttachmentId
WHERE attachment0_.MessageId=?p0;
Inoltre se si prova ad eseguire un'azione di update che non coinvolge il nome file anche in questo caso il contenuto non viene coinvolto.
s = NHFactory.GetNewSession();
using (ITransaction tx = s.BeginTransaction())
{
message = s.Get<Model.Solution2.MessageInfoV2>(messageId);
message.MessageText = "Messaggio di test modificato";
foreach (var attachment in message.Attachments)
{
attachment.FileName = attachment.FileName.Replace("XML", "xm_");
_log.InfoFormat("Attachment filename \"{0}\" (Id: {1})", attachment.FileName, attachment.AttachmentId);
}
s.Flush();
s.Close();
}
UPDATE message_v2 SET Sender = ?p0, Recipient = ?p1, MessageText = ?p2 WHERE MessageId = ?p3;
UPDATE attachment_v2 SET FileName = ?p0 WHERE AttachmentId = ?p1;
UPDATE attachment_v2 SET FileName = ?p0 WHERE AttachmentId = ?p1;
UPDATE attachment_v2 SET FileName = ?p0 WHERE AttachmentId = ?p1;
Il codice di esempio è disponibile qui.
Nel file compresso è disponibile il database MySQL e la libreria base.
Vorrei riportare alcuni suggerimenti relative alle impostazioni necessarie per supportare le impostazioni italiane ed il set di caratteri relativo, in un sito ASP.NET appoggiato su di un database MySQL.
In particolare ho avuto problemi nel fare in modo che le codifiche relative al character set di ASP.NET venissero salvate e recuperate correttamente da MySQL.
Per quanto riguarda ASP.NET è sufficiente indicare nel file di configurazione:
<globalization uiculture="it-IT" culture="it-IT"/>
Per quanto riguarda MySQL per permettere il supporto delle accentate e del simbolo euro ho verificato che è necessario utilizzare il character set utf-8.
Per fare questo occorre innanzitutto indicare il character set nella string di connessione al database utilizzando includendo CHARSET=utf8 all'interno della stessa.
<connectionStrings>
<add name="myConnection" providerName="MySql.Data.MySqlClient" connectionString="Server=localhost;Database=myDB;Uid=usr;Pwd=pwd;CHARSET=utf8"/>
</connectionStrings>
Dopo di chè occorre che tutte le colonne di tip0 testuale delle tabelle di MySQL siano impostate per utilizzare il charset opportuno:
Con questi elementi è possibile fare in modo che senza intervenire sul
Stamane mi sono imbattuto nel seguente errore da parte del componente ReportViewer contenuto in una pagina aspx dell'applicazione su cui sto' lavorando.
The Group expression for the grouping ‘matrix1_RowGroup1’ contains an error:
Request for the permission of type 'System.Data.SqlClient.SqlClientPermission,
System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
failed.
Dopo un po' di ricerche ho individuato il seguente articolo che mi ha chiarito le idee su cosa succedeva: report-viewer-request-for-the-permission-of-type-sqlclientpermission-failed.
L'applicazione utilizza NHibernate per il persistance layer e l'errore si è verificato dopo il porting a SQL Server del database, infatti a parità di codice utilizzando un DB MySQL il problema non si presenta.
Al report passo come data soure degli oggetti per cui utilizzo il lazy loading per alcune delle proprietà (per chi non conoscesse NH il lazy loading prevede che quando si carica un oggetto da db le proprietà che referenziano altri oggetti vengano caricate dal database solo al momento dell'effettivo utilizzo delle stesse e non insieme all'oggetto principale).
Quindi in questo caso quando il report viene generato non tutti i dati sono ancora stati caricati ed il ReportViewer effettua questo caricamento (a sua insaputa ed attraverso i proxy di NH) dal database.
Il problema è che il ReportViewer per problemi di sicurezza genera il report in un AppDomain separato da quello dell'applicazione principale. Nel caso del data provider SqlClient viene negato in questo ambito l'esecuzione delle query su DB (tramite la classe SqlClientPermission).
In questi giorni ho sostituito in un'applicazione ASP.NET 3.5 la versione di NHibernate 1.2.0.3002 con la versione 2.1.0.4000, di seguito riporto un breve elenco delle attività svolte per supportare questa nuova release.
Per cominciare
La nuova versione dei binari si trova su NHForge, nel mio caso ho dovuto scaricare la distribuzione di Nhibernate ed Nhibernate Caches, l'implementazione delle varie classi per la cache di secondo livello è stata infatti distribuita separatamente.
Sostituzione delle librerie
Nel progetto vanno ovviamente sostituiti i riferimenti alle vecchie librerie di NH.
Le librerie principali da referenziare sono quelle incluse nella cartella Required_Bins, e nel mio caso quelle relative a ProxyFactory Castle presenti in \Required_For_LazyLoading\Castle.
Infine per utilizzare la cache di secondo livello ho inserito il riferimento a NHibernate.Caches.SysCache.dll contenuta nella distribuzione di Nhibernate Caches.
Cambio del file di configurazione
Per quanto riguarda la parte di configurazione di NH nel web.config occorre modificare il tipo di gestione della sezione relativa a NH da name value a ConfigurationSectionHandler, di conseguenza la definizione della sezione cambia da:
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
Dopo di chè occorre intervenire con la modifica della sezione vera e propria, nel mio caso ho cambiato la sezione da:
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect"/>
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
<add key="hibernate.connection.connection_string_name" value="connectionName"/>
<add key="hibernate.cache.provider_class" value="NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache"/>
<add key="hibernate.cache.use_second_level_cache" value="true"/>
</nhibernate>
a
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string_name">connectionName</property>
<property name="cache.use_second_level_cache">true</property>
<property name="cache.provider_class">NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
</session-factory>
</hibernate-configuration>
Nella nuova versione è necessario specificare esplicitamente la proprietà proxyfactro.factory_class, in precedenza se non veniva specificato nulla veniva utilizzata di default l'implementazione Castle.
Sostituzione namespace NHibernate.Expression con NHibernate.Criterion
Come da release notes il namespace Expression è stato modificato in Criterion a partire dalla 2.0, quindi di conseguenza occorre sostituire il nome del namespace.
Inoltre poichè da qualche parte utilizzavo ancora la classe EqExpresssion che non è più presente nelle nuove versioni ho sostituito eventuali espressioni obsolete del tipo:
Session.CreateCriteria(...).Add(new EqExpression("propname", value)
con
Session.CreateCriteria(...).Add(Expression.Eq("propname", value))
Cambiamenti a IQuery.SetParameterList
Dagli errori di compilazione ho scoperto che il metodo IQuery.SetParameterList è stato modificato passando dagli overload:
SetParameterList(string name, IEnumerable vals)
SetParameterList(string name, IEnumerable vals, IType type)
ai seguenti:
SetParameterList(string name, ICollection vals);
SetParameterList(string name, object[] vals);
SetParameterList(string name, ICollection vals, IType type);
SetParameterList(string name, object[] vals, IType type);
Questo può comportare problemi nel caso in cui si passasse al metodo un oggetto di tipo IList
Il problema è facilmente risolvibile. Se la lista passata è una lista di oggetti miaLista è sufficiente passare al metodo miaList.ToArray(), mentre se è una lista di valori è necessario fare prima un cast a una lista di oggetti e quindi si può utilizzare miaList.Cast().ToArray().