sabato 30 gennaio 2010
Il comportamento di default del wizard dell’EF accorpa i metadati del modello come risorse del nostro assembly e inserisce una stringa di connessione nell’ app.config o nel web.config in modo da referenziare a runtime tali risorse:
<connectionStrings>
<add name="NorthwindEntities"
connectionString="metadata=res://*/NorthwindModel.csdl|res://*/NorthwindModel.ssdl|res://*/NorthwindModel.msl;..."
providerName="System.Data.EntityClient" />
</connectionStrings>
Tuttavia, “embeddare” i metadati del nostro modello come risorsa dell’assembly può costituire un limite: infatti sorgerebbero dei problemi qualora volessimo
- esporre i metadati alla modifica di agenti esterni
- referenziare tramite connection string dei metadati definiti altrove sul file system
- caricare i metadati on-demand magari leggendoli da stream di rete
Infatti, esistono dei casi in cui può essere utile distinguere tra metadati di test e metadati di produzione o comunque prevedere delle politiche che possano richiedere storage model (SSDL) e mapping model (MSL) diversi, pur mantenendo l’integrità del modello concettuale (CSDL) utilizzato dallo sviluppatore.
Una prassi molto comune è impostare l’opzione 'Metadata Artifact Processing' del designer a 'Copy to Output Directory'.
In questo modo nella bin del nostro progetto troviamo i metadati SSDL, MSL e CSDL separati fisicamente dalla .dll di output.
Quindi abbiamo già raggiunto l’obiettivo di poter modificare i metadati (solitamente SSDL e MSL) senza dover ricompilare la nostra applicazione. A questo punto possiamo scegliere di modificare la nostra connection string in modo che punti al corretto insieme di file:
string connectionString = @"metadata=.\NorthwindModel.csdl|.\NorthwindModel.ssdl|.\NorthwindModel.msl;
provider=System.Data.SqlClient; provider connection string=""..."" ";
using (NorthwindContext context = new NorthwindContext(connectionString)) { ... }
Uno step ulteriore che si può prevedere è il caricamento dinamico di metadati non soltanto da una locazione fisica su file system, ma anche da un generico stream (es. da un web server via HTTP). A riguardo, il seguente esempio mostra come caricare dinamicamente CSDL, SSDL e MSL in modo da creare un oggetto di tipo EntityConnection da passare al nostro ObjectContext:
// Get CSDL/SSDL/MSL from a generic stream ...
string csdl = "<?xml version="1.0" encoding="utf-8"?><Schema...</Schema>";
string ssdl = "<?xml version="1.0" encoding="utf-8"?><Schema...</Schema>";
string msl = "<?xml version="1.0" encoding="utf-8"?><Mapping...</Mapping>";
XmlTextReader csdlXmlReader = new XmlTextReader(new StringReader(csdl));
XmlTextReader ssdlXmlReader = new XmlTextReader(new StringReader(ssdl));
XmlTextReader mslXmlReader = new XmlTextReader(new StringReader(msl));
EdmItemCollection edmItemCollection = new EdmItemCollection(new[] { csdlXmlReader });
StoreItemCollection storeItemCollection = new StoreItemCollection(new[] { ssdlXmlReader });
StorageMappingItemCollection storageMappingItemCollection = new StorageMappingItemCollection(edmItemCollection, storeItemCollection, new[] { mslXmlReader });
// Create the metadata workspace for the EntityConnection ...
MetadataWorkspace metadataWorkspace = new MetadataWorkspace();
metadataWorkspace.RegisterItemCollection(edmItemCollection);
metadataWorkspace.RegisterItemCollection(storeItemCollection);
metadataWorkspace.RegisterItemCollection(storageMappingItemCollection);
string sqlConnectionString = @"Data Source=.\SQLSRV08;Initial Catalog=Northwind;User ID=XXX;Password=XXX;";
EntityConnection entityConnection = new EntityConnection(metadataWorkspace,new SqlConnection(sqlConnectionString));
using (NorthwindContext context = new NorthwindContext(entityConnection)) { ... }
venerdì 8 gennaio 2010
nRoute è uno dei framework a supporto di applicazioni Silverlight sviluppate secondo il pattern M-V-VM. In merito, segnalo questo interessante articolo in cui si parla dell' introduzione di Reverse Commands.
Da un punto di vista pratico viene introdotta una nuova interfaccia IReverseCommand che estende la solita ICommand. Tale specializzazione permette di definire un trigger "di ritorno" dal View-Model verso la View una volta che un command è stato eseguito. L'aspetto interessante si trova quindi nel fatto che il target dell' IReverseCommand è la View e non il ViewModel.
Secondo i principi del pattern M-V-VM in Silverlight, esistono due meccanismi primari di comunicazione tra View e ViewModel:
- Cambiamenti di stato relativi a databinding tra oggetti della View e proprietà del ViewModel
- Invocazioni di azioni ( ICommand appunto ) dalla View verso il ViewModel
Se in questi scenari architetturali gli ICommand sono concepiti per essere scatenati dalla View (e gestiti nel ViewModel), l'introduzione di ReverseCommands avviene per esplicitare il meccanismo inverso sempre in un'ottica strongly-typed.
mercoledì 6 gennaio 2010
Con il potenziamento delle funzionalità out-of-browser, Silverlight 3+ è ormai una tecnologia matura per supportare scenari off-line. Nell’ Isolated Storage client-side, infatti, oggi possiamo gestire ad esempio un vero e proprio database engine.
Ultimamente la mia attenzione è rivolta in particolare a due interessanti progetti (in beta):
martedì 29 dicembre 2009
martedì 8 dicembre 2009
Questo post è più una nota personale che vorrei condividere a fronte delle diverse volte in cui ho riscontrato questa esigenza.
Quando si effettua il deploy di un’ applicazione che utilizza EF su System.Data.SQLite, è possibile trovarsi di fronte al seguente errore:
[ArgumentException: Impossibile trovare il provider dati .Net Framework richiesto. Potrebbe non essere installato.]
System.Data.Common.DbProviderFactories.GetFactory(String providerInvariantName) +1310319
System.Data.EntityClient.EntityConnection.GetFactory(String providerString) +35
[ArgumentException: Il provider dell'archivio specificato non è stato trovato nella configurazione oppure non è valido.]
System.Data.EntityClient.EntityConnection.GetFactory(String providerString) +62
System.Data.EntityClient.EntityConnection.ChangeConnectionString(String newConnectionString) +394
System.Data.EntityClient.EntityConnection..ctor(String connectionString) +82
System.Data.Objects.ObjectContext.CreateEntityConnection(String connectionString) +42
System.Data.Objects.ObjectContext..ctor(String connectionString, String defaultContainerName) +16
...
|
Solitamente, la causa risiede nella mancata registrazione del provider System.Data.SQLite a livello di machine.config della macchina. La classica soluzione prevede quindi la registrazione esplicita del provider a livello applicazione sfruttando il .config (Riferimento MSDN: http://msdn.microsoft.com/en-us/library/dd0w4a2z(VS.80).aspx):
<configuration>
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.SQLite"/>
<add name="SQLite Data Provider" invariant="System.Data.SQLite"
description=".Net Framework Data Provider for SQLite"
type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
</DbProviderFactories>
</system.data>
</configuration>
Tuttavia, in soluzioni in cui si richiede che il DbProviderFactory sia caricato on-demand a runtime (ad esempio in sistemi che utilizzano IoC per l’accesso ai dati) si potrebbe ricorrere alla seguente tecnica: enumerare i DbProviderFactory correntemente installati e, qualora System.Data.SQLite non fosse trovato, caricarlo esplicitamente:
DataTable factoryClassesTable = System.Data.Common.DbProviderFactories.GetFactoryClasses();
IEnumerable<DataRow> queryResult = from factoryClassDataRow in factoryClassesTable.AsEnumerable()
where factoryClassDataRow.Field<string>("InvariantName") == "System.Data.SQLite"
select factoryClassDataRow;
if (queryResult.Count() == 0)
{
System.Data.DataSet dataSet = ConfigurationManager.GetSection("system.data") as System.Data.DataSet;
if (dataSet != null) dataSet.Tables[0].Rows.Add("SQLite Data Provider", ".Net Framework Data Provider for SQLite",
"System.Data.SQLite",
"System.Data.SQLite.SQLiteFactory, System.Data.SQLite");
}
Ovviamente ogni ulteriore considerazione è ben accetta :D
sabato 17 ottobre 2009
Analizzando diverse soluzioni WPF che sfruttano l’architettura M-V-VM, mi è saltata all’occhio una strategia di implementazione dell’interfaccia ICommand semplice e potente a mio modo di vedere. Andiamo direttamente al codice:
public class GenericCommand<T> : ICommand
{
public Predicate<T> CanExecuteDelegate { get; set; }
public Action<T> ExecuteDelegate { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecuteDelegate != null) return CanExecuteDelegate((T)parameter);
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteDelegate != null) ExecuteDelegate((T)parameter);
}
}
Sostanzialmente l’obiettivo di questo GenericCommand è di permettere una “tipizzazione” dichiarativa del parametro gestito dall’ interfaccia ICommand, in modo tale da poter costruire ViewModel contenenti delegati che gestiscono il pattern CanExecute/Execute operando a nostro piacimento su parametri tipizzati passati magari direttamente dalla View. Ecco un esempio molto semplice di ViewModel...
public class ContactsViewModel
{
public ObservableCollection<string> Contacts { get; private set; }
public GenericCommand<string> AddContact { get; set; }
public GenericCommand<object> ClearContacts { get; set; }
public ContactsViewModel()
{
Contacts = new ObservableCollection<string>();
AddContact = new GenericCommand<string>();
ClearContacts = new GenericCommand<object>();
AddContact.CanExecuteDelegate += new Predicate<string>(AddContact_CanExecute);
AddContact.ExecuteDelegate += new Action<string>(AddContact_Execute);
ClearContacts.CanExecuteDelegate += new Predicate<object>(ClearContacts_CanExecute);
ClearContacts.ExecuteDelegate += new Action<object>(ClearContacts_Execute);
}
bool AddContact_CanExecute(string contactName) { return (!string.IsNullOrEmpty(contactName)); }
void AddContact_Execute(string contactName) { Contacts.Add(contactName); }
bool ClearContacts_CanExecute(object parameter) { return (Contacts.Count > 0); }
void ClearContacts_Execute(object parameter) { Contacts.Clear(); }
}
...e per chiudere il cerchio riporto anche lo XAML saliente di una View di esempio.
<TextBox Name="txtContactName" ... />
<Button Command="{Binding AddContact}" CommandParameter="{Binding ElementName=txtContactName, Path=Text}" ...>Add Contact</Button>
<ListBox ItemsSource="{Binding Contacts}" ... />
<Button Command="{Binding ClearContacts}" ...>Clear Contacts</Button>
Cosa ne pensate?
Technorati Tag:
WPF,
ICommand,
M-V-VM
sabato 26 settembre 2009
La possibilità di creare Window di forma irregolare costituisce a mio modo di vedere uno dei più divertenti nonché semplici vantaggi di WPF.
In genere, ogni tecnica di realizzazione di Window dalla forma completamente personalizzata si basa sulla valorizzazione preliminare di tre proprietà:
In questo modo siamo subito svincolati sia dalla forma rettangolare standard della nostra Window, che dalla barra standard del titolo. Si aprono quindi diverse possibilità di sviluppo. E’ possibile ad esempio sfruttare immagini semitrasparenti come background della Window (scelta semplice e di veloce realizzazione, ma non consigliabile in WPF dal momento che la Window subirebbe un rendering con più pixel in sistemi con maggiori DPIs). Una soluzione sicuramente più potente sfrutta invece il programming model che WPF ci mette a disposizione relativamente alla grafica vettoriale: in altre parole, per creare una Window dai bordi arrotondati basta partire dall’elemento Border.
Vorrei riportare un esempio che mi è rimasto impresso dopo aver letto il libro “Pro WPF in C# 2008” : creare una Window dalla forma irregolare, con bordi arrotondati.
N.B.: tralascio volutamente ogni questione di carattere architetturale ( es. realizzazione di custom control template per le nostre Window ).
Codice XAML:
<Window x:Class="WPF_Demo.ShapedWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
AllowsTransparency="True" WindowStyle="None" Background="Transparent" ResizeMode="CanResizeWithGrip">
<Border ... CornerRadius="0,20,30,20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="Title" ... MouseLeftButtonDown="WindowDragMove"></TextBlock>
<Button ... Command="Close">
<Button.Content>
<Image Source="..." Stretch="Fill"></Image>
</Button.Content>
</Button>
<Grid Grid.Row="1" Background="White">
<Rectangle Grid.RowSpan="3" Width="5" VerticalAlignment="Stretch" HorizontalAlignment="Right"
Cursor="SizeWE" Fill="Transparent"
MouseLeftButtonDown="BeginWindowResize"
MouseLeftButtonUp="EndWindowResize"
MouseMove="WindowResize">
</Rectangle>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"...>Body</TextBlock>
</Grid>
<TextBlock Grid.Row="2" Text="Footer" ...></TextBlock>
</Grid>
</Border>
</Window>
Code-behind (interaction logic):
public partial class ShapedWindow : Window
{
private bool _isResizing = false;
public ShapedWindow()
{
InitializeComponent();
CommandBinding binding = new CommandBinding(ApplicationCommands.Close);
binding.Executed += CloseWindow;
this.CommandBindings.Add(binding);
}
private void CloseWindow(object sender, RoutedEventArgs e) { this.Close(); }
private void BeginWindowResize(object sender, MouseButtonEventArgs e) { _isResizing = true; }
private void EndWindowResize(object sender, MouseButtonEventArgs e)
{
_isResizing = false;
Rectangle rectangle = (Rectangle)sender;
rectangle.ReleaseMouseCapture();
}
private void WindowResize(object sender, MouseEventArgs e)
{
Rectangle rectangle = (Rectangle)sender;
if (_isResizing)
{
rectangle.CaptureMouse();
double newWidth = e.GetPosition(this).X + 5;
if (newWidth > 0) this.Width = newWidth;
}
}
private void WindowDragMove(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2) this.WindowState = (this.WindowState == WindowState.Maximized) ? WindowState.Normal : WindowState.Maximized;
else this.DragMove();
}
}
Si nota subito che in una soluzione di questo tipo (non poteva essere altrimenti) è compito dello sviluppatore gestire sia il resize che il move della Window, dal momento che è assente la barra del titolo standard che l’utente usa comunemente per interagire con la finestra stessa.
Differentemente dal mondo Windows Forms, in WPF è possibile gestire la modalità di dragging della Window semplicemente invocando il metodo Window.DragMove() , che in questo caso specifico viene usato in corrispondenza dell’evento MouseLeftButtonDown scatenato sul TextBlock che funge da barra del titolo ( due click consecutivi invece “massimizzano” o “normalizzano” la finestra ;) ).
Per quanto riguarda invece il resize, la questione diventa un po’ più “complessa”. Anzitutto, impostare la proprietà Window.ResizeMode a CanResizeWithGrip significa far apparire il sizing grip nell’angolo in basso a destra assumendo che la window sia comunque rettangolare. Questo significa quindi che esso può apparire o troppo distante o addirittura sopra l’effettivo bordo della Window.
Se non si vuole scegliere questo tipo di approccio, bensì si desidera poter ridimensionare manualmente la finestra interagendo con i suoi bordi effettivi, esistono due strade: o si utilizzano P/Invoke che inviano messaggi Win32 per ridimensionare la window, oppure più semplicemente (come in questo caso) si gestisce la posizione e l’azione scatenata dal mouse in ogni bordo che si vuole utilizzare per ridimensionare la finestra, andando poi a gestire di conseguenza la proprietà Width.
Nell’esempio, si è scelto di posizionare un Rectangle trasparente di larghezza 5 in corrsipondenza del lato destro della finestra. In questo modo gli opportuni event handler (MouseLeftButtonDown, MouseLeftButtonUp, MouseMove) ci permettono di gestire il resize manuale della window ogni volta che il mouse interagisce con il bordo destro della stessa.
martedì 8 settembre 2009
Spesso in ambiente desktop può essere utile salvare degli screenshot relativi alla UI della nostra applicazione: in alcuni scenari ad esempio, oltre ai soliti log e trace, è molto comodo avere automaticamente lo screenshot dell’UI al momento dell’errore, magari come allegato di una email di report.
Una comune soluzione in Windows Forms è quella che sfrutta GDI+ tramite il metodo Graphics.CopyFromScreen(…) . In questo post vorrei mostrare come anche in WPF è possibile approfittare della stessa tecnica con il minimo sforzo: in particolare, nell’ helper class che segue sono presenti degli overload del metodo SaveScreenShot(…) per salvare su file system gli screenshot sia di singole Window (WPF) / Form (Windows Forms) che dell’intero schermo.
public class ScreenshotHelper
{
// Per Windows Forms...
public static void SaveScreenShot(Form form, string fileName, ImageFormat imageFormat)
{
SaveScreenShot(form.Location, form.Size, fileName, imageFormat);
}
// Per WPF...
public static void SaveScreenShot(Window window, string fileName, ImageFormat imageFormat)
{
SaveScreenShot(new System.Drawing.Point((int)window.Left, (int)window.Top),
new System.Drawing.Size((int)window.Width, (int)window.Height),
fileName, imageFormat);
}
private static void SaveScreenShot(System.Drawing.Point windowLocation, System.Drawing.Size windowSize,
string fileName, ImageFormat imageFormat)
{
using (Bitmap bitmap = new Bitmap(windowSize.Width, windowSize.Height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(windowLocation, new System.Drawing.Point(0, 0), windowSize);
}
bitmap.Save(fileName, imageFormat);
}
}
// Schermo intero...
public static void SaveScreenShot(string fileName, ImageFormat imageFormat)
{
Rectangle bounds = Screen.GetBounds(System.Drawing.Point.Empty);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(System.Drawing.Point.Empty, System.Drawing.Point.Empty, bounds.Size);
}
bitmap.Save(fileName, imageFormat);
}
}
}
Nota: Questo metodo non riesce a catturare controlli trasparenti. Per gestirli occorre utilizzare altri metodi, nativi di GDI+, come mostrato in questo post.
mercoledì 26 agosto 2009
In un’ applicazione WPF, per default la classe Application mantiene viva l’applicazione stessa finché almeno una finestra è ancora aperta. Se si vuole cambiare questo comportamento, è possibile utilizzare la proprietà Application.ShutdownMode direttamente dal markup XAML:
( ATTENZIONE: se si istanzia l’oggetto Application a mano, è obbligatorio impostare la proprietà ShutdownMode prima di invocare il metodo Run() )
I valori ammissibili per tale proprietà sono:
- OnLastWindowClose (default): l’applicazione WPF rimane in vita fintantoché esiste almeno una Window attiva. Se si chiude la MainWindow, la proprietà Application.MainWindow continua a puntare alla MainWindow chiusa. In questo tipo di situazione, si può quindi scegliere di ri-assegnare da codice la proprietà MainWindow in modo tale che essa punti ad una Window differente.
- OnMainWindowClose: l’applicazione WPF rimane viva fintantoché la MainWindow è aperta.
- OnExplicitShutdown: L’applicazione WPF rimane viva fintantoché non viene chiamato esplicitamente il metodo Application.Shutdown(), anche se tutte le Window sono chiuse. E’ chiaro che questo approccio ha senso nel caso in cui occorra gestire una logica complessa per stabilire se e quando la nostra applicazione debba chiudersi.
Un paio di note:
- Indipendentemente dal meccanismo di shutdown scelto, il metodo Application.Shutdown() è sempre valido per iniziare il processo di terminazione dell’applicazione: dietro le quinte, infatti, il metodo Application.Shutdown() non fa altro che scatenare l’uscita dal metodo Application.Run(), il che (occhio!) non preserva dall’esecuzione di eventuale codice aggiuntivo nel Main() o nel gestore dell’evento Application.Exit.
- Quando ShutdownMode=”OnMainWindowClose” e viene chiusa la MainWindow, l’oggetto Application chiude automaticamente tutte le altre finestre prima che il contesto di esecuzione esca dal metodo Run(), analogamente a quanto avviene invocando il metodo Application.Shutdown().
Technorati Tag:
WPF,
Shutdown
lunedì 27 aprile 2009
giovedì 2 aprile 2009
Per chi sviluppa utilizzando il pattern M-V-VM l’interfaccia ICommand costituisce la base per costruire un’infrastruttura che permetta alla View di invocare comandi applicativi direttamente nel ViewModel. L’interfaccia mette a disposizione tre semplici membri: due metodi Execute(...), CanExecute(...) ed un evento CanExecuteChanged. E’ facile immaginare come in progetti di una certa consistenza sia necessario costruirsi delle implementazioni spesso particolari dell’interfaccia ICommand, cosa che da una parte implica tanta buona volontà, dall’altra ripaga quando si dormono sonni tranquilli pensando ad una View completamente svincolata dalla logica applicativa.
In genere un’ utile implementazione di ICommand poggia sull’ intercettazione dell’evento CanExecuteChanged in modo da poter esporre alla View informazioni sul fatto che il nostro command sia o meno eseguibile ( si pensi ad esempio ad un Button enabled o meno ). A mio modo di vedere, una limitazione dell’interfaccia ICommand risiede nel metodo CanExecute(…) , il quale appunto è un metodo ( con annesso parametro opzionale ) e non una proprietà, cosa che non mette a disposizione il classico pattern Property/PropertyChanged sfruttabile nei data binding tra ViewModel e View.
E’ semplice ovviare a questa situazione: ad esempio si può fare in modo che il nostro ICommand implementi una sorta di proprietà “wrapper” per esporre un sistema binding-friendly CanExecute/CanExecuteChanged.
Vediamo un esempio pratico. In una mia applicazione WPF avevo bisogno di realizzare un VM per una Window di Login e volevo “bindare” la proprietà CanLogin del VM ( che semplicemente ritorna true se UserName e Password sono entrambi valorizzati ) direttamente con il metodo CanExecute(…) del mio ICommand.
Ho quindi realizzato un’implementazione di ICommand (chiamata senza fantasia CanExecuteCommand) che accetta:
- Un Delegate da eseguire all’interno dell’ Execute(…)
- Un oggetto di tipo Binding che rappresenta il binding tra una proprietà “wrapper” per il metodo CanExecute(…) dell’ ICommand (nello specifico CanExecuteAction) e una proprietà del ViewModel (nello specifico CanLogin) in modo da poter scatenare l’evento CanExecuteChanged proprio al variare della proprietà CanLogin del ViewModel.
Difficile da spiegare, facile da implementare :D
Command:
public class CanExecuteCommand : DependencyObject, ICommand
{
Action<object> _executeDelegate;
public event EventHandler CanExecuteChanged;
public bool CanExecuteAction
{
get { return (bool)GetValue(CanExecuteActionProperty); }
set { SetValue(CanExecuteActionProperty, value); }
}
public static readonly DependencyProperty CanExecuteActionProperty =
DependencyProperty.Register("CanExecuteAction", typeof(bool), typeof(CanExecuteCommand),
new PropertyMetadata(OnCanExecuteActionChanged));
public CanExecuteCommand(Action<object> executeDelegate) : this(executeDelegate, null) { }
public CanExecuteCommand(Action<object> executeDelegate, Binding canExecuteActionBinding)
{
_executeDelegate = executeDelegate;
if (canExecuteActionBinding != null) BindingOperations.SetBinding(this, CanExecuteActionProperty, canExecuteActionBinding);
}
#region ICommand Members
public void Execute(object parameter) { _executeDelegate(parameter); }
public bool CanExecute(object parameter) { return CanExecuteAction; }
#endregion
private void RaiseCanExecuteActionChanged()
{
if (CanExecuteChanged != null) CanExecuteChanged(this, new EventArgs());
}
static void OnCanExecuteActionChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
(dependencyObject as CanExecuteCommand).RaiseCanExecuteActionChanged();
}
}
ViewModel:
public class LoginViewModel : ILoginViewModel, INotifyPropertyChanged
{
private string _username;
private string _password;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand ReadyCommand { get; private set; } // In Binding con la View
#region ILoginViewModel Members
public string UserName // In Binding con la View
{
get { return _username; }
set
{
if (_username != value)
{
_username = value;
RaisePropertyChanged("UserName");
RaisePropertyChanged("CanLogin");
}
}
}
public string Password // In Binding con la View
{
get { return _password; }
set
{
if (_password != value)
{
_password = value;
RaisePropertyChanged("Password");
RaisePropertyChanged("CanLogin");
}
}
}
public bool CanLogin
{
get { return !string.IsNullOrEmpty(UserName) && !string.IsNullOrEmpty(Password); }
}
public void Login()
{
//... Do Login...
}
#endregion
public LoginViewModel()
{
Binding binding = new Binding("CanLogin");
binding.Source = this;
ReadyCommand = new CanExecuteCommand((arg) => Login(), binding);
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Technorati Tag:
WPF,
ICommand
giovedì 19 marzo 2009
Più volte nello sviluppo Silverlight/WPF mi sono imbattuto nella necessità di utilizzare i file di risorsa (.resx) e devo dire che le prime volte mi sono trovato un po’ in difficoltà dal momento che non riuscivo a capire per quale arcano i miei binding dichiarativi nel codice XAML verso le mie risorse non funzionassero affatto. Andiamo per ordine: supponendo di definire un file di risorsa in questo modo ...
... in fase di design Visual Studio notifica uno “strano” errore,

indipendentemente dal fatto che si utilizzi Public o Internal come access modifier (più precisamente se si selezionasse Internal l’errore diverrebbe AG_E_PARSER_BAD_TYPE). Mantenendo l’access modifier come “Public” ed andando a vedere il codice autogenerato da Visual Studio ci si imbatte in questa piccola “finezza”:
Classe public e costruttore internal!!!
Personalmente ho sempre ovviato al problema nel modo più semplice, impostando il costruttore come public per ogni file di risorsa. Oggi invece mi imbatto in questo post che propone invece una soluzione più elegante a cui non avevo proprio pensato, ovvero realizzare semplicemente una classe pubblica con una singola proprietà che espone la classe associata al file di risorsa. SEMPLICE MA GENIALE!!!
Technorati Tag:
Silverlight 2,
WPF
lunedì 16 marzo 2009
Una caratteristica di un controllo visuale che spesso e volentieri risulta gradevole è la possibilità di mostrare/nascondere il contenuto del controllo stesso tramite un effetto FadeIn/FadeOut. In genere, questo effetto è realizzato tramite una semplice animazione che lavora sull’Opacity del controllo (portandola da 1 a 0 e viceversa) e che magari imposta a Visible/Collapsed la proprietà Visibility subito dopo l’animazione in modo da riempire/liberare istantaneamente un’area specifica dello schermo. In molti casi risulta dunque utile legare l’ effetto FadeIn/FadeOut proprio alla proprietà Visibility del controllo, in modo da ottenere automaticamente le animazioni desiderate al momento della valorizzazione della proprietà (che altrimenti non effettuerebbe ovviamente alcun effetto visuale di transizione). Un modo che ritengo molto interessante per ottenere questo tipo di scenario sfrutta il VisualStateManager : nel seguente esempio vediamo come sia possibile effettuare l’override della proprietà Visibility di un UserControl per agganciare gli effetti di FadeIn/FadeOut agli stati Visible/Collapsed definiti tramite VisualStateManager.
MyControl.xaml
<UserControl xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows" ...>
<Grid x:Name="LayoutRoot">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Visible">
<Storyboard>
<DoubleAnimation Duration="0:0:.5" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity"
From="0" To="1" AutoReverse="False" Completed="VisibleCompleted" />
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Collapsed">
<Storyboard>
<DoubleAnimation Duration="0:0:.5" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Opacity"
From="1" To="0" AutoReverse="False" Completed="CollapsedCompleted" />
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
...
</Grid>
</UserControl>
MyControl.xaml.cs
public partial class MyControl : UserControl
{
private Visibility _visibility;
public event EventHandler VisibilityChanged;
public MyControl() { InitializeComponent(); }
public new Visibility Visibility
{
get { return _visibility; }
set
{
if (_visibility != value)
{
_visibility = value;
OnVisibilityChanged();
}
}
}
protected virtual void OnVisibilityChanged()
{
if (this.Visibility == Visibility.Visible)
{
base.Visibility = this.Visibility;
VisualStateManager.GoToState(this, "Visible", true);
}
else VisualStateManager.GoToState(this, "Collapsed", true);
}
private void CollapsedCompleted(object sender, EventArgs e)
{
base.Visibility = this.Visibility;
FireVisibilityChanged();
}
private void VisibleCompleted(object sender, EventArgs e) { FireVisibilityChanged(); }
private void FireVisibilityChanged()
{
if (VisibilityChanged != null) VisibilityChanged(this, new EventArgs());
}
}
Come si può notare, nell’esempio si è voluto implementare anche un evento VisibilityChanged che viene scatenato ogni volta che l’animazione FadeIn/FadeOut ha terminato la propria esecuzione. Infatti, la transizione visuale da uno stato Visible ad uno stato Collapsed può essere relativamente lunga e di conseguenza potrebbe essere utile sapere esattamente quando il nostro controllo è completamente visibile/nascosto.
lunedì 9 marzo 2009
Molto spesso capita di dover integrare, anche solo per semplici miglioramenti grafici, dei plugin jQuery all’interno delle nostre pagine ASP.NET. Gran parte delle volte l’impatto è indolore mentre in alcuni casi specifici occorre far riferimento ad accorgimenti non banali, soprattutto quando c’è di mezzo ASP.NET Ajax.
Il classico esempio si verifica quando ci si aspetta che la funzione $(document).ready() venga chiamata correttamente anche dopo un asynchronous postback tramite UpdatePanel. Infatti, la funzione $(document).ready() permette di determinare il momento in cui il DOM della pagina è caricato dal browser. Tuttavia, in caso di asynchronous postback il DOM può essere eventualmente modificato e non ricaricato nuovamente. Quindi il nostro codice javascript nella forma:
$(document).ready(function()
{
// codice...
});
... non verrà eseguito!
Un workaround molto semplice per questo tipo di situazione è quello di sfruttare l’evento client-side endRequest generato dopo un postback asincrono, quando il controllo viene restituito al browser. Agganciando il nostro codice jQuery che usa $(document).ready() all’interno di un opportuno event Handler per tale evento possiamo risolvere il problema. Ecco un semplice esempio:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>jQuery .ready() function and asynchronous postback</title>
</head>
<body>
<form id="mainForm" runat="server">
<div>
<asp:ScriptManager ID="scriptManager" runat="server">
<Scripts>
<asp:ScriptReference Path="http://code.jquery.com/jquery-latest.js" />
</Scripts>
</asp:ScriptManager>
<asp:UpdatePanel ID="updatePanel" runat="server">
<ContentTemplate>
<asp:Button ID="btnTest" runat="server" Text="Do Async PostBack" />
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
<script type="text/javascript">
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
function EndRequestHandler(sender, args)
{
if (args.get_error() == undefined) Test();
}
function Test()
{
$(document).ready(function()
{
alert('$(document).ready() called!!!');
});
}
Test();
</script>
</body>
</html>
martedì 3 marzo 2009
(da MSDN) ObjectStateManager tracks query results, and provides logic to merge multiple overlapping query results. It also performs in-memory change tracking when a user inserts, deletes, or modifies objects, and provides the change set for updates. This change set is used by the change processor to persist modifications.
La classe ObjectStateManager in generale gestisce lo stato dei nostri EntityObject. In quei casi in cui i nostri dati devono essere semplicemente presentati in modalità read only può essere utile disabilitare il change tracking dell’ ObjectContext, ottenendo un incremento di performance talvolta significativo.
In particolare, per disabilitare il change tracking occorre utilizzare l’opzione MergeOption.NoTracking dell’enumeration MergeOption, che permette di lavorare con Entità aventi EntityState ‘Detached’. Fondamentalmente esistono due modi per disabilitare il change tracking:
- Utilizzando l’oggetto ObjectQuery. Esiste infatti un overload del costruttore che permette di specificare l’opzione MergeOption desiderata.
- A livello di EntitySet
Vediamo entrambi gli esempi:
Caso 1
using (NorthwindObjectContext context = new NorthwindObjectContext())
{
ObjectQuery<Customer> queryCustomers = new ObjectQuery<Customer>("SELECT VALUE c FROM NorthwindObjectContext.Customers AS c", context, MergeOption.NoTracking);
foreach (Customer customer in queryCustomers)
{
Console.WriteLine("{0} - {1}", customer.ContactName, customer.EntityState.ToString());
}
}
Caso 2
using (NorthwindObjectContext context = new NorthwindObjectContext())
{
context.Customers.MergeOption = MergeOption.NoTracking;
foreach (Customer customer in context.Customers)
{
Console.WriteLine("{0} - {1}", customer.ContactName, customer.EntityState.ToString());
}
}
Technorati Tag:
Entity Framework
venerdì 27 febbraio 2009
Qualche giorno fa mi sono imbattutto in una simpatica applicazione Windows Forms che permette di disegnare su una lavangna virtuale il posizionamento in campo di due squadre di calcio. Un'applicazione per allenatori insomma :D. In breve, il funzionamento è semplicemente basato sull'aggiunta e sullo spostamento di pedine (rosse o blu) su un campo di calcio virtuale con eventuale possibilità di ingrandirne/rimpicciolirne le dimensioni.
Guardando il sorgente ho osservato come l’intera applicazione fosse stata concepita e sviluppata "alla Windows Forms" con l'intera logica piazzata all'interno di ciascun event handler dei vari elementi dell'UI.
Ho quindi pensato di esercitarmi nel fare un po' di refactoring, ma soprattutto di analizzare alcuni aspetti prettamente grafici nel porting di una applicazione WindowsForms ad una versione WPF.
Dal punto di vista architetturale, prima di passare alla versione MVVM ho anzitutto ritenuto opportuno inserire un “passaggio intermedio” nel refactoring, soffermandomi su una versione MVP. Il tutto per mettere sostanzialmente in risalto al mio cervello i principali punti di differenza tra i due pattern. Inoltre, come già accennato, la fase di analisi ha toccato alcune potenzialità grafiche di WPF, come il Drag & Drop/Move e lo Zoom.
In questo post vorrei condividere con voi come mi sono mosso, sperando di ricevere magari qualche utile consiglio ;D
Anzitutto sono partito definendo un mini-core, ovvero un'interfaccia "marker" IView per le View
public interface IView { }
ed una classe generica astratta per il presenter:
public abstract class PresenterBase<T> where T : IView
{
public T View { get; set; }
}
A questo punto ho iniziato il porting della Form principale dell'applicazione di partenza (che si chiamava SoccerBlackBoardForm) realizzando la corrispondente Window WPF (SoccerBlackBoardWindow) che implementa un'interfaccia IBlackboardView dedotta analizzando il codice dell'applicazione originale. Infine ho realizzato una classe presenter BlackboardPresenter per le View che implementano IBlackboardView. Il tutto è rappresentato sinteticamente nel seguente class Diagram:

Omettendo per brevità il codice dell'interfaccia IBlackboardView, riporto direttamente lo scheletro del mio refactoring dal momento che si tratta di codice abbastanza autoesplicativo:
1. La costruzione della Window SoccerBlackBoardWindow ha letteralmente svuotato di logica la corrispondente versione Windows Forms fino ad avere la seguente forma:
SoccerBlackBoardWindow.xaml
<Window ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Vertical">
<ListBox x:Name="lstImages">
<ListBox.ItemTemplate>
<DataTemplate>
<Image ... Source="{Binding}"></Image>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add Image >>" Click="AddImageClick" ...></Button>
<Button Content="Clear" Click="ClearClick" ...></Button>
</StackPanel>
<Canvas x:Name="drawingArea" Grid.Column="1" ...>
<Canvas.Background>
<ImageBrush Stretch="Fill"
ImageSource="Resources/SoccerField.png"></ImageBrush>
</Canvas.Background>
</Canvas>
</Grid>
</Window>
SoccerBlackBoardWindow.xaml.cs
public partial class SoccerBlackBoardWindow : Window, IBlackboardView
{
private BlackboardPresenter _presenter;
#region IBlackboardView Members
public Panel DrawingArea
{
get { return drawingArea; }
}
public Point MousePosition
{
get { return Mouse.GetPosition(drawingArea); }
}
public IList<BitmapSource> AvailableImages
{
get { return (IList<BitmapSource>)lstImages.ItemsSource; }
set { lstImages.ItemsSource = value; }
}
public BitmapSource SelectedImage
{
get { return (BitmapSource)lstImages.SelectedItem; }
set { lstImages.SelectedItem = value; }
}
public void AttachPresenter(BlackboardPresenter presenter) { _presenter = presenter; }
#endregion
public SoccerBlackBoardWindow()
{
InitializeComponent();
_presenter = new BlackboardPresenter(this);
_presenter.LoadImages();
}
protected void AddImageClick(object sender, RoutedEventArgs e) { _presenter.DrawImage(SelectedImage, new Size(100, 100), 0, 0, 0); }
protected void ClearClick(object sender, RoutedEventArgs e) { _presenter.ClearDrawingArea(); }
}
2. Il presenter BlackboardPresenter è stato quindi realizzato per aggiornare la View sia in base agli eventi utente e l'interazione con il model, che in questo caso è dato da una banale collezione readonly di immagini che l'utente ha a disposizione per l'inserimento nella area di disegno (DrawingArea)
Notare come sia semplice realizzare un meccanismo di Drag & Move :D.
BlackboardPresenter.cs
public class BlackboardPresenter : PresenterBase<IBlackboardView>
{
private Point _tmpPosition;
private const int ZOOMDELTA = 10;
public BlackboardPresenter(IBlackboardView view) { View = view; }
public void LoadImages()
{
IList<BitmapSource> availableImages = new List<BitmapSource>();
availableImages.Add(new BitmapImage(new Uri("Resources/SoccerBall.png", UriKind.Relative)));
availableImages.Add(new BitmapImage(new Uri("Resources/blueBall.png", UriKind.Relative)));
availableImages.Add(new BitmapImage(new Uri("Resources/redBall.png", UriKind.Relative)));
View.AvailableImages = availableImages;
}
public void ClearDrawingArea(){ View.DrawingArea.Children.Clear(); }
public Image DrawImage(ImageSource source, Size size, int canvasLeft, int canvasTop,int zIndex)
{
Image image = new Image();
image.Source = source;
image.Height = size.Height;
image.Width = size.Width;
image.Cursor = Cursors.Hand;
Canvas.SetLeft(image, canvasLeft);
Canvas.SetTop(image, canvasTop);
Panel.SetZIndex(image, zIndex);
image.MouseMove += delegate(object o, MouseEventArgs args) { MoveImage(image); };
image.MouseWheel += delegate(object o, MouseWheelEventArgs args) { ZoomImage(image, args.Delta); };
image.MouseDown += delegate(object o, MouseButtonEventArgs args) { CaptureImage(image); };
image.MouseUp += delegate(object o, MouseButtonEventArgs args) { ReleaseImage(image); };
View.DrawingArea.Children.Add(image);
return image;
}
public void ReleaseImage(Image image) { image.ReleaseMouseCapture(); }
public void CaptureImage(Image image)
{
image.CaptureMouse();
_tmpPosition= View.MousePosition;
}
public void MoveImage(Image image)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
Canvas.SetLeft(image, Canvas.GetLeft(image) + (View.MousePosition.X - _tmpPosition.X));
Canvas.SetTop(image, Canvas.GetTop(image) + (View.MousePosition.Y - _tmpPosition.Y));
}
_tmpPosition= View.MousePosition;
}
public void ZoomImage(Image image, int amount)
{
if (amount > 0)
{
image.Height += ZOOMDELTA;
image.Width += ZOOMDELTA;
}
else
{
if ((image.Height - ZOOMDELTA > ZOOMDELTA) && (image.Width - ZOOMDELTA > ZOOMDELTA))
{
image.Height -= ZOOMDELTA;
image.Width -= ZOOMDELTA;
}
}
}
}
Il risultato ottenuto è raffigurato qua sotto: non vi dico quanto è stato divertente usare questa app per spiegare alla mia compagna la tattica del fuorigioco :D.
martedì 24 febbraio 2009
Dal punto di vista della flessibilità, ritengo che WCF sia una delle tecnologie più potenti attualmente disponibili per lo sviluppo di applicazioni orientate ai servizi. In particolare, una delle caratteristiche che apprezzo maggiormente è la facilità di personalizzazione del comportamento runtime degli OperationContract sia client-side che service-side ( pratica per altro molto usata nel WCF REST Starter Kit :D ).
In questo post volevo soffermarmi proprio su questo aspetto, mostrando un semplice esempio di implementazione dell’interfaccia IOperationBehavior, che per l'appunto definisce quattro metodi finalizzati all'estensione del runtime behavior di un'operation:
- AddBindingParameters : serve per aggiungere nuovi elementi di binding utilizzabili dall'operation in fase di esecuzione.
- ApplyClientBehavior : viene chiamato client-side
- prima che il contenuto della richiesta sia serializzato e inviato al service
- dopo che la risposta è stata deserializzata ma prima che i valori restituiti vengano forniti al metodo proxy
- ApplyDispatchBehavior : viene chiamato service-side dopo la deserializzazione dei parametri, ma prima che essi siano forniti all'operation del service.
- Validate : permette di verificare che l'operation soddisfi dei criteri custom ( es. autorizzazioni ).
Vediamo ora una semplice implementazione che mette in evidenza come sia veramente semplice agganciare un OperationBehavior ad un OperationContract tramite un attributo. Gli ambiti di applicazione sono veramente tanti: ad esempio potremmo usare questa tecnica service-side per eseguire della logica custom prima e dopo l’invocazione di un’operation da parte di un client. Nello specifico, supponiamo di voler scrivere in un file di log sul server sia i parametri di input passati all'operation che il risultato dell'invocazione. Per ottenere questo scenario necessitiamo anzitutto della definizione di un oggetto che sia in grado di ispezionare i parametri dell'operation sia prima che dopo la chiamata: in altre parole abbiamo bisogno di un IParameterInspector. Questa interfaccia definisce due metodi, BeforeCall(…) e AfterCall(…), che ci permettono di inserire della logica custom ( sempre sia client-side che service-side ) per accedere alle informazioni inerenti una operation ed i relativi parametri di input/output nelle fasi immediatamente precedenti e successive all’invocazione.
Partiamo dunque dal nostro IParameterInspector:
public class ParameterLogInspector : IParameterInspector
{
private FileInfo _logFileInfo;
public ParameterLogInspector(string fileName)
{
_logFileInfo = new FileInfo(fileName);
if (!_logFileInfo.Exists) _logFileInfo.Create();
}
#region IParameterInspector Members
public object BeforeCall(string operationName, object[] inputs)
{
StringBuilder sb = new StringBuilder();
foreach (object input in inputs) sb.AppendFormat("{0};", input.ToString());
this.Log(string.Format("Operation \"{0}\" is calling... inputs: {1}", operationName, sb.ToString()));
return null;
}
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{
this.Log(string.Format("Operation \"{0}\" called. returnValue: {1}",operationName,returnValue.ToString()));
}
#endregion
private void Log(string message)
{
using (StreamWriter sw = _logFileInfo.AppendText())
{
sw.WriteLine(string.Format("{0}> {1}", DateTime.Now.ToString(), message));
}
}
}
Quindi, ci posizioniamo all'interno del metodo ApplyDispatchBehavior(...) semplicemente per aggiungere il nostro IParameterInspector alla collection ParameterInspectors dell'oggetto DispatchOperation (che rappresenta l'operation service-side) :
public class ParametersLog : Attribute, IOperationBehavior
{
public string FileName { get; set; }
#region IOperationBehavior Members
...
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.ParameterInspectors.Add(new ParameterLogInspector(this.FileName));
}
...
#endregion
}
Il gioco è fatto: non rimane che decorare l'OperationContract di interesse con il nostro attributo ParametersLog per ottenere l' estensione del runtime che ad ogni chiamata scriverà in un file di log i valori dei parametri di input/output .
[ServiceContract]
public interface IAuthentication
{
[OperationContract]
[ParametersLog(FileName="C:/log.txt")]
UserInfo Login(string userName, string password);
[OperationContract]
void Logout(string userName);
}
sabato 21 febbraio 2009
Quando si sviluppano architetture N-tier che prevedono uno o più service layer è sempre consigliabile l’utilizzo di DTO per scambiare dati tra livelli, indipendentemente dal (fuorviante) fatto che ogni DataContract esposto da un servizio (es. WCF) possa essere in mapping 1:1 con i business objects (es. EntityObject). Un DTO infatti è solitamente un POCO le cui proprietà in molti casi si possono mappare sia nel nome che nel tipo in un sottoinsieme di quelle di uno o più oggetti di business.
Ad ogni modo, al fine di aumentare la produttività nel valorizzare un DTO che possiede proprietà in comune nel nome e nel tipo con uno o più EntityObject, può avere senso implementare un extension method che via reflection copi il valore delle proprietà di un oggetto sorgente nelle proprietà di un oggetto destinazione appunto qualora tali proprietà abbiano stesso nome e tipo.
public static void CopyProperties<T1, T2>(this T1 source, T2 destination)
{
if (source == null) throw new ArgumentNullException("Source object can’t be null.");
if (destination == null) throw new ArgumentNullException("Destination object can’t be null.");
PropertyInfo[] destinationPropertyInfos = destination.GetType().GetProperties();
foreach (PropertyInfo propertyInfo in destinationPropertyInfos)
{
if (propertyInfo.CanWrite)
{
source.GetType().GetProperties()
.Where(p => p.CanRead && (p.Name == propertyInfo.Name && p.PropertyType == propertyInfo.PropertyType))
.ToList()
.ForEach(p => propertyInfo.SetValue(destination, p.GetValue(source, null), null));
}
}
}
Quindi, supponendo di avere il seguente object model di partenza...
...è possibile scrivere qualcosa del genere, al fine di evitare la noiosa scrittura delle solite righe di inizializzazione della classe DTO partendo dall’ Entity Objecy:
Customer customerEntity = null;
using (NorthwindEntities entities = new NorthwindEntities())
{
customerEntity = entities.Customers.First(c => c.CustomerID == "ALFKI");
}
CustomerDTO customerDTO = new CustomerDTO();
customerEntity.CopyProperties(customerDTO);
lunedì 16 febbraio 2009
EntityClient (namespace System.Data.EntityClient) è un provider che l’EntityFramework mette a disposizione per accedere ai dati descritti da unEntity Data Model (EDM), ovvero una sorta di gateway per query sul modello concettuale e non sulla relativa rappresentazione fisica.
Le query invocabili dall’ EntityClient vanno dunque scritte in un dialetto di SQL chiamato “EntitySQL”: a runtime, la pipeline compila il testo EntitySQL in un command tree che viene passato al data provider sottostante per la generazione del comando SQL nativo. Dietro le quinte, infatti, EntityClient utilizza i provider ADO.NET per accedere alle sorgenti dati ( es. SqlClient per SQL Server ). L’aspetto interessante è che i risultati di una query EntitySQL possono non limitarsi a semplici tabelle, bensì possono contenere anche gerarchie complesse.
In generale, l’uso di EntityClient è consigliabile in applicazioni model-based che richiedono particolari performance nella misura in cui non si necessita della materializzazione delle Entities a partire da resultset, bensì di un IDataReader consumabile direttamente magari per materializzazioni di oggetti specifici.
In qualità di provider ADO.NET a tutti gli effetti, EntityClient segue i soliti pattern Connection-Command-Parameter-DataReader, etc. dove le suddette classi sono precedute dal prefisso “Entity”. Ecco un esempio di paginazione dati usando EntityClient:
public List<string> GetProductNames(int pageNumber, int pageSize)
{
List<string> productNames = new List<string>();
using (EntityConnection conn = new EntityConnection("name=NorthwindEntities"))
{
string cmdText = string.Format("SELECT VALUE v FROM NorthwindEntities.Products AS v ORDER BY v.ProductName SKIP {0} LIMIT {1}", (pageNumber * pageSize).ToString(), pageSize);
using (EntityCommand cmd = new EntityCommand(cmdText, conn))
{
conn.Open();
using(EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
while (reader.Read()) productNames.Add(reader["ProductName"].ToString());
}
}
}
return productNames;
}
Technorati Tag:
Entity Framework