Blogs

The One With

August 2010 - Posts

  • Scheduler Control – iCalendar VEvent Properties (coming in v2010 vol 2)

         

    In v2010 vol 2, we are adding the ability to export custom VEVENT properties, when exporting to iCalendar.

    Let’s see how it works.

    We have a simple Appointments table:

    Scheduler Appointments Table

    with the standard fields that the Scheduler can work with: Start and End times, Subject, Location etc… We’ll map the ReminderData field to ReminderInfo to store the reminder data.

    The RecipientsData field we intend to use for a list of email addresses. A simple scenario is a list of attendees to be notified of a meeting. We’ll map it using the CustomFieldMappings like so:

    this.schedulerStorage1.Appointments.CustomFieldMappings.Add(
        new AppointmentCustomFieldMapping(
            "RecipientsData", 
            "RecipientsData", 
            FieldValueType.String));      

     

     

    Custom fields, once mapped, can be accessed directly from the Appointment object. The following InitAppointmentDisplayText demonstrates this.

    private const string RecipientsDataColumn = "RecipientsData";
    
    private void schedulerControl1_InitAppointmentDisplayText(
        object sender, AppointmentDisplayTextEventArgs e) {
        
        string attendees = Convert.ToString(e.Appointment.CustomFields[RecipientsDataColumn]);
        
        e.Description = string.Format("{0}
    Required Attendees: {1}", 
                            e.Description, 
                            attendees);
    }

     Scheduler - Init Display Text

    Exporting to iCalendar (.ics)

    Exporting our Appointments to .ics format is done using the Storage.ExportToICalendar method.

    void ExportAppointments(Stream stream) {
        schedulerControl1.Storage.ExportToICalendar(stream);
    }

    the exported .ics file:

    BEGIN:VEVENT

    DTSTAMP:20100824T170910Z

    DTSTART:20100920T090000Z

    DTEND:20100920T103000Z

    DESCRIPTION:User stories estimating and determining their priority

    LOCATION:Conference Hall

    SUMMARY:Release planning

    UID:6f990cad-182f-4b33-be1f-de10d8d29649

    BEGIN:VALARM

    TRIGGER:-PT1H

    ACTION:DISPLAY

    END:VALARM

    X-MICROSOFT-CDO-BUSYSTATUS:FREE

    X-DEVEXPRESS-STATUS:FREE

    X-DEVEXPRESS-LABEL:2

    X-DEVEXPRESS-CUSTOMFIELD-RECIPIENTSDATA:projectmanager@company.com\;teamlea

    der@company.com

    END:VEVENT

    Notice that in addition to all the standard VEVENT properties, there are a couple X-DEVEXPRESS prefixed ones. The ExportToICalendar does not lose any information when exporting and preserves them using a custom prefixed property, per iCalendar specifications. This is also done for custom appointment fields:

    X-DEVEXPRESS-CUSTOMFIELD-RECIPIENTSDATA:projectmanager@company.com\;teamleader@company.com

    Controlling the export

    Our RecipientsData was intended to track the list of Attendees, which has a corresponding VEVENT property ATTENDEE. The ExportToICalendar did it’s job of exporting and preserving the appointment information but it does not know that RecipientsData is to be translated to ATTENDEE.

    To fix, this we can now use a new iCalendarExporter where we can subscribe to the exporting process and add the needed VEVENT attributes.

    using DevExpress.XtraScheduler.iCalendar.Components;
    
    void ExportAppointments(Stream stream) {
        iCalendarExporter exporter = new iCalendarExporter(schedulerStorage1);
        exporter.AppointmentExporting += exporter_AppointmentExporting;
        exporter.Export(stream);
    }      
    
    void exporter_AppointmentExporting(object sender, AppointmentExportingEventArgs e) {
        string s = Convert.ToString(e.Appointment.CustomFields[RecipientsDataColumn]);
        
        string[] attendeees = s.Split(';');
    
        iCalendarAppointmentExportingEventArgs args = e as iCalendarAppointmentExportingEventArgs;
    
        int count = addresses.Length;
        
        for (int i = 0; i < count; i++) {
              AddEventAttendee(args.VEvent, addresses[i]);
        }
    }
    
    void AddEventAttendee(VEvent ev, string address) {
        TextProperty p = new TextProperty("ATTENDEE", string.Format("mailto:{0}", address));
        p.AddParameter("CN", address);
        p.AddParameter("RSVP", "TRUE");
        ev.CustomProperties.Add(p);
    }
    

    the exported .ics file will now have the desired ATTENDEE:

    BEGIN:VEVENT

    //<skipped>

    X-DEVEXPRESS-CUSTOMFIELD-RECIPIENTSDATA:projectmanager@company.com\;teamlea

    der@company.com

    ATTENDEE;CN=projectmanager@company.com;RSVP=TRUE:mailto:projectmanager@comp

    any.com

    ATTENDEE;CN=teamleader@company.com;RSVP=TRUE:mailto:teamleader@company.com

    END:VEVENT

    We can open the exported file in Microsoft Outlook and see that the attendee list is picked up automatically.

    iCalendar in Microsoft Outlook

    You can download the example code here.

    Cheers

    Azret

  • Silverlight Charts (coming in v2010 vol 2)

         

    This is probably the most awaited addition to our Silverlight Product Line. Yes, I am talking about the Charting Suite! Here are some early previews of the Bar and Point Charts, please let us know what you think.

    Fig 1: Side By Side Bars

    Silverlight Charts - Size By Side 2D Bars

    Fig 2: Full-Stacked Bars

    Silverlight Charts - 2D Full Stacked Bar

    Fig 3: Side By Side Stacked Bars

    Silverlight Charts - 2D Side By Side Stacked Bar

    Fig 4: Side By Side Full-Stacked Bars

    Silverlight Charts - 2D Side By Side Full Stacked Bar

    Fig 5: Stacked Bars

    Silverlight Charts - 2D Stacked Bar

    Fig 6: Custom Draw Options

    Silverlight Charts - Custom Draw

    Fig 7: Points

    Silverlight Charts - 2D Points

    Fig 8: Resolving Point Overlapping

    Silverlight Charts - Resolve Overlapping

    Fig 9: Secondary Axes

    Silverlight Charts - Secondary Axes

     

    Cheers

    Azreet

  • WPF Rich Text Editor Table Support (coming in v2010 vol 2)

         

    Did someone ask if the new WPF Rich Text Editor will support tables? :) Here are some sneak previews:

    Fig 1: WPF Rich Edit Control – Simple Invoice Table

    WPF Rich Edit - Table Support

    Fig 2: WPF Rich Edit Control – Cell Borders

    WPF Rich Edit - Borders

    Cheers

    Azret

  • End-User Report Designer – Viewing Reports (Part 2)

         

    XtraReports™ SuiteIn part 1 we created an end-user report designer that can publish reports to a database using OData protocol. Now, let’s create a Silverlight application that can view the reports that we have published.

    Report Service

    First we’ll add a printing service to our ASP.NET host application.

    Silverlight-enabled XtraReports Service

    By default Silverlight-enabled XtraReports Service looks up the reports by type name, we want to override this behavior and load the report from the database.

    protected override XtraReport CreateReport(
        string reportTypeName, 
        Dictionary<string, object> parameters) {
        
        try {
            using (List listService = new List()) {
    
                using (Session session 
                            = new Session(listService.GetDataLayer())) {
    
                    Report report 
                        = session.GetObjectByKey<Report>(new Guid(reportTypeName));
    
                    if (report == null) {
                        return Create404Report();
                    }
    
                    File file = report.File;
    
                    if (file == null) {
                        return Create404Report();
                    }
    
                    XtraReport retVal = new XtraReport();
    
                    using (MemoryStream stream = new MemoryStream(file.Binary)) {
                        retVal.LoadLayout(stream);
                        return retVal;
                    }
    
                }
            }
        } catch (Exception e) {
            return Create500Report(e);
        }
        
    }

    Note: The assumption of the new CreateReport is that the reportTypeName is a report ID, a GUID.

    Silveright Viewer

    Inside the Silverlight, we’ll simply drop the DocumentPreview control on our page and load the report on page load. I have described this process here.

    <dxp:DocumentPreview Name="documentPreview1"/>
    

    void MainPage_Loaded(object sender, RoutedEventArgs e) {
        if (!HtmlPage.Document.QueryString.ContainsKey("id")) {
            return;
        }
        
        string reportId = HtmlPage.Document.QueryString["id"];
    
        if (string.IsNullOrWhiteSpace(reportId)) {
            return;
        }
    
        ReportPreviewModel model
                       = new ReportPreviewModel(
                            new Uri(App.Current.Host.Source.AbsoluteUri + "../../ReportService.svc").ToString());
    
        model.ReportTypeName = reportId;
    
        documentPreview1.Model = model;
    
        model.CreateDocument();
    }

    That’s it, we can now access our reports by a URL for example

    http://localhost.:56844/Report.aspx?id=7750954ac4b749a2a41c70f419e741c2

    Silverlight Report Viewer

    Final Notes

    • You can download the complete sample here.
    • The included Web.config will be useful to you if you need to copy paste some settings.
    • You will also need DevExpress.Xpo.Services.10.1.dll this file is not included in the provided sample but you can download it from http://xpo.codeplex.com.
    • Get more information and more samples of OData Provider for XPO.

    Cheers

    Azret

  • End-User Report Designer - Publishing Reports (Part 1)

         

    XtraReports™ SuiteOne of the challenges for any enterprise level application is reporting. How do we create reports? How to do we let our users create them without our involvement? How do we deploy them? etc…

    These and all other reporting related questions are addressed by suites like XtraReports™. The deployment question is an interesting one. How should we deliver the actual reports? Should we hard-code them into the app? Should we have a special folder for the report files or should we save them into a database? This last option is what I want to show you how to do, using the end-user report designer.

    The first thing we will need to do is to create an end-user report designer. I learned how to do this by watching this video.

    End-User Report Designer - Publish Report

     

    I have added an extra button “Publish” and when clicked, I want to show a wizard of some sort and submit this report to the database using an OData service end point.

    Report Publishing Wizard

    Preparing the Publishing Web Service

    A single report will be saved in two tables: the “Report” table and “File” table.

    [ResourceSetName("Reports")]
    public class Report : XPCustomObject {
        Guid _ID;
        [Key(AutoGenerate=true)]
        public Guid ID {
            get { return _ID; }
            set {
                SetPropertyValue<Guid>("ID", ref _ID, value);
            }
        }
    
        string _Title;
        [Size(256)]
        public string Title {
            get { return _Title; }
            set {
                SetPropertyValue<string>("Title", ref _Title, value);
            }
        }
    
        File _File;
        [Association("File-Reports")]
        public File File {
            get { return _File; }
            set {
                SetPropertyValue<File>("File", ref _File, value);
            }
        }
    }
    
    

    [ResourceSetName("Files")]
    public class File : XPCustomObject {
        Guid _ID;
        [Key(AutoGenerate = true)]
        public Guid ID {
            get { return _ID; }
            set {
                SetPropertyValue<Guid>("ID", ref _ID, value);
            }
        }
    
        [Association("File-Reports")]
        public XPCollection<Report> Reports {
            get {
                return GetCollection<Report>("Reports");
            }
        }
    
        byte[] _Binary;
        public byte[] Binary {
            get { return _Binary; }
            set {
                SetPropertyValue<byte[]>("Binary", ref _Binary, value);
            }
        }
    }
    

    and the OData service:

    public class List : XpoDataService {
        public static void InitializeService(DataServiceConfiguration config) {
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
    
    

    Publishing the Report

    After the OData service is ready, we can submit the report using the Data Service Client Library:

    DataServiceContext list = new DataServiceContext(new Uri("http://localhost.:56844/List.svc"));
    
    Reports.Designer.Service.File file = new Reports.Designer.Service.File();
    file.Binary = GetReportBinary();
    
    Report report = new Report();
    report.Title = title;
    
    list.AddObject("Files", file);
    list.AddObject("Reports", report);                               
    list.SetLink(report, "File", file);
    
    list.SaveChanges();
     

    private byte[] GetReportBinary() {
        byte[] binary;
        using (MemoryStream stream = new MemoryStream()) {
            xrDesignPanel1.Report.SaveLayout(stream);
            binary = stream.ToArray();
        }
        return binary;
    }

    And that’s it! We can now access the reports and the actual report files from anywhere.

    http://localhost:56844/List.svc/Reports

    <?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
    <feed 
      xml:base="http://localhost:56844/List.svc/" 
      xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
      xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
      xmlns="http://www.w3.org/2005/Atom">
      <title type="text">Reports</title>
      <id>http://localhost:56844/List.svc/Reports</id>
      <updated>2010-08-24T23:00:35Z</updated>
      <link rel="self" title="Reports" href="Reports" />
      <entry>
        <id>http://localhost:56844/List.svc/Reports(guid'13378cf1-553e-4fd7-9b5e-bd63eb6106cb')</id>
        <title type="text"></title>
        <updated>2010-08-24T23:00:35Z</updated>
        <author>
          <name />
        </author>
        <link rel="edit" title="Report" href="Reports(guid'13378cf1-553e-4fd7-9b5e-bd63eb6106cb')" />
        <link 
          rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/File" 
          type="application/atom+xml;type=entry" 
          title="File" 
          href="Reports(guid'13378cf1-553e-4fd7-9b5e-bd63eb6106cb')/File"/>
        <category 
          term="Reports.Web.Report" 
          scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
        <content type="application/xml">
          <m:properties>
            <d:ID m:type="Edm.Guid">13378cf1-553e-4fd7-9b5e-bd63eb6106cb</d:ID>
            <d:Title>Products</d:Title>
          </m:properties>
        </content>
      </entry>
    </feed>
    

    http://localhost:56844/List.svc/Reports(guid'13378cf1-553e-4fd7-9b5e-bd63eb6106cb')/File

    <?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
    <entry 
      xml:base="http://localhost:56844/List.svc/" 
      xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
      xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
      xmlns="http://www.w3.org/2005/Atom">
      <id>http://localhost:56844/List.svc/Files(guid'1b5c8d22-99b3-4c93-b4dd-e2573bf11605')</id>
      <title type="text"></title>
      <updated>2010-08-24T23:02:10Z</updated>
      <author>
        <name />
      </author>
      <link 
        rel="edit" 
        title="File" 
        href="Files(guid'1b5c8d22-99b3-4c93-b4dd-e2573bf11605')"/>
      <link 
        rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Reports" 
        type="application/atom+xml;type=feed" 
        title="Reports" 
        href="Files(guid'1b5c8d22-99b3-4c93-b4dd-e2573bf11605')/Reports"/>
      <category 
        term="Reports.Web.File" 
        scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
      <content type="application/xml">
        <m:properties>
          <d:ID m:type="Edm.Guid">1b5c8d22-99b3-4c93-b4dd-e2573bf11605</d:ID>
          <d:Binary m:type="Edm.Binary">Ly8......NCg==</d:Binary>
        </m:properties>
      </content>
    </entry>
    

    We are still missing the Report Viewer piece, so we’ll address this in Part 2.

    Cheers

    Azret

  • Rich Text Edit and Regular Expressions

         

    Today someone showed this cool little feature in the Find/Replace dialog of the Rich Edit Control

     WinForms Rich Text Edit - Regular Expressions

    You can search for any string or you can build a complex regular expression to match a specific pattern. This Find/Replace feature is also available via Document API

    DocumentRange[] FindAll(Regex regex);
    DocumentRange[] FindAll(Regex regex, DocumentRange range);
    int ReplaceAll(Regex regex, string replaceWith);
    int ReplaceAll(Regex regex, string replaceWith, DocumentRange range);

    Pretty cool ah?

    Azret

  • WPF and Silverlight Reporting Service Enhancements

         

    For v2010 vol 2 release, the Reporting Service underwent a big revision to address the way it manages report objects on the server. Let me explain the problem.

    Client & Server Call Flow

    Fig 1:  Client & Service Call Flow

    Fig 1, The above shows the basic call flow between the Report Viewer (The Client) and the Server (The Printing Service). We start off by requesting a particular report to be build. Than, until we get the build completion status we start requesting the pages. The problem is, where should the printing service store the report objects during the report fetching process? In the original implementation, we stored them in the ASP.NET Session. This approach, as we discovered, has serious limitations. For one, it requires the printing service to be ASP.NET Compatible so that the Session management facilities are available. This by itself is not that big a deal, but as the number of requests increase, the amount of memory required for report generation also increases. Eventually, you start running out of memory causing the service to be restarted.

    To address this, we have decided on a different approach. Instead of saving the report documents in ASP.NET Session, we now store them in a database. This removes the ASP.NET Compatible requirement and addresses the memory issues.

    You can configure this database for your service in the Web.config file. The XtraReports Service Wizard

    XtraReports Service Wizard

    will help you with the configuration settings by generating a template for your service. You can also refer to this example if you need to copy paste these from somewhere.

    One of these settings is the connecting string that the service is using to locate this “temp database”:

     <connectionStrings>
        <add name="xtraReports" 
          connectionString="XpoProvider=MSAccess;Provider=Microsoft.Jet.OLEDB.4.0;
                Mode=Share Deny None;Data Source=|DataDirectory|/MyReportService.mdb"/>
      </connectionStrings>

    we would of course recommend you to use a production strength database like SQL Server. So change your connection string accordingly. It is also ideal to have this database running on the same machine for performance reasons.

    Although this was planned for 10.2, we were able to deliver it in the recent minor release 10.1.6. So you should already have this bits.

    Please let us know if you have any questions about it.

    Cheers

    Azret

  • WPF Rich Text Editor Control – (coming in v2010 vol 2)

         

    Yes!!! Our Rich Text Editor is going to be available in this upcoming v2010 vol 2 release. In the spirit of our release previews, there is an early preview:

    WPF Rich Text Editor & WPF Ribbon Integration

    WPF Rich Text Editor & WPF Ribbon Integration

    Cheers

    Azret

  • OData Provider for XPO – Using Server Mode to handle Huge Datasets

         

    OData + !summary ExtensionsThe most exciting part of OData is its simplicity and at the same time its flexibility. The idea of accessing data over HTTP using a well defined RESTful API, and the fact that by doing so we completely remove direct access to the database from our clients, is indeed very powerful!

    So what is Server Mode?

    Simply put, Server Mode is a concept of our Grid Controls that, if enabled, instead of fetching the entire data set, the grid only fetches the number of records relevant to the current view. Similarly, sorting, filtering, summary operations and group summaries are delegated to the underlying IQueryable<T>. This is why !summary was very important.

    Let’s have a closer look at what the grid actually does. I have a resource set “ServerModeItems” on the server that exposes a SQL Server table with 100,000 records. I then bind it to a WinForms Grid Control is Server Mode like so:

    this.gridControl1.DataSource = ServerModeSource2.Create<ServerModeItem>(
        new Uri("http://localhost:59906/ServerMode.svc"),
        "ServerModeItems",
        "OID");

    Note: Never mind the name ServerModeSource2. It’s a variation of LinqServerModeDataSource designed specifically for handling IQueryables that implement IServerMode2. The actual name will change once this goes to a full CTP. Suggestions are welcome :). Although binding directly to LinqServerModeDataSource and AtomPubQuery<> will work just fine, the ServerModeSource2 is able to utilize a custom !contains extension which is explained below.

    Under the Hood

    Initial Load

    The initial grid data load is incredibly lightweight: Query.OrderBy(it => it.OID).Take(128).Select() which than translates into a ?$top=128&$orderby=OID request.

    WinForms Grid Control in Server Mode with an OData data source.

    Sorting by the Sent column for example will execute the following: ?$top=128&$orderby=Sent desc,OID desc 

    Scrolling

    As I start scrolling down, the Grid (the Data Controller actually) will start requesting more data from our IQueryable. It will do this in 2 steps.

    Step 1: Fetch the Keys for a specific range.

    • LINQ: Query.OrderBy(e => e.OID).Skip(100).Take(768).Select(e => e.OID)
    • URL: ?$skip=100&$top=768&$orderby=OID

    The range for key selection is not arbitrary. It is calculated and optimized on the fly as you scroll based on how much time it takes to fetch N number records vs. k * N number of records. If getting 2 * N keys takes the same time as getting N keys then we’ll get 2 * N keys.

    Step 2: Fetching records for the keys.

    Once the keys are selected, the Grid will request the actual data rows for the visible range.

    • LINQ: Query.Where(e = > e.OID == @id1 || e.OID == @id2 || e.OID == @id3 etc…)
    • URL: ?$filter=OID eq @id1 or OID eq @id2 or OID eq @id3…

    Grouping

    When grouping is performed, we first run the GroupBy query:

    • LINQ: GroupBy(e => e.Sent).OrderBy(e => e.Key).Select([Key+Summaries])
    • URL: ?!summary=Sent asc,[Summaries]

    and than when we expand a group, the process of initial load and scrolling repeats itself but this time only for the current group.

    • LINQ: Query.Where(e => (e.Sent == 4/6/2010 12:00:00 AM)).OrderBy(e => e.Sent).ThenBy(e => e.OID).Take(128)
    • URL: ?$filter=(Sent eq datetime'2010-04-06T00:00:00')&$top=128&$orderby=Sent,OID

    WinForms Grid Control in Server Mode with an OData data source.

    Optimizations

    Of course, a big trade off of having to fetch data over HTTP is that it is slower than if you had a DB right next to you. Utilizing the Grid in Server Mode solves this, but there is still room for improvements. One improvement that we are thinking about for 10.2 release or right after, is a smart Data Controller that will perform key fetches in the background thus giving us a very smooth scrolling experience.

    Other optimization we can do at the query level is to delay load the record fetches (see Step 2). This means that when data is needed for a visible range, we return fakes and issue a fetch request asynchronously. Than when the response comes back, we notify the Grid to repaint itself.

    Dealing with URL Length Limitations

    Look back at Step 2 above:

    URL: ?$filter=OID eq @id1 or OID eq @id2 or OID eq @id3…

    This URI is shortened for readability. The actual query string can get really big and ugly, and sooner or later you will run into URL Length Limitations, which you will need to configure for both .NET HTTP Runtime and IIS. Making the acceptable query string sizes bigger will only solve one problem. You will soon run into recursion limits set by the Data Service Library. Because filter expressions are recursive and when they are generated straight out of LINQ and are not simplified they look like this:

    Query.Where(it => (((((((((((((((((((((((((((((((((((((((((((((((((((((((it.OID == 6792) OrElse (it.OID == 6818)) OrElse (it.OID == 6882)) OrElse (it.OID == 6959)) OrElse (it.OID == 7008)) OrElse (it.OID == 7038)) OrElse (it.OID == 7045)) OrElse (it.OID == 7064)) OrElse (it.OID == 7089)) OrElse (it.OID == 7124)) OrElse (it.OID == 7167)) OrElse (it.OID == 7176)) OrElse (it.OID == 7268)) OrElse (it.OID == 7270)) OrElse (it.OID == 7306)) OrElse (it.OID == 7349)) OrElse (it.OID == 7444)) OrElse (it.OID == 7560)) OrElse (it.OID == 7573)) OrElse (it.OID == 7607)) OrElse (it.OID == 7616)) OrElse (it.OID == 7649)) OrElse (it.OID == 7726)) OrElse (it.OID == 7755)) OrElse (it.OID == 7786)) OrElse (it.OID == 7862)) OrElse (it.OID == 8018)) OrElse (it.OID == 8069)) OrElse (it.OID == 8074)) OrElse (it.OID == 8104)) OrElse (it.OID == 8141)) OrElse (it.OID == 8215)) OrElse (it.OID == 8329)) OrElse (it.OID == 8371)) OrElse (it.OID == 8439)) OrElse (it.OID == 8590)) OrElse (it.OID == 8643)) OrElse (it.OID == 8660)) OrElse (it.OID == 8688)) OrElse (it.OID == 8792)) OrElse (it.OID == 8939)) OrElse (it.OID == 9033)) OrElse (it.OID == 9042)) OrElse (it.OID == 9067)) OrElse (it.OID == 9123)) OrElse (it.OID == 9143)) OrElse (it.OID == 9193)) OrElse (it.OID == 9287)) OrElse (it.OID == 9329)) OrElse (it.OID == 9341)) OrElse (it.OID == 9387)) OrElse (it.OID == 9393)) OrElse (it.OID == 9502)) OrElse (it.OID == 9507)) OrElse (it.OID == 9542)))

    AtomPubQuery<> solves this by:

    • 1: Optimizing the the resulting URL to avoid unneeded parentheses. 
    • 2: Putting the $filter criteria into the HTTP header X-Filter-Criteria
    • 3: Using a custom !contains extension designed specifically for fetching data by multiple keys. !contains=OID in (1,3,4,5,6 etc…)

    Hope you got excited about all of this :). You can download the Pre CTP from http://xpo.codeplex.com/.

    Cheers

    Azret

  • OData Provider for XPO – GroupBy(), Count(), Max() and More…

         

    OData + !summary ExtensionsLast time I have introduced you to a custom OData extension !summary. But as exciting as it was, the standard .NET Data Client Library does not support it. Why should it right? It does not know about it. To solve this, the eXpress Persistent Objects (XPO) Toolkit now includes a client side library that can understand and work with all the extensions that we make.

    The AtomPubQuery<> (similar to the .NET DataServiceQuery<>) will also support all the basic OData operations like $filter, $orderby etc… Let’s see how to use it.

    Let’s assume you have an OData service exposing data from the Northwind Database and you want to get top 5 sales broken down by country.

    AtomPubQuery<Order> Orders {
        get {
            return new AtomPubQuery<Order>(
                new Uri("http://localhost:54691/Northwnd.svc", UriKind.Absolute), 
                "Orders", 
                "OrderID", 
                this);
        }
    }
    var query = from o in Orders
                group o by o.ShipCountry  into g
                orderby g.Count() descending
                select new Sale() { Country = g.Key, Total = g.Count() };
    this.chartControl1.DataSource = query.Take(5).ToList();
     
    OData feed bound to a Chart Control 

    This will make the following request:

    http://localhost:54691/Northwnd.svc/Orders?!summary=ShipCountry,Count() desc&$top=5

    and execute the following query on the server:

    select top 5 ShipCountry, count(*) from Orders group by ShipCountry order by count(*) desc

    Pretty cool ah? :)

    Cheers,

    Azret

  • OData Provider for XPO - Summary Extensions (!summary)

         

    OData + !summary Extensions

    The Open Data Protocol allows us to address resources a number of different ways. We can filter using the $filter operator, sort with $orderby, page via the $top & $skip or with the help of server supplied continuation tokens.

    These operators, for the most part, are enough when dealing with straightforward feeds. But when working with enterprise level data, we need to be able to access more than just data segments. We need aggregates! The OData Provider for XPO implements support for them using a custom bang extension !summary.

    !summary=Count()

    Assuming we have a resource set “Items” with the following structure:

    <EntityType Name="Item">
      <Key>
        <PropertyRef Name="ID" />
      </Key>
      <Property Name="ID" Type="Edm.Int32" Nullable="false" />
      <Property Name="Content" Type="Edm.String" Nullable="true" />
      <Property Name="Amount" Type="Edm.Decimal" Nullable="false" />
      <Property Name="Created" Type="Edm.DateTime" Nullable="false" />
      <Property Name="Author" Type="Edm.String" Nullable="true" />
      <Property Name="Type" Type="Edm.Int32" Nullable="false" />
    </EntityType>
    

    Count all items where Author = ‘Steve’:

    http://localhost:54691/Service.svc/Items?$filter=Author eq 'Steve'&!summary=Count()

    <feed 
      xml:base="http://localhost:54691/Service.svc/" 
      xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
      xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
      xmlns="http://www.w3.org/2005/Atom">
      <entry>
        <content type="application/xml">
           <m:properties>
            <d:Key>*</d:Key>
            <d:Summary>Count()=2386</d:Summary>
          </m:properties>
        </content>
      </entry>
    </feed>
    Executed Server Side SQL:
    SELECT COUNT(*) FROM "dbo"."Items" N0 
    WHERE (N0."Author" = @p0)

    !summary=Max(),Min()

    http://localhost:54691/Service.svc/Items?!summary=Max(Created),Min(Created)
    <feed 
      xml:base="http://localhost:54691/Service.svc/" 
      xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
      xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
      xmlns="http://www.w3.org/2005/Atom">
      <entry>
        <content type="application/xml">
          <m:properties>
            <d:Key>*</d:Key>
            <d:Summary>Max(Created)=2010-09-17T00:00:00,Min(Created)=2010-07-01T00:00:00</d:Summary>
          </m:properties>
        </content>
      </entry>
    </feed>

    !summary=Sum(),Avg()

    Get the amount Avg and Total for items with type 3:

    http://localhost:54691/Service.svc/Items?!summary=Sum(Amount),Avg(Amount)&$filter=Type eq 3

    <feed 
      xml:base="http://localhost:54691/Service.svc/" 
      xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
      xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
      xmlns="http://www.w3.org/2005/Atom">
      <entry>
        <content type="application/xml">
          <m:properties>
            <d:Key>*</d:Key>
            <d:Summary>Sum(Amount)=4975139.6643,Avg(Amount)=497.5139</d:Summary>
          </m:properties>
        </content>
      </entry>
    </feed>
    Executed Server Side SQL:
    SELECT SUM(N0."Amount"), AVG(N0."Amount") 
    FROM "dbo"."Items" N0 
    WHERE (N0."Type" = @p0)

    !summary=Author,Sum(Amount)

    !summary extension supports single-level grouping as well! If a field name is specified, grouping is assumed on that field.

    In this example we get the total amount grouped by Author.

    http://localhost:54691/Service.svc/Items?!summary=Author,Sum(Amount) 

    <feed
     
    xml:base="http://localhost:54691/Service.svc/"
      xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
      xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
      xmlns="http://www.w3.org/2005/Atom">
      <
    entry>
        <
    contenttype="application/xml">
          <
    m:properties>
            <
    d:Key>Bill</d:Key>
            <
    d:Summary>Sum(Amount)=1249351.4500</d:Summary>
          </
    m:properties>
        </
    content>
      </
    entry>
      <
    entry>
        <
    contenttype="application/xml">
          <
    m:properties>
            <
    d:Key>Steve</d:Key>
            <
    d:Summary>Sum(Amount)=1187387.2850</d:Summary>
          </
    m:properties>
        </
    content>
      </
    entry>
      <
    entry>
        <
    contenttype="application/xml">
          <
    m:properties>
            <
    d:Key>John</d:Key>
            <
    d:Summary>Sum(Amount)=1278919.2063</d:Summary>
          </
    m:properties>
        </
    content>
      </
    entry>
      <
    entry>
        <
    contenttype="application/xml">
          <
    m:properties>
            <
    d:Key>Paul</d:Key>
            <
    d:Summary>Sum(Amount)=1259481.7230</d:Summary>
          </
    m:properties>
        </
    content>
      </
    entry>
    </
    feed>

    Executed Server Side SQL:

    SELECT N0."Author",SUM(N0."Amount") 
    FROM "dbo"."Items" N0 
    GROUP BY N0."Author"

    Cheers

    Azret

  • OData Provider for XPO – Binding to Scheduler

         

    Scheduler <-> OData <-> XPO

    So far we have been focusing on creating and consuming one-way OData feeds (read-only feeds). This time, let us look at how to CREATE, UPDATE and DELETE items. CUD operations are handled by the IDataServiceUpdateProvider and this interface is fully supported by the eXpress Persistent Objects (XPO) Toolkit.

    Server Side

    Apart from setting the entity access rule to EntitySetRights.All, there is nothing we really need to do on the server side.

    Client Side

    CREATE

    void CreateObject(object entity) {
        DataServiceContext service 
            = new DataServiceContext(new Uri("http://localhost:7676/Service.svc"));
    
        service.AddObject("<EntitySetName>", entity);
    
        service.SaveChanges();
    }

    UPDATE

    void UpdateObject(object entity) {
        DataServiceContext service 
            = new DataServiceContext(new Uri("http://localhost:7676/Service.svc"));
    
        service.AttachTo("<EntitySetName>", entity);
        service.UpdateObject(entity);
    
        service.SaveChanges();
    }

    DELETE

    void DeleteObject(object entity) {
        DataServiceContext service 
            = new DataServiceContext(new Uri("http://localhost:7676/Service.svc"));
    
        service.AttachTo("<EntitySetName>", entity);
        service.DeleteObject(entity);
    
        service.SaveChanges();
    }

    Sample Application

    To see all CRUD operations in action download the source code for the WinForms Calendar Sample

    Resources


    Cheers

    Azret

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.