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.

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...

Unerwartete ChangeConflictException in LinqToSql

Das Löschen eines Datensatzes in LinqToSql in der Regel recht einfach: Zunächst ermittelt man das zu löschende Entity und ruft im Folgenden zunächst die Methode DeleteOnSubmit und im Anschluss SubmitChanges auf.

public void RemoveAddressById(int id)
{
    using (AddressRepositoryDataContext context = new AddressRepositoryDataContext(ConnectionString))
    {
        Address addressToDelete = (from address in context.Addresses
                                   where address.Id == id
                                   select address).FirstOrDefault();
        if (addressToDelete != null)
        {
            context.Addresses.DeleteOnSubmit(addressToDelete);
            context.SubmitChanges();
        }
    }
}

Dieses Vorgehen war bei mir bisher immer von Erfolg gekrönt. Heute allerdings funktionierte es plötzlich nicht mehr - bei der Ausführung erschien eine ChangeConflictException.

Ein Konflikt konnte es nicht sein, denn es handelte sich um die einzige Applikation auf dieser Datenbank und es wurden auch keine weiteren Datenänderungen durchgeführt.

Seltsam sah auch das automatisch abgesetzte SQL-Statement aus - so kann das ja nicht funktionieren....

Was war passiert? Zusammengefasst: Das in der dbml-Datei gespeicherte Datenbankschema stimmte nicht mehr mit dem real existierenden Datenbankschema überein. Beim Entwurf waren alle Spalten als Not Nullable definiert. In der Zwischenzeit war eine der Spalten Nullable geworden. Beim entsprechenden Datensatz stand nun in dieser Spalte auch ein Null-Wert. Offenbar kommt LinqToSql damit nicht klar. Nach einer Aktualisierung des Schemas der dbml-Datei funktionierte alles wieder wie gewohnt.

Bliebe zu wünschen übrig, dass hier eine aussagekräftigere Fehlermeldung kommt - zumal der Wert der Spalte für eine Delete-Operation überhaupt nicht notwendig ist, da der zu löschende Datensatz ja eindeutig anhand des Primärschlüssels erkennbar gewesen wäre.

Mit LINQ To Objects fehlende Elemente finden

Gelegentlich möchte man nicht nur wissen, welche Werte vorliegen, sondern auch welche Werte fehlen. Ein Beispiel dafür ist folgendes Szenario: Man erwartet Werte für jede Kalenderwoche des Jahres und erhält aber nur 49 Werte. Statt mit einer Schleife durch alle möglichen Werte zu iterieren kann man dazu auch LINQ bemühen:

public static class ExtensionMethods
{
    /// <summary>
    /// Gets the missing numbers in a list of integers.
    /// </summary>
    /// <param name="list">A list of integers</param>
    /// <param name="first">Minimum value</param>
    /// <param name="last">Maximum value</param>
    /// <returns>Missing integers</returns>
    public static IEnumerable<int> GetMissing(this IEnumerable<int> list, int first, int last)
    {
        // range that contains all numbers in the interval
        var range = Enumerable.Range(first, last - first + 1);

        // getting the difference
        var missingNumbers = range.Except(list);

        return missingNumbers;
    }

    /// <summary>
    /// Gets the missing numbers in a list of integers.
    /// </summary>
    /// <param name="list">A list of integers</param>
    /// <returns>Missing integers</returns>
    public static IEnumerable<int> GetMissing(this IEnumerable<int> list)
    {
        if (list.Count() < 2)
            return new int[0];
            
        // min value
        int first = list.Min();
        // max value
        int last = list.Max();

        // range that contains all numbers in the interval
        var range = Enumerable.Range(first, last - first + 1);

        // getting the difference
        var missingNumbers = range.Except(list);

        return missingNumbers;
    }
}

Das Prinzip ist recht simpel: Zunächst wird ermittelt, wie der erwartete Wertebereich ist und mittels der Extension-Method Range() eine Variable mit allen erwarteten Werten gefüllt. Im Anschluss daran ermittelt die Extension-Method Except() alle fehlenden Werte.

Ermittlung der größten Datei(en) in einer Ordnerstruktur

Wie kann man ausgehend von einem Ordner von allen Dateien - auch die die in Unterordnern sind - die größte bzw. die größten ermitteln? 

Das Ziel ist ganz klar - es wird aus der Ordnerstruktur das FileInfo-Objekt benötigt, dessen Eigenschaft Length am größten ist. Um an diese Objekte zu kommen, gibt es zum einen die Möglichkeit, rekursiv durch alle Unterordner gehen und für alle Dateien ein solches Objekt zu erzeugen und sich die relevanten Objekte zu speichern. Eine andere Möglichkeit ist, per Directory.GetFiles an die Dateipfade und darüber diese FileInfo-Objekte zu instanziieren:

// define the root
const string rootPath = @"C:\Program Files\Microsoft SQL Server";

// check if the directory exists
if (!Directory.Exists(rootPath))
    throw new DirectoryNotFoundException();

// get all file names in the directory
// warning: may throw System.UnauthorizedAccessException
string[] fileNames = Directory.GetFiles(rootPath, "*.*", SearchOption.AllDirectories);

// get all FileInfo objects from file names
var files = fileNames.Select(fileName => new FileInfo(fileName));

// query the largest file
FileInfo largestFile = (from file in files
                        orderby file.Length descending
                        select file).First();

// query the top 10 largest files
FileInfo[] topTenLargestFiles = (from file in files
                               orderby file.Length descending
                               select file).Take(10).ToArray();

Vorteil dieser Methode: Der Code ist kurz und übersichtlich. Aber es gibt auch mindestens einen Nachteil: Besteht nach Code Access Policy kein Zugriff auf eine der abgefragten Dateien oder Ordner, so wirft Directory.GetFiles eine System.UnauthorizedAccessException und bricht ohne Ergebnis ab. 

Die Selektion der größten Datei(en) selbst ist dann, wie gezeigt, sehr leicht mit LINQ zu realisieren.

Mit LINQ To Objects jedes n-te Element selektieren

Besonders nach string.Split()-Aufrufen ist es mir schon häufiger untergekommen, dass ich nur jedes 2. Element aus einem Ergebnis-Array bekommen wollte. Meist läuft man dann einfach mit einer for-each-Schleife durch das Array und nimmt sich die passenden Elemente.

Mit Hilfe der Extension-Methods, die Linq bietet, kann man die gleiche Funktionalität auch erreichen. Im folgenden Beispiel wird aus dem Array jedes 2. Element herausgelöst:

string[] values = new [] { "a", "b", "c", "d" };
var result = values.Select(value => value)
                   .Where((value, index) => (index+1) % 2 == 0);

LINQ to XML mit XML-Namespaces

Der Zugriff auf XML-Strukturen ist mit Linq-to-Xml recht einfach. Interessant wird es dann, wenn Namespaces mit im Spiel sind.

Nehmen wir mal diese XML-Datei an:

<library xmlns="http://schemas.mylibrary.com">
    <books>
        <book title="book 1" isbn="1234567890" />
        <book title="book 2" isbn="1234567891" />
        <book title="book 3" isbn="1234567892" />
    </books>
</library>

Ein Zugriffsversuch ohne Namespaceangabe bringt erwartungsgemäß keine Ergebnisse:

XDocument library = XDocument.Load("C:\\library.xml", LoadOptions.None);

var booktitles = from book in library.Descendants("book")
                 select book.Attribute("title").Value;

Lösung bietet die Angabe des Namespaces:

XDocument library = XDocument.Load("C:\\library.xml", LoadOptions.None);

var booktitles = from book in library.Descendants(XNamespace.Get("http://schemas.mylibrary.com") + "book")
                 select book.Attribute("title").Value;

Neuesten Datensatz ermitteln mit LinqToObjects

Um aus einer Auflistung von Objekten mit einem Timestamp das jeweils aktuellsten zu bekommen müssen mehrere Linq-Operatoren verknüpft werden.

Ausgangslage soll folgende Liste von Objekten des Typs WorkItem sein:

List<WorkItem> workitems = new List<WorkItem>();
workitems.Add(new WorkItem() { UserId = 101, Comment = "comment for 2009-06-17", TimeStamp = new DateTime(2009, 06, 17) });
workitems.Add(new WorkItem() { UserId = 101, Comment = "comment for 2009-06-18", TimeStamp = new DateTime(2009, 06, 18) });
workitems.Add(new WorkItem() { UserId = 101, Comment = "comment for 2009-06-19", TimeStamp = new DateTime(2009, 06, 19) });
workitems.Add(new WorkItem() { UserId = 101, Comment = "comment for 2009-06-20", TimeStamp = new DateTime(2009, 06, 20) });
workitems.Add(new WorkItem() { UserId = 102, Comment = "comment for 2009-06-18", TimeStamp = new DateTime(2009, 06, 18) });
workitems.Add(new WorkItem() { UserId = 102, Comment = "comment for 2009-06-19", TimeStamp = new DateTime(2009, 06, 19) });
workitems.Add(new WorkItem() { UserId = 102, Comment = "comment for 2009-06-21", TimeStamp = new DateTime(2009, 06, 21) });

Ziel ist nun, pro UserId das WorkItem mit dem höchsten TimeStamp zu ermitteln. Zunächst muss der höchste TimeStamp pro Benutzer ermittelt werden. Das geht noch recht einfach mit einem grouped max:

from wi in workitems
group wi by wi.UserId into g
select new { UserId = g.Key, TimeStamp = g.Max(w => w.TimeStamp) }

Das Ergebnis enthält nun sozusagen die Schlüsselspalten, um eine Filterung durchzuführen. Diese muss nun mittels Join mit der originalen Liste durchgeführt werden. Problem hierbei ist, dass im equals des join-Operators nur ein Kriterium angegeben werden kann. Der Schlüssel für die Filterung besteht aber aus 2 Kriterien: UserId und TimeStamp. Die Lösung ist, in diesem Fall den Join über einen anonymen Typ zu realisieren:

var results = from workitem in workitems
        join newestitem in
            (from wi in workitems
             group wi by wi.UserId into g
             select new { UserId = g.Key, TimeStamp = g.Max(w => w.TimeStamp) })
        on new { workitem.UserId, workitem.TimeStamp } equals new { newestitem.UserId, newestitem.TimeStamp }
        select workitem;

LinqToSql - System.ObjectDisposedException: Cannot access a disposed object.

Versucht man mit LinqToSql auf Kinddaten des aktuellen Datensatzes zuzugreifen, kann es zu folgender Fehlermeldung kommen:

Exception: System.ObjectDisposedException: Cannot access a disposed object. 
Object name: 'DataContext accessed after Dispose.'.

Das kann dann passieren, wenn per Foreign Key verknüpfte Datensätze einer Variable die innerhalb des using-Blocks gefüllt wurde außerhalb des using-Blocks verwendet werden soll.

Parent myParent = null;

using (myDatabaseDataContext context = new myDatabaseDataContext())
{
    myParent = (from parent in context.Parent
               where parent.Id == id
               select parent).FirstOrDefault();
}
int childCount = myParent.Children.Count;

Die letzte Anweisung wirft den Fehler. Grund ist die verzögerte Ausführung von Linq. Der Datenbankzugriff wird erst durchgeführt, wenn das erste mal darauf zugegriffen wird. Dafür gibt es 2 mögliche Lösungen:

  1. Erzwingen der sofortigen Ausführung durch Aufruf von .ToList(), .Single() oder .First() o.ä.
  2. Verwendung von DataLoadOptions

Die erste Lösung ist recht trivial. Der Aufruf von .ToList() zwingt den DataContext, die Daten abzurufen:

Parent myParent = null;

using (myDatabaseDataContext context = new myDatabaseDataContext())
{
    myParent = (from parent in context.Parent
                   where parent.Id == id
                   select parent).FirstOrDefault();
    List myChildren = myParent.Children.ToList();
}

int childCount = myParent.Children.Count;

Auch die zweite Lösung ist recht trivial. Mittels DataLoadOptions kann dem Context mitgeteilt werden, welche Datensätze zusätzlich abgerufen werden sollen:

Parent myParent = null;

using (myDatabaseDataContext context = new myDatabaseDataContext())
{
    DataLoadOptions options = new DataLoadOptions();
    options.LoadWith<Parent>(p => p.Children);
    context.LoadOptions = options;
    myParent = (from parent in context.Parent
                   where parent.Id == id
                   select parent).FirstOrDefault();
}

int childCount = myParent.Children.Count;

InvalidOperationException in First()-Methode vermeiden

In einigen meiner Linq-Anweisungen hab in der Vergangenheit die First()-Methode verwendet, um sicherzugehen, dass ich genau ein Element zurückbekomme - analog zu TOP 1 in SQL. Allerdings hab ich lernen müssen, dass Linq zwar so ähnlich aussieht und so ähnlich funktioniert, aber eben doch nicht gleichzusetzen ist. Die genannte Methode verhält sich nämlich unterschidlich zu ihrem SQL-Pendant. Gibt es gar keine Datensätze, die den Kriterien entsprechen, gibt SQL auch keine Datensätze zurück. Linq hingegen wirft eine InvalidOperationException, wo ich mir null erhofft hätte: 

Die Lösung in diesem Fall ist, dass nicht First() verwendet wird, sondern FirstOrDefault(). Da der Standardwert eines Objekts null ist, bekomm ich nun auch ohne Exception null zurückgegeben: