Bernhard's profileBernhard Grojer - BlogPhotosBlogLists Tools Help

Blog


    October 17

    LINQ Provider – Vom Ausdruck zum Ergebnis – Teil 4


    Teil 4 der Blog-Serie zum Thema LINQ Provider.
    Bevor Sie hier weiterleisen
    sollten sie die ersten 3 Teile gelesen haben.

    Teil 1: Hintergründe und Ziele
    Teil 2: Basisklasse Query<T>
    Teil 3: Basisklasse QueryProvider

    Da wir nun die Basisklassen soweit abgesteckt haben können wir nun dazu übergehen uns um die Implementierung zu kümmern.

    Wir brauchen daher als ersten Punkt einen Provider (abgeleitet von QueryProvider) der unsere abstrakte Funktionalität Provider.Execute(…) implementiert.
    Außerdem müssen wir noch eine Klasse erstellen, die unsere Daten (woher diese auch immer stammen) typsicher repräsentiert.

    Für dieses Beispiel verwende ich nun eine Klasse “Employee” ….

    public class Employee
    {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public bool Active { get; set; }
    }

    … und den dazu passenden Provider …

    public class EmployeeProvider : QueryProvider
    {
            public override object Execute(Expression expression)
            {
                 //Kommt noch …
    return null; } }

    Außerdem ist es noch “hilfreich”, wenn wir uns einen DataContext (sie kennen so etwas eventuell aus LINQ-TO-SQL) erzeugen:

    public class NorthwindDataContext
    {
            QueryProvider Provider = new EmployeeProvider();
    
            public Query<Employee> Employees
            {
                get { return new Query<Employee>(Provider); }
            }
    }

    Und somit können wir nun unsere ersten Abfragen gegen unsere eigene Datenquelle erzeugen.

    var context = new NorthwindDataContext();
    var res = from e in context.Employees
              where e.Active
              orderby e.LastName
              select e;

    Wenn Sie jetzt einen Breakpoint in die Provider.Execute Methode setzen und die Anwendung starten bekommen wir nun den von LINQ erzeugten ExpressionTree als Parameter in die Execute-Methode.

    Diesen ExpressionTree gilt es nun noch auszuwerten, den gang zur Datenquelle zu machen und die Rückgabe zu erstellen.

    Der von LINQ erstellte ExpressionTree enhält hierbei alle Information über die eigentliche Abfrage. Welche Felder inkludiert sind, welche Kritierien bei der Abfrage benötigt werden und auch welche Rückgabewerte erwartet werden.

    Da – wie der Name schon sagt – der ExpressionTree eine Baumstruktur darstellt ist es oft notwendig eine Methode zu entwickeln die sich selbst aufruft und somit den kompletten Baum abarbeitet. Dabei wird eine Abfrage für die eigentliche Datenquelle erstellet.

    In meinem Beispiel hab ich hierfür XML gewählt:

    Die Execute(….) Methode bereitet einen LINQ Abfrage vor die wir entsprechend unseres ExpressionTrees in der ProcessExpression(….) Methode verändern/ersetzen.

    public override object Execute(Expression expression)
    {
          //Create query
          var doc = XDocument.Load(file);
          var query = from emp in doc.Descendants("Employee")
                      select emp;
    
          //Edit query
          ProcessExpression(expression, ref query);
    
          //Transform Data
          var res = GetElementsFromXml(query);
    
          //Return Data
          return res;
    }

    In ProcessEpxression editieren wir nun die Linq To Xml Abfrage (natürlich könnte man hier auch einen SQL Abfrage zusammensetzen mit einem StringBuilder etc.) und haben am Ende die fertige Abfrage für die XML Datei.

    ACHTUNG: Dies hier sollte lediglich einen Ansatz darstellen. In der Praxis wäre noch jede Menge zu tun. (mehere Bedinungen richtig verknüpfen, optimierungen f. d. Abfrage, implementieren aller Operatoren …)

    private void ProcessExpression(Expression e, ref IEnumerable<XElement> resultQuery)
    {
        if (e is UnaryExpression)
        {
            var exp = e as UnaryExpression;
            ProcessExpression(exp.Operand, ref resultQuery);
        }
        else if (e is LambdaExpression)
        {
            var exp = e as LambdaExpression;
            ProcessExpression(exp.Body, ref resultQuery);
        }
        else if (e is BinaryExpression)
        {
            var exp = e as BinaryExpression;
            if (exp.NodeType == ExpressionType.Equal)
            {
                string attrib = string.Empty;
                object value = null;
    
                if (exp.Left is MemberExpression)
                {
                    var left = exp.Left as MemberExpression;
                    attrib = left.Member.Name;
                    value = Expression.Lambda(exp.Right).Compile().DynamicInvoke();
                }
                else if (exp.Right is MemberExpression)
                {
                    var right = exp.Right as MemberExpression;
                    attrib = right.Member.Name;
                    value = Expression.Lambda(exp.Left).Compile().DynamicInvoke();
                }
                else
                {
                    throw new NotImplementedException();
                }
    
                //ChangeTargetExpressionTree
                resultQuery = from emp in resultQuery
                              where emp.Attribute(attrib).Value == GetValueFromMapping(attrib, value).ToString()
                              select emp;
            }
        }
        else if (e is MethodCallExpression)
        {
            var exp = e as MethodCallExpression;
            string attrib, value = string.Empty;
    
            if (exp.Type == typeof(IOrderedQueryable<Employee>))
            {
                attrib = (((exp.Arguments[1] as UnaryExpression).Operand as LambdaExpression).Body as MemberExpression).Member.Name;
                resultQuery = from emp in resultQuery
                              orderby emp.Attribute(attrib).Value
                              select emp;
            }
    
            if (exp.Method.DeclaringType == typeof(string))
            {
                value = Expression.Lambda(exp.Arguments[0]).Compile().DynamicInvoke().ToString();
                attrib = (exp.Object as MemberExpression).Member.Name;
    
                switch (exp.Method.Name)
                {
                    case "Contains":
                        resultQuery = from emp in resultQuery
                                      where emp.Attribute(attrib).Value.Contains(value)
                                      select emp;
                        break;
    
                    case "StartsWith":
                        resultQuery = from emp in resultQuery
                                      where emp.Attribute(attrib).Value.StartsWith(value)
                                      select emp;
                        break;
    
                    default:
                        throw new NotImplementedException();
                }
            }
            else
            {
                foreach (var current in exp.Arguments)
                {
                    ProcessExpression(current, ref resultQuery);
                }
            }
        }
    }

    Damit haben wir unseren ersten lauffähigen Provider und können die Anwendung testen.

    Im 5ten und letzten Teil der Serie werden wir den Provider noch mit funktionalitäten ausstatten, um auch Änderungen speichern zu können (ChangeTracking).

    October 14

    ADC08 - LINQ TO …


    Wie versprochen die Samples sowie die Slides zu meinem Vortrag “LINQ Provider”.

    Noch Fragen? Mailen oder Kommentar schreiben!

    Silverlight 2.0 - RTW

    Silverlight 2.0 wurde heute fertiggestellt. Es enthält jedoch keine Änderungen zur RC0 Version und die Client-Runtime sollte automatisch ein Update erfahren.

    Auch Tools für Visual Studio 2008 SP1 sowie ein Update für Blend 2.0 (SP1) ist erhältlich.

    Download und Infos gibts hier: http://silverlight.net/

    October 10

    .net 4.0 & Visual Studio 2010

    Das .net 4.0 sowieso Visual Studio 2010 bereits angekündigt wurden habe ich schon vor kurzem gebloggt.

    In der Zwischenzeit gibts bereits jetzt mehr Informationen dazu hier.

    LINQ Provider - Vom Ausdruck zum Ergebnis - Teil 3


    Teil 3 von der Blog-Serie zum Thema LINQ Provider.
    Bevor Sie hier weiterleisen
    sollten Teil 1 und Teil 2 gelesen haben.

    Nachdem wir im Teil 2 unsere Query<T> Klasse implementiert haben stehen wir nun vor unserem letzten Problem: den eigentlichen Provider für die Datenquelle

    Dieser muss:

    - die Abfrage in möglichst optimierter Form f. d. Datenquelle entsprechend umwandeln
    - das Ergebnis von der Datenquelle in die (Ziel-)Objekte wandeln und retournieren.

    Genau dafür haben wir bereits vorab die Idee geboren eine entsprechende Provider-Basisklasse zu erstellen, um später nur noch die die eigentliche Logik - Provider.Execute(…) – umzusetzen zu müssen.

    Daher implementieren wir im Provider die Funktionalität um eine Instanz von Query<T> zu bilden und diese zu retournieren (CreateQuery<>()).
    Ein nicht generischer Aufruf ist hier im Moment nicht implementiert.

    public abstract class QueryProvider : IQueryProvider
        {
    
            #region IQueryProvider Members
    
            public IQueryable<T> CreateQuery<T>(Expression expression)
            {
                return new Query<T>(this, expression);
            }
    
            public IQueryable CreateQuery(Expression expression)
            {
                throw new NotImplementedException();
            }
    
            T IQueryProvider.Execute<T>(Expression expression)
            {
                return (T)this.Execute(expression);
            }
    
            object IQueryProvider.Execute(Expression expression)
            {
                return this.Execute(expression);
            }
    
            #endregion
    
            public abstract object Execute(Expression expression);
        }

    In der Basisklasse erzeugen wir außerdem eine abstrakte Methode “Execute” die unser eigentlicher Provider später implementieren muss.
    Die vom IQueryProvider Interface stammenden .Execute<T> Methoden rufen diese auf und reichen das Ergebnis weiter.

    Somit haben wir bereits mehr als nur einen Grundstein gelegt und können nun verschiedene Provider on-top umsetzen und abfragen auf beliebige Datenquellen ermöglichen.

    public class MyCustomProvider : QueryProvider
    {
            public override object Execute(Expression expression)
            {
               //Execute Query, Create return Value(s)
            }
    }

    Im vierten Teil der Serie werden wir uns eine Linq.Abfrage bereit legen und die Execute Methode in unsere konkrete Providerklasse implementieren.

    Sie möchten mehr wissen zum Thema? Auf der ADC08 gibt es eine Session von mir:

    October 09

    DataBinding in WPF/Silverlight – Refresh ohne Code im UI - INotifyPropertyChanged


    Unter WPF und Silverlight ist DataBinding ein leichtes geworden. Wie funktionierts?

    Mithilfe der Klasse (Binding) kann man sowohl im XAML Code als auch via Programmlogik ein DataBinding erstellen:
    <TextBlock Text="{Binding Path=MYPATH, Source=MYSOURCE" />

    MYPATH muss dabei ein Property sein, gegen das man binden möchte (zB die Value-Eigenschaft eines Sliders).
    MYSOURCE hingegen ein Quelle (zB eine Instanz des Sliders)

    Doch wir hätten diesmal gerne eine Uhrzeit im Userinterface (oder irgend einen anderen Wert aus der Programm-Logik).
    Auch das ist in Silverlight/WPF kein Problem und umfasst 3 Schritte.

    1) Unsere Datenquelle (Klasse) umsetzen und INotifyPropertyChanged implementieren
    2) Eine Instanz der Klasse in den Ressourcen ablegen.
    3) DataBinding zwischen der Resource und dem UI-Element erstellen

    1) Die Klasse umsetzen:

    public class RefreshingClock : INotifyPropertyChanged
        {
            DispatcherTimer dt;
    
            public RefreshingClock()
            {
                dt = new DispatcherTimer();
                dt.Interval = TimeSpan.FromSeconds(1);
                dt.Tick += dt_Tick;
                dt.Start();
            }
    
            void dt_Tick(object sender, EventArgs e)
            {
                if (this.PropertyChanged != null)            
                    this.PropertyChanged(this, new PropertyChangedEventArgs("CurrentTime"));
                
            }
    
            public DateTime CurrentTime
            {
                get
                {
                    return DateTime.Now;
                }
    
            }
    
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            #endregion
        }

    Hier ist eigentlich lediglich wichtig, dass wir immer, bei Änderungen d. Property “CurrentTime” unser INotifyPropertyChanged Event triggern müssen.
    Siliverlight/WPF Bindings konsumieren hierbei dieses Event und werden Somit über die Änderung informiert.

    2) Eine Instanz der Klasse bilden:

    <UserControl.Resources>
            <local:RefreshingClock x:Key="Clock" />
    </UserControl.Resources>

    3) Das DataBinding:

    <TextBlock Text="{Binding Path=CurrentTime, Source={StaticResource Clock}}" />

    Und somit haben wir 0 Zeilen Programmcode im eigentlichen UI.

    Das Ergebnis:
    image 

    Code zum Downloden gibt es hier.

    October 07

    LINQ Provider - Vom Ausdruck zum Ergebnis - Teil 2


    Teil 2 von der Blog-Serie zum Thema LINQ Provider.
    Bevor Sie hier weiterleisen sollten
    Teil 1 gelesen haben.


    Im ersten Teil haben wir bereits Basistechnologien abgesteckt und die Hintergründe schon etwas umschifft.
    Wir sind weiteres dazu übergegangen uns mit 2 Basisklassen auseinanderzusetzen.

    Auf der einen Seite haben wir uns dafür Query<T> bereit gestellt, die die eigentliche “Ergebnismenge” in Zukunft repräsentieren soll.
    zB eine Liste von Kunden, Mitarbeitern, Produkten usw. usf.
    Auf der anderen Seite hatten wir noch den QueryProvider, der den eigentlichen Gang zur Datenquelle, anhand der Basis-Interaces erstellt.

    Zuerst beginnen wir nun die Funktionalitäten in Query<T> umzusetzen:

    Hierfür wird es allerdings notwendig, dass wir eine Instanz unseres QueryProvider in dieser Klasse halten können und weiteres einen entsprechenden Konstrukter bereitstellen, der einen QueryProvider sowie optional einen ExpressionTree von LINQ entgegennimmt.
    Wurde uns durch LINQ kein entsprechender ExpressionTree weitergegeben erzeugen wir uns eine ConstantExpression die unsere Ergebnismenge nicht weiter einschränkt.

    public class Query<T> : IOrderedQueryable<T>, IQueryable, IOrderedQueryable
        {
            QueryProvider _Provider;
            Expression _Expression;
    
            public Query(QueryProvider provider)
            {
                this._Provider = provider;
                this._Expression = Expression.Constant(this);
            }
    
            public Query(QueryProvider provider, Expression expression)
                : this(provider)
            {
                this._Expression = expression;
            }
    
           //Implementierung der Interfaces ...
        }

    Bei der Implementierung des Interfaces machen wir nun nichts anderes als “jeweils” Provider.Execute(…) aufzurufen und diesen unsere Expression weitergereicht.
    Die Auswertung und auch der Gang zur Datenquelle obliegt nun unserem Provider.

        public class Query<T> : IOrderedQueryable<T>, IQueryable, IOrderedQueryable
        {
           //...
    
            public IEnumerator<T> GetEnumerator()
            {
                var r = this.Provider.Execute<IEnumerable<T>>(this._Expression) as IEnumerable<T>;
                return r.GetEnumerator();
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                var r = this.Provider.Execute(this._Expression) as IEnumerable;
                return r.GetEnumerator();
            }
      
           //...
         }

    Und somit sind wir nun auch einem Geheimnis von LINQ auf die Schliche gekommen, weshalb Abfragen üblicherweise erst bei Zugriff ausgeführt werden.
    Den Erst das Triggern von GetEnumerator() ruft die Execute Methode unseres Providers auf.

    Unsere nächste Aufgabe wird somit ein Umsetzung unserer Provider-Basisklasse werden. Mehr dazu jedoch im 3ten Teil.

    Sie möchten mehr wissen zum Thema? Auf der ADC08 gibt es eine Session von mir:

    October 03

    ADO.net DataServices


    Mit dem Release vom .net Framework 3.5 SP1 (böse Zungen sagen auch .net 3.6 dazu) haben wir eine relativ einfache Möglichkeit bekommen, um Daten über eine HTTP Schnittstelle bereitzustellen.

    Was müssen wir dafür tun?

    1) Erstellen einer neuen ASP.net Web Application

    image

    2) Ein ADO.net Entity Data Model erstellen (*.edmx)

    3) Ein ADO.net Data Service hinzufügen und adaptieren.

    Imports System.Data.Services
    Imports System.Linq
    Imports System.ServiceModel.Web
    
    Public Class Northwind
        ' TODO: replace [[class name]] with your data class name
        Inherits DataService(Of NorthwindEntities)
    
        ' This method is called only once to initialize service-wide policies.
        Public Shared Sub InitializeService(ByVal config As IDataServiceConfiguration)
            ' TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
            ' Examples:
            ' config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead)
            ' config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All)
    
            config.SetEntitySetAccessRule("*", EntitySetRights.All)
    
        End Sub
    
    End Class

    Achtung das ADO.net DataService muss hierbei vom Context abgeleitet werden.

    4) Testen des Services. –> F5 sollte hierfür reichen.

    Im Firefox wirds sofort klappen.
    Im Internet-Explorer (zumindest IE 8 Beta 2) vermutlich nicht.
    http://127.0.0.1:49338/Northwind.svc/ <- Im IE muss nämlich der letzte Schrägstrich noch manuell dran gemacht werden damits läuft.

    image

    Im einem zukünftigen Eintrag wollen wir das soeben erstellte Service noch konsumieren.

    Fragen dazu? Kommentar posten ;)