image Ich habe mich diese Woche wieder mit den Core Funktionen der ADO.NET Data Services beschäftigt. Wir nutzen aktuell das Microsoft Ex-Astoria Projekt, um eine SQL 2008 Datenbank (läuft mit SQL Mirroring) über das Internet an eine Applikation zu binden. An sich eine tolle Sache, aber immer wieder gibt es “neue” Situationen auf die man reagieren muss. In der klassischen Programmierung (C#, SQL über TCP 1433, …) kommt es recht selten vor, dass die Verbindung zur Datenschicht (SqlNativeClient, OLEDB, ODBC, …) unterbrochen wird. In unserem Szenario steckt viel “Cloud” in der Lösung und somit müssen wir mit dem “nicht immer” funktionierenden Internet rechnen. Aktuelle Implementierungen von Microsoft im Cloud-Umfeld, haben für solche Probleme im Code meist die Möglichkeit anzugeben, wie oft der Request wiederholt werden soll. Die ADO.NET Data Services haben so was noch nicht. Also was machen? Wir haben uns für den Weg der Extension Method entschieden. Und so sieht das dann aus (erste Tests waren sehr vielversprechend):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Services.Client;
using System.Diagnostics;
 
namespace Core.ExtensionMethods
{
    public static class DataServiceContextExtension
    {
        public static DataServiceResponse SaveChangesWithRetry(this DataServiceContext ctx, int retryCount)
        {
            for (int i = 0; i < retryCount; i++)
            {
                try
                {
                    return ctx.SaveChanges();
                }
                catch (System.Data.Services.Client.DataServiceRequestException ex)
                {
                    #region Deeper analysis, disabled
                    //foreach (ChangeOperationResponse cor in ex.Response)
                    //{
                    //    if (cor.Descriptor is EntityDescriptor)
                    //    {
                    //        EntityDescriptor ed = (EntityDescriptor)cor.Descriptor;
                    //        Trace.WriteLine(ed.State);
                    //    }
                    //    else if (cor.Descriptor is LinkDescriptor)
                    //    {
                    //        LinkDescriptor ld = (LinkDescriptor)cor.Descriptor;
                    //        Trace.WriteLine(ld.State);
                    //    }
                    //} 
                    #endregion
                    if (i + 1 >= retryCount)
                    {
                        throw;
                    }
                }
                catch (Exception ex)
                {
                    if (i + 1 >= retryCount)
                    {
                        throw;
                    }
                }
            }
            throw new ApplicationException("Loop must be wrong in retry SaveChanges");
        }
 
        public static DataServiceResponse SaveChangesWithRetry(this DataServiceContext ctx, SaveChangesOptions options, int retryCount)
        {
            for (int i = 0; i < retryCount; i++)
            {
                try
                {
                    return ctx.SaveChanges(options);
                }
                catch (System.Data.Services.Client.DataServiceRequestException ex)
                {
                    #region Deeper analysis, disabled
                    //foreach (ChangeOperationResponse cor in ex.Response)
                    //{
                    //    if (cor.Descriptor is EntityDescriptor)
                    //    {
                    //        EntityDescriptor ed = (EntityDescriptor)cor.Descriptor;
                    //        Trace.WriteLine(ed.State);
                    //    }
                    //    else if (cor.Descriptor is LinkDescriptor)
                    //    {
                    //        LinkDescriptor ld = (LinkDescriptor)cor.Descriptor;
                    //        Trace.WriteLine(ld.State);
                    //    }
                    //} 
                    #endregion
                    if (i + 1 >= retryCount)
                    {
                        throw;
                    }
                }
                catch (Exception ex)
                {
                    if (i + 1 >= retryCount)
                    {
                        throw;
                    }
                }
            }
            throw new ApplicationException("Loop must be wrong in retry SaveChanges Batch");
        }
    }
}

Wir haben das ganze nun in einer Library verpackt. Wenn es dann daran geht, den Code in einem anderen Projekt zu nutzen, das den oben gezeigten Code als Assemly einbindet, dann muss man, wie in der MSDN beschrieben, den Namespace als Using angeben. In unserem Fall also:

using Core.ExtensionMethods;

How to: Implement and Call a Custom Extension Method (C# Programming Guide)

This topic shows how to implement your own extension methods for any type in the .NET Framework Class Library, or any other .NET type that you want to extend. Client code can use your extension methods by adding a reference to the DLL that contains them, and adding a using directive that specifies the namespace in which the extension methods are defined.

Der aufrufende Code sieht also in etwas so aus:

var ctxMaster = Core.Data.Context.GetMaster(); //Wrapper around setting url and credentials
var query = from ts in ctxMaster.TStuff
                       where ts.StuffId == someGuidkey
                        && ts.IsDeleted == false
                       select ts;
 
var firstStuff = query.First();
firstStuff.IsDeleted = true;
ctxMaster.UpdateObject(firstStuff);
 
//ctxMaster.SaveChanges(); //Old: save and pray
ctxMaster.SaveChangesWithRetry(3); //New: with retry!

Der Post bringt hoffentlich zwei Dinge: 1.) Wie einfach es ist ein Retry auf einem “MS-REST” Context funktioniert und 2.) Was man tun muss, um die Extension Method auch in einem anderen Projekt oder Namespace nutzen zu können.

Ciao Marco