code it

Martins Tech Blog

Zugriff auf Listeninhalte mit LINQ to SharePoint

In der in diesem Jahr erscheinenden Version bietet SharePoint mit der Assembly Microsoft.SharePoint.Linq.dll (zu finden im Ordner 14\ISAPI) die Möglichkeit, recht einfach Listeninhalte abzufragen. Wenn man sich etwas näher damit beschäftigt, kann man sehr viele Parallelelen zu LINQ to Objects und Linq to SQL entdecken.

Erster Schritt ist, eine Referenz auf die genannte Assembly hinzuzufügen. Ab diesem Zeitpunkt steht der Namespace Microsoft.SharePoint.Linq zur Verfügung. Dieser beinhaltet die Klasse DataContext, über die ähnlich wie bei LINQ to SQL auf die Inhalte zugegriffen werden kann. Die Initialisierung des Kontexts ist recht einfach – hierzu wird einfach im Konstruktor des Objekts die Url der zu verwendenden Site übergeben.

Vor dem Erfolg steht aber ein wenig Arbeit, denn der Zugriff ist typsicher und so müssen für die verwendeten Inhaltstypen Klassen angelegt werden, damit klar ist, welche Property sich in welchem Listenfeld abbildet und wie darauf zugegriffen werden muss. Diese kann man selbst schreiben, oder man nutzt dafür ein Tool ;). Auch hier zeigen sich wieder Parallelen zu bereits Bekanntem: Mit Hilfe des Tools SPMetal (zu finden im Ordner 14\BIN) können die Klassen auf Basis einer bestehenden Site automatisch erstellt werden.

Ab diesem Zeitpunkt ist es recht einfach, wie das folgende Beispiel zeigt:

DataContext data = new DataContext("http://mysite/");

EntityList<Page> wikiPages = data.GetList<Page>("Pages");
var filteredPages = from wikiPage in wikiPages
            where wikiPage.Name == "product-presentation.aspx"
            select wikiPage;

foreach (var filteredPage in filteredPages)
{
    Console.WriteLine(filteredPage.Path);
}

Wenn man der Dokumentation Glauben schenken darf, wird bei der Ausführung versucht, so viel wie möglich in CAML zu übersetzen und direkt und damit schnell auszuführen und der Rest wird dann mit Hilfe von LINQ to Objects im Speicher ausgeführt. Allerdings muss ich dazusagen, dass SharePoint 2010 noch nicht final ist und sich sowohl an den Schnittstellen als auch an der Doku noch einiges tun kann.

Nachlese zum Treffen der .NET Usergroup Dresden am 24.02.2010

Vergangenen Mittwoch war es wieder einmal soweit und es fand ein Treffen der .NET Usergroup Dresden statt. Wie bereits angekündigt, hatten wir dieses Mal Alexander Groß zu Gast, der uns ein aktuelles Projekt zeigte. Fachlich ging es dabei um ein Gebiet mit dem sich wohl die Wenigsten schon beschäftigt haben. Aber das war ja auch nicht das was uns hauptsächlich interessierte.
Im Gegensatz zu den meisten anderen Treffen, die hauptsächlich darauf ausgerichtet sind, dass anhand von Slides ein bestimmtes Themengebiet rübergebracht wird, ging es dieses Mal eher um "Diskussion am lebenden Objekt" von der beide Seiten etwas haben. Und so ging es auch nach einer kurzen Einleitung und Vorstellung des fachlichen Hintergrunds in tiefe und interessante Diskussionen. Alexander zeigte, wie durch AutoMapper Objekte der Business-Logik zu Views transformiert werden können, wie er Castle Windsor einsetzt, um Abhängigkeiten von Objekten in Tests aufzulösen, wie mit Hilfe von Rake dynamische Konfigurationen aus Templates erzeugt werden können und wie letztenendes die Applikation mit Hilfe von MSDeploy auf das produktive System kommt.
Zusammenfassend haben wohl alle den ein oder anderen Anstupser bekommen, um in ihren eigenen Projekten auch mal über den Tellerrand zu schauen und auch Alex konnte ein wenig Feedback zu seiner Implementierung bekommen.

LINQ-Queries dynamisch aufbauen

Einer der größten Vorteile von LINQ to Objects ist es, dass man typsicher auch sehr komplexe Abfragen über Objektstrukturen durchführen kann. Zuweilen möchte man aber dem Anwender die Möglichkeit bieten, selbst zu bestimmen, welche Operationen mit den Objektstrukturen durchgeführt werden. Als Beispiel soll hier die dynamische Filterung und Sortierung anhand der Member des Objekts dienen. Spätestens zu diesem Zeitpunkt wird eines der größten Pro's von LINQ zum Verhängnis - die Typsicherheit.

Um hier eine gewisse Dynamik unterzubringen muss man zunächst wissen, wie LINQ-Queries aufgebaut sind. Sie bestehen aus sogenannten Expression Trees, die dann ausgeführt werden. Liegt ein LINQ-Query vor, kann man sich z.B. mit dem Expression Tree Viewer aus den C# Samples den Expression-Tree ansehen. 

Um hier dynamische Filterung und/oder Sortierung zu implementieren, gilt es, einen solchen Baum dynamisch aufzubauen. Das ist gar nicht so kompliziert, wie es sich auf den ersten Blick anhört.

Angenommen, es liegt eine generische List mit Adressen vor (im Beispiel Restaurant-Adressen) - mit den für Adressdaten typischen Membern (Name, Straße, Postleitzahl, Ort). Per Parameter soll definiert werden können, welche Eigenschaft gefiltert werden soll und wie das Filterkriterium ist -gleiches gilt für die Sortierung. 

Im Beispiel definiert die Klasse RestaurantRepository Zugriffsmethoden für den Zugriff auf Listen vom Typ Restaurant. Dabei kann mittels GetRestaurants eine komplette Liste abgerufen werden die Methode GetFilteredRestaurants implementiert den dynamischen Aufbau. Als Parameter dient hier zunächst ein Dictionary, dessen Key den zu filternden Membernamen und dessen Value den zu filternden Wert beinhaltet - sowie eine Liste, deren Inhalt definiert nach welchen Membern sortiert werden soll.

public static IEnumerable<Restaurant> GetFilteredRestaurants(Dictionary<string, string> filter, List<string> sort)
{
IEnumerable<Restaurant> restaurants = GetRestaurants();


#region get lambdas
// where
ParameterExpression pe = Expression.Parameter(typeof(Restaurant), "address");
Expression whereExpression = null;
foreach (string filterAttribute in filter.Keys)
{
   Expression left = MemberExpression.Property(pe, filterAttribute);

   string filterValue = filter[filterAttribute];

   Expression right = Expression.Constant(filterValue);

   if (whereExpression == null)
   {
       whereExpression = Expression.Equal(left, right);
   }
   else
   {
       whereExpression = Expression.And(whereExpression, Expression.Equal(left, right));
   }
}

Expression<Func<Restaurant, bool>> whereCondition = Expression.Lambda<Func<Restaurant, bool>>(whereExpression, new ParameterExpression[] { pe });

var filteredItems = restaurants.AsQueryable().Where(whereCondition);

// order by
Expression queryExpr = filteredItems.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";

foreach (string sortCriteria in sort)
{
   var type = typeof(Restaurant);
   var property = type.GetProperty(sortCriteria);
   var parameter = Expression.Parameter(type, "p");
   var propertyAccess = Expression.MakeMemberAccess(parameter, property);
   var orderByExpression = Expression.Lambda(propertyAccess, parameter);

   queryExpr = Expression.Call(typeof(Queryable), methodAsc,
                                           new[] { type, property.PropertyType },
                                           queryExpr, Expression.Quote(orderByExpression));
   methodAsc = "ThenBy";
   methodDesc = "ThenByDescending";
}

#endregion

return (filteredItems.Provider.CreateQuery(queryExpr)).ToList();
}

Zunächst wird eine ParameterExpression instanziiert. Da Member mit Konstanten verglichen werden sollen, wird für jedes Filterkriterium eine MemberExpression und eine Konstante initiiert. Beides wird mit Expression.Equal verkettet. Mit Hilfe von Expression.And können mehrere solcher Vergleiche aneinandergekettet werden.

Ist die Filterung durchgeführt, kann dann noch die Sortierung bearbeitet werden. Auch hier wird wieder eine Expression mit MemberAccess initiiert. Anhand des Wertes kann dann mit Hilfe von Expression.Call die Sortierung durchgeführt werden.

Ein kleines Testprogramm zeigt dann, dass der Code auch funktioniert...

Treffen der .NET Usergroup Dresden

Das letzte Treffen der .NET Usergroup ist noch gar nicht so lange her - letzten Mittwoch war es soweit. Eigentlich wollte ich dort auch einen Vortrag halten, aber eine fiese Erkältung hat mich davon abgehalten. Die Präsentation ist aber nur aufgeschoben und wir suchen schon nach einem neuen passenden Termin. Außerdem ärgere mich auch ein wenig, den Vortrag von Erik zum Thema Azure verpasst zu haben

Und wo ich einmal dabei bin: Das nächste Treffen steht auch schon direkt vor der Tür: Die .NET-Usergroup Dresden trifft sich das nächste Mal am 24.02. um 18:00 Uhr. Auch dieses Mal verspricht es wieder sehr interessant zu werden: Alexander Groß - vielen bekannt von der .NET Usergroup Leipzig - macht mit uns einen Rundgang durch sein aktuelles Projekt. Technisch wird das sehr interessant, denn mit dabei ist eine bunte Mischung von Technologien, Produkten und Frameworks - um nur wenige zu nennen: FubuMVC, NHibernate, MSpec, AutoMapper, ReSharper, Rake und MSDeploy. Ohne Slides aber dafür direkt am lebenden Objekt gezeigt, bleibt viel Raum für Diskussionen und Fragen.

Wir treffen uns dieses Mal bei der Communardo Software GmbH. Nähere Informationen zum Termin und eine Anfahrtsbeschreibung gibt es natürlich auch.