
Aggiunta di funzionalità di query più complesse
Il provider disponibile a questo punto fornisce una modalità molto limitata per consentire ai client di specificare le informazioni sul percorso nella query LINQ. In particolare il provider è solo in grado di ottenere le informazioni sul percorso da espressioni di uguaglianza come Place.Name == "Seattle" o Place.State == "Alaska" (Place.Name = "Seattle" o Place.State = "Alaska" in Visual Basic).
Nella procedura successiva viene illustrato come aggiungere il supporto per una modalità aggiuntiva al fine di specificare le informazioni sul percorso. Dopo aver aggiunto questo codice, il provider sarà in grado di estrarre le informazioni sul percorso dalle espressioni della chiamata al metodo, ad esempio place.Name.StartsWith("Seat").
Per aggiungere il supporto per i predicati contenenti String.StartsWith
Nel progetto LinqToTerraServerProvider, aggiungere il metodo VisitMethodCall alla definizione della classe LocationFinder.
Protected Overrides Function VisitMethodCall(ByVal m As MethodCallExpression) As Expression
If m.Method.DeclaringType Is GetType(String) And m.Method.Name = "StartsWith" Then
If ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "Name") Or _
ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "State") Then
_locations.Add(ETH.GetValueFromExpression(m.Arguments(0)))
Return m
End If
End If
Return MyBase.VisitMethodCall(m)
End Function
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.DeclaringType == typeof(String) && m.Method.Name == "StartsWith")
{
if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "Name") ||
ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "State"))
{
locations.Add(ExpressionTreeHelpers.GetValueFromExpression(m.Arguments[0]));
return m;
}
}
return base.VisitMethodCall(m);
}
Ricompilare il progetto LinqToTerraServerProvider.
Per testare la nuova funzionalità del provider, aprire il file Program.cs (o Module1.vb in Visual Basic) nel progetto ClientApp. Sostituire il codice nel metodo Main con il codice seguente:
QueryableTerraServerData<Place> terraPlaces = new QueryableTerraServerData<Place>();
var query = from place in terraPlaces
where place.Name.StartsWith("Lond")
select new { place.Name, place.State };
foreach (var obj in query)
Console.WriteLine(obj);
Dim terraPlaces As New QueryableTerraServerData(Of Place)
Dim query = From place In terraPlaces _
Where place.Name.StartsWith("Lond") _
Select place.Name, place.State
For Each obj In query
Console.WriteLine(obj)
Next
Eseguire il programma e visualizzare i risultati. Dovrebbero essere presenti approssimativamente 29 risultati.
Nella procedura successiva viene illustrato come aggiungere la funzionalità al provider per consentire alla query client di specificare le informazioni sul percorso utilizzando due metodi aggiuntivi, in particolare Enumerable..::.Contains e List<(Of <(T>)>)..::.Contains. Dopo aver aggiunto questo codice, il provider sarà in grado di estrarre le informazioni sul percorso dalle espressioni della chiamata al metodo nella query client, ad esempio placeList.Contains(place.Name), dove l'insieme placeList è un elenco concreto fornito dal client. Il vantaggio di consentire ai client di utilizzare il metodo Contains è che possono specificare un numero qualsiasi di percorsi semplicemente aggiungendoli a placeList. Variando il numero di percorsi non si modifica la sintassi della query.
Per aggiungere il supporto per le query contenenti il metodo Contains nella relativa clausola 'where'
Nella definizione della classe LocationFinder del progetto LinqToTerraServerProvider sostituire il metodo VisitMethodCall con il codice seguente:
Protected Overrides Function VisitMethodCall(ByVal m As MethodCallExpression) As Expression
If m.Method.DeclaringType Is GetType(String) And m.Method.Name = "StartsWith" Then
If ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "Name") Or _
ETH.IsSpecificMemberExpression(m.Object, GetType(Place), "State") Then
_locations.Add(ETH.GetValueFromExpression(m.Arguments(0)))
Return m
End If
ElseIf m.Method.Name = "Contains" Then
Dim valuesExpression As Expression = Nothing
If m.Method.DeclaringType Is GetType(Enumerable) Then
If ETH.IsSpecificMemberExpression(m.Arguments(1), GetType(Place), "Name") Or _
ETH.IsSpecificMemberExpression(m.Arguments(1), GetType(Place), "State") Then
valuesExpression = m.Arguments(0)
End If
ElseIf m.Method.DeclaringType Is GetType(List(Of String)) Then
If ETH.IsSpecificMemberExpression(m.Arguments(0), GetType(Place), "Name") Or _
ETH.IsSpecificMemberExpression(m.Arguments(0), GetType(Place), "State") Then
valuesExpression = m.Object
End If
End If
If valuesExpression Is Nothing OrElse valuesExpression.NodeType <> ExpressionType.Constant Then
Throw New Exception("Could not find the location values.")
End If
Dim ce = CType(valuesExpression, ConstantExpression)
Dim placeStrings = CType(ce.Value, IEnumerable(Of String))
' Add each string in the collection to the list of locations to obtain data about.
For Each place In placeStrings
_locations.Add(place)
Next
Return m
End If
Return MyBase.VisitMethodCall(m)
End Function
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.DeclaringType == typeof(String) && m.Method.Name == "StartsWith")
{
if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "Name") ||
ExpressionTreeHelpers.IsSpecificMemberExpression(m.Object, typeof(Place), "State"))
{
locations.Add(ExpressionTreeHelpers.GetValueFromExpression(m.Arguments[0]));
return m;
}
}
else if (m.Method.Name == "Contains")
{
Expression valuesExpression = null;
if (m.Method.DeclaringType == typeof(Enumerable))
{
if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[1], typeof(Place), "Name") ||
ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[1], typeof(Place), "State"))
{
valuesExpression = m.Arguments[0];
}
}
else if (m.Method.DeclaringType == typeof(List<string>))
{
if (ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[0], typeof(Place), "Name") ||
ExpressionTreeHelpers.IsSpecificMemberExpression(m.Arguments[0], typeof(Place), "State"))
{
valuesExpression = m.Object;
}
}
if (valuesExpression == null || valuesExpression.NodeType != ExpressionType.Constant)
throw new Exception("Could not find the location values.");
ConstantExpression ce = (ConstantExpression)valuesExpression;
IEnumerable<string> placeStrings = (IEnumerable<string>)ce.Value;
// Add each string in the collection to the list of locations to obtain data about.
foreach (string place in placeStrings)
locations.Add(place);
return m;
}
return base.VisitMethodCall(m);
}
Questo metodo aggiunge ogni stringa dell'insieme, al quale viene applicato Contains, all'elenco di percorsi in base a cui eseguire una query sul servizio Web. Un metodo denominato Contains viene definito in Enumerable e List<(Of <(T>)>). Pertanto, il metodo VisitMethodCall deve verificare entrambi questi tipi dichiaranti. Enumerable.Contains viene definito come metodo di estensione, per cui l'insieme al quale viene applicato rappresenta in realtà il primo argomento al metodo. List.Contains viene definito come metodo di istanza, per cui l'insieme al quale viene applicato rappresenta l'oggetto ricevente del metodo.
Ricompilare il progetto LinqToTerraServerProvider.
Per testare la nuova funzionalità del provider, aprire il file Program.cs (o Module1.vb in Visual Basic) nel progetto ClientApp. Sostituire il codice nel metodo Main con il codice seguente:
QueryableTerraServerData<Place> terraPlaces = new QueryableTerraServerData<Place>();
string[] places = { "Johannesburg", "Yachats", "Seattle" };
var query = from place in terraPlaces
where places.Contains(place.Name)
orderby place.State
select new { place.Name, place.State };
foreach (var obj in query)
Console.WriteLine(obj);
Dim terraPlaces As New QueryableTerraServerData(Of Place)
Dim places = New String() {"Johannesburg", "Yachats", "Seattle"}
Dim query = From place In terraPlaces _
Where places.Contains(place.Name) _
Order By place.State _
Select place.Name, place.State
For Each obj In query
Console.WriteLine(obj)
Next
Eseguire il programma e visualizzare i risultati. Dovrebbero essere presenti approssimativamente 5 risultati.