Blogs

Gary's Blog

September 2010 - Posts

  • XPO - Stored Procedure Support coming in V2010 Vol 2 (Part 2)

         

    You’ll recall in my previous post on this topic I told you that there would be two ways of working with stored procedures in the forthcoming 2010.2 release, those were:

    1. Direct calling of existing stored procedures and the ability to handle returned data;
    2. Mapping of persistent classes on views in the database with help of INSTEAD-OF triggers and stored procedures.

    I then went on to demonstrate how to use the first version. Well guess what? I’m back, and this time we’re going to take a look at that second variant, which works by mapping persistent classes to views in the database, which in turn query data from related tables in the database. Clearly, it’s not possible to add, edit or delete records from a database view and so to solve this problem, we’ll use INSTEAD-OF triggers to redirect the required operations to the stored procedures. To do this we’ll have to perform the following set of tasks for each database table:

    1. Create a view.
    2. Map the persistent class to the corresponding View;
    3. Create INSTEAD-OF triggers for INSERT, UPDATE and DELETE functionality.
    4. Create stored procedure for INSERT, UPDATE and DELETE functionality.

    In the first blog post I demonstrated how to use the Persistent Classes Wizard to get started with stored procedures. Here we will use the same technique, with one difference. In the Generating Persistent Classes for an existing Database dialog, we will check the Generate views and stored procedures for tables access button.

    Generate Persistent Classes for an Existing Database Wizard

    The remaining steps are the same as in the previous post. As a result, the wizard will generate the code shown in the first post as well as the DDL code to generate views, triggers and stored procedures in the database.

    For example, this code generates an EmployeeSplit_xpoView view:

    CREATE VIEW [EmployeeSplit_xpoView] AS
        SELECT
            [ID],
            [Extension],
            [PhotoPath]
        FROM [EmployeeSplit]
    GO
    CREATE PROCEDURE [sp_EmployeeSplit_xpoView_insert]
        @ID int,
        @Extension nvarchar(4),
        @PhotoPath nvarchar(255)
    AS
    BEGIN
        BEGIN TRY
            INSERT INTO [EmployeeSplit](
                [ID],
                [Extension],
                [PhotoPath]
            )
            VALUES(
                @ID,
                @Extension,
                @PhotoPath
            );
        END TRY
        BEGIN CATCH
            DECLARE @ErrorMessage NVARCHAR(4000);
            DECLARE @ErrorSeverity INT;
            DECLARE @ErrorState INT;
            SELECT @ErrorMessage = ERROR_MESSAGE(),
                @ErrorSeverity = ERROR_SEVERITY(),
                @ErrorState = ERROR_STATE();
            RAISERROR(
                @ErrorMessage,
                @ErrorSeverity,
                @ErrorState
            );
        END CATCH
    END
    GO
    CREATE PROCEDURE [sp_EmployeeSplit_xpoView_update]
        @ID int,
        @old_Extension nvarchar(4),
        @Extension nvarchar(4),
        @old_PhotoPath nvarchar(255),
        @PhotoPath nvarchar(255)
    AS
        UPDATE [EmployeeSplit] SET
            [Extension]=@Extension,
            [PhotoPath]=@PhotoPath
        WHERE
            [ID] = @ID
    GO
    CREATE PROCEDURE [sp_EmployeeSplit_xpoView_delete]
        @ID int,
        @old_Extension nvarchar(4),
        @old_PhotoPath nvarchar(255)
    AS
        DELETE FROM [EmployeeSplit] WHERE
            [ID] = @ID
    GO

    And this code generates the respective triggers.

    CREATE TRIGGER [t_EmployeeSplit_xpoView_insert]
    ON [EmployeeSplit_xpoView]
    INSTEAD OF INSERT AS
    BEGIN
        DECLARE @cur CURSOR
        SET @cur = CURSOR FOR
            SELECT
                [ID],
                [Extension],
                [PhotoPath]
            FROM inserted
        OPEN @cur
        DECLARE @ID int
        DECLARE @Extension nvarchar(4)
        DECLARE @PhotoPath nvarchar(255)
        FETCH NEXT FROM @cur INTO
            @ID,
            @Extension,
            @PhotoPath
        WHILE(@@fetch_status <> -1)
        BEGIN
            EXEC [sp_EmployeeSplit_xpoView_insert]
                @ID,
                @Extension,
                @PhotoPath
            FETCH NEXT FROM @cur INTO
                @ID,
                @Extension,
                @PhotoPath
        END
        CLOSE @cur
        DEALLOCATE @cur
    END
    GO
    CREATE TRIGGER [t_EmployeeSplit_xpoView_update]
    ON [EmployeeSplit_xpoView]
    INSTEAD OF UPDATE AS
    BEGIN
        DECLARE @cur CURSOR
        SET @cur = CURSOR FOR
            SELECT
                i.[ID],
                d.[Extension] as [old_Extension],
                i.[Extension],
                d.[PhotoPath] as [old_PhotoPath],
                i.[PhotoPath]
            FROM
                inserted i
                INNER JOIN
                deleted d
                ON
                    i.[ID] = d.[ID]
        OPEN @cur
        DECLARE @ID int
        DECLARE @old_Extension nvarchar(4)
        DECLARE @Extension nvarchar(4)
        DECLARE @old_PhotoPath nvarchar(255)
        DECLARE @PhotoPath nvarchar(255)
        FETCH NEXT FROM @cur INTO
            @ID,
            @old_Extension,
            @Extension,
            @old_PhotoPath,
            @PhotoPath
        WHILE(@@fetch_status <> -1)
        BEGIN
            EXEC [sp_EmployeeSplit_xpoView_update]
                @ID,
                @old_Extension,
                @Extension,
                @old_PhotoPath,
                @PhotoPath
            FETCH NEXT FROM @cur INTO
                @ID,
                @old_Extension,
                @Extension,
                @old_PhotoPath,
                @PhotoPath
        END
        CLOSE @cur
        DEALLOCATE @cur
    END
    GO
    CREATE TRIGGER [t_EmployeeSplit_xpoView_delete]
    ON [EmployeeSplit_xpoView]
    INSTEAD OF DELETE AS
    BEGIN
        DECLARE @cur CURSOR
        SET @cur = CURSOR FOR
            SELECT
                [ID],
                [Extension],
                [PhotoPath]
            FROM deleted
        OPEN @cur
        DECLARE @ID int
        DECLARE @Extension nvarchar(4)
        DECLARE @PhotoPath nvarchar(255)
        FETCH NEXT FROM @cur INTO
            @ID,
            @Extension,
            @PhotoPath
        WHILE(@@fetch_status <> -1)
        BEGIN
            EXEC [sp_EmployeeSplit_xpoView_delete]
                @ID,
                @Extension,
                @PhotoPath
            FETCH NEXT FROM @cur INTO
                @ID,
                @Extension,
                @PhotoPath
        END
        CLOSE @cur
        DEALLOCATE @cur
    END
    GO
    
    
    DROP TRIGGER [t_EmployeeSplit_xpoView_delete]
    GO
    DROP TRIGGER [t_EmployeeSplit_xpoView_update]
    GO
    DROP TRIGGER [t_EmployeeSplit_xpoView_insert]
    GO
    DROP PROCEDURE [sp_EmployeeSplit_xpoView_delete]
    GO
    DROP PROCEDURE [sp_EmployeeSplit_xpoView_update]
    GO
    DROP PROCEDURE [sp_EmployeeSplit_xpoView_insert]
    GO
    DROP VIEW [EmployeeSplit_xpoView]
    GO

    In addition to the above generated code, the corresponding persistent classes will be decorated with the PersistentAttribute, mapping them to the corresponding views in the database:

    using System;
    using DevExpress.Xpo;
    namespace Northwind {
    
        [Persistent("EmployeeSplit_xpoView")]
        public class EmployeeSplit : XPLiteObject {
            int fID;
            [Key]
            public int ID {
                get { return fID; }
                set { SetPropertyValue<int>("ID", ref fID, value); }
            }
            string fExtension;
            [Size(4)]
            public string Extension {
                get { return fExtension; }
                set { SetPropertyValue<string>("Extension", ref fExtension, value); }
            }
            string fPhotoPath;
            [Size(255)]
            public string PhotoPath {
                get { return fPhotoPath; }
                set { SetPropertyValue<string>("PhotoPath", ref fPhotoPath, value); }
            }
            public EmployeeSplit(Session session) : base(session) { }
            public EmployeeSplit() : base(Session.DefaultSession) { }
            public override void AfterConstruction() { base.AfterConstruction(); }
        }
    
        [NonPersistent]
        public class CustOrdersOrders : PersistentBase {
            int fOrderID;
            public int OrderID {
                get { return fOrderID; }
                set { SetPropertyValue<int>("OrderID", ref fOrderID, value); }
            }
            DateTime fOrderDate;
            public DateTime OrderDate {
                get { return fOrderDate; }
                set { SetPropertyValue<DateTime>("OrderDate", ref fOrderDate, value); }
            }
            DateTime fRequiredDate;
            public DateTime RequiredDate {
                get { return fRequiredDate; }
                set { SetPropertyValue<DateTime>("RequiredDate", ref fRequiredDate, value); }
            }
            public CustOrdersOrders(Session session) : base(session) { }
            public CustOrdersOrders() : base(Session.DefaultSession) { }
            public override void AfterConstruction() { base.AfterConstruction(); }
        }
        public static class NorthwindSprocHelper {
    
            public static DevExpress.Xpo.DB.SelectedData ExecCustOrdersOrders(Session session, string CustomerID){
                return session.ExecuteSproc("CustOrdersOrders", CustomerID);
            }
    
            static LoadDataMemberOrderItem[] CustOrdersOrdersOrderArray = {new LoadDataMemberOrderItem(0, "OrderID"), new LoadDataMemberOrderItem(1, "OrderDate"), new LoadDataMemberOrderItem(2, "RequiredDate")};
    
            public static System.Collections.Generic.ICollection<CustOrdersOrders> ExecCustOrdersOrdersIntoObjects(Session session, string CustomerID){
                return session.GetObjectsFromSproc<CustOrdersOrders>(CustOrdersOrdersOrderArray, "CustOrdersOrders", CustomerID);
            }
    
            public static XPDataView ExecCustOrdersOrdersIntoDataView(Session session, string CustomerID){
                DevExpress.Xpo.DB.SelectedData sprocData = session.ExecuteSproc("CustOrdersOrders", CustomerID);
                return new XPDataView(session.Dictionary, session.GetClassInfo(typeof(CustOrdersOrders)), CustOrdersOrdersOrderArray, sprocData);
            }
            public static void ExecCustOrdersOrdersIntoDataView(XPDataView dataView, Session session, string CustomerID){
                DevExpress.Xpo.DB.SelectedData sprocData = session.ExecuteSproc("CustOrdersOrders", CustomerID);
                dataView.PopulatePropertiesOrdered(session.GetClassInfo(typeof(CustOrdersOrders)), CustOrdersOrdersOrderArray);
                dataView.LoadOrderedData(CustOrdersOrdersOrderArray, sprocData);
            }
        }
    }

    Well that’s it for this post, until next time, happy XPO’ing Smile

  • XAF – Module Localizations Stored in Satellite Assemblies (Coming in V2010 Vol 2)

         

    In previous versions of XAF the support for localization was not perfect. The culture-specific resources of each module were stored in the module’s assembly, and it was impossible to localize them without rebuilding. As the default XAF modules are available only in English, you had to localize strings, supplied with the modules, in each new application. These included strings like: captions of classes and properties from the Business Class Library, captions of actions supplied with the System Module, validation messages, exceptions messages, etc. To put it bluntly, this was a PITA.

    As we all know, the .NET Framework provides a common way of storing the culture-specific strings in satellite assemblies. The MSDN states that the satellite assembly is “A .NET Framework assembly containing resources specific to a given language. Using satellite assemblies, you can place the resources for different languages in different assemblies, and the correct assembly is loaded into memory only if the user elects to view the application in that language.

    Previously, you could localize the DevExpress components, used in your XAF application, via the satellite assemblies. Now this approach is also available with XAF modules too!

    To support the satellite assemblies in XAF modules, we made the following changes:

    Firstly, the naming convention for the XML file storing the localization differences of the modules was changed. For instance, the Model.DesignedDiffs_de.xafml file is now called Model.DesignedDiffs.Localization.de.xafml. With this convention, Visual Studio places this resource in the satellite assembly automatically. Note the naming convention for user and application difference files remains the same.

    Next the algorithm for obtaining the list of languages, available in the Model Editor, was changed. Now, the list can be retrieved from these sources:

    1) The Languages key from the configuration file’s appSettings section:

    <?xml version="1.0" encoding="utf-8"?>
    
    <configuration>
    
      <appSettings>
    
        <!-- ... -->
    
        <add key="Languages" value="en;de;fr" />
    
        <!-- ... -->
    
      </appSettings>
    

    The value of this key consists of available language codes, separated by the semicolon.

    2) From the current project/application differences file names.

    When the Model Editor is invoked as the Visual Studio designer, only the second source is available. At runtime, these sources are joined. So, if there are no differences for a certain language, but this language is added in the configuration file, then this language can be specified as preferred, and the values stored in the corresponding satellite assembly will be used in the UI.

    Note, to load the localized property values from the satellite assembly, the Model Editor or the application must be restarted:

    Languages Manager Restart Dialog

    Some of our customers have kindly provided translations for ready-to-use satellite assemblies, you can find these assemblies attached to The collection of localized DevExpress assemblies KB article. At the moment there are no V10.2 satellite assemblies, but they will be there shortly after release. This means that you’ll be able to get satellite assemblies for XAF modules and DevExpress components, used in your application. After installing them, and adding the target language with the Model Editor, you’ll see the translated values, leaving you to translate only your custom strings with the Localization Tool.

    Localization Tool

    Of course, if you don’t like the supplied translations, you can modify them. Smile

    If there are no ready-to-use XAF satellite assemblies for your language, you can create the translations with the Localization Tool. To reuse the translations in the future, you can export them to a CSV file. When localizing new applications, you can import the localization from this file. If you can send this CSV file to us, we’ll build satellite assemblies, based on your translations, and share them so that you and other customers will be able to use them in the future. How’s that for service, eh?

    Note that when deploying your application, you should deploy the required satellite assemblies to the client’s machine too. When using the XCopy deployment approach, you should either put the satellite assemblies in the application folder’s subfolder (e.g. MainDemo/de), or register them in GAC.

    The benefits of this new approach are:

    • Standard XAF modules can be localized simply by downloading and installing the satellite assemblies from the KB article mentioned above.
    • You can create the satellite assemblies for your custom XAF modules easily.
    • You can add new languages to the deployed application without the necessity to recompile and reinstall it.
    • You won’t experience any inconvenience due to these changes when migrating to a new XAF version, as the Project Converter will rename language-specific difference files and add the required languages to the configuration file.

    Well that’s all for this post, until next time, happy XAF’ing. Open-mouthed smile

  • XAF–Logoff Functionality (coming in V2010 Vol 2)

         

    XAF has had support for logging off and logging back onto ASP.NET XAF applications for quite some time, remember that small red key icon in the corner of the screen?

    WebLogoff

    Users have been asking us to introduce this functionality to Windows Forms XAF applications for a while now and in 10.2 we’ve finally introduced it!

    WinLogoff

    What this means is that now you can log off from an application and log back on using another user’s credentials (or have another user log on) without having to restart the application!

    We’ve introduced a couple of new events to the XafApplication class – LoggingOff and LoggedOff. You can handle the former, to cancel the log off process and the latter to be notified when a user has logged off.

    If you’re using a custom logon parameters class, you can implement the newly introduced ISupportResetLogonParameters interface, to support logging off without restarting the application. Implementing the interface is quite straightforward, as it exposes a single Reset method. A typical implementation should just release all managed and unmanaged resources and reset all key properties, like so…

    public class MyLogonParameters : ISupportResetLogonParameters
    {
    
        //...
        public void Reset()
        {
            company = null;
            employee = null;
            password = null;
            objectSpace.Dispose();
            objectSpace = null;
            availableCompanies = null;
            availableUsers.Dispose();
            availableUsers = null;
        }
    
    }

    So when would you use such a feature? Okay, imagine this scenario; you write an application for use in a warehouse. You have just input an order and the application is working out the location of each item on order for automated picking. As each item is picked, by robot, the picking list is updated. Obviously this is a long running process. In the meantime, one of your colleagues finishes his or her shift and wishes to “clock out”. With this feature, you can log off, have them log on and “clock out” and then log back in again yourself, to see how your long running process is coming along!

    Well that’s it for this post, until next time, happy XAF’ing!

    Update:

    The guys on the XAF team have asked me to update this post and let you in on some of the context for this feature. It was first requested by customers more than 3 years ago and ended up as a suggestion for our framework: Security.Win - Logon / Logoff without leaving application (http://www.devexpress.com/Support/Center/ViewIssue.aspx?issueid=S91410).

    On the surface it looked like a specific feature and was not highly requested by other users. However, as time went on more and more customers asked for this functionality and so we created a KB Article, showing how to accomplish this task in the current version of XAF. To be honest, the temporary solution worked fine, but we knew it would be much better, and easier for our users, if it was implemented in the framework by default.

    Over the next two years we continued to maintain this solution and provide some minor improvements and bug fixes. These bugs mostly occurred when migrating from one major version to another. Again, it seem to us that it would be less costly to have this feature as part of the framework itself.

    Finally, the new application model in the 10.1 release, several related suggestions, as well as customer demand, made it possible to plan this feature for the 10.2 release. Early results of our work can already be seen in the 10.1.X versions, in which we introduced the log off procedure, demonstrated in the KB article, directly into the XafApplication class. Later, with the help of great customer feedback, we could support the most popular scenarios our customers needed, things like fast log off without the need to re-setup the whole application, support for custom logon parameters, support for scenarios in which user model differences were stored in the database, etc. We also refactored our Web library to provide the Log Off feature on the Core library level and finally polished the implemented functionality. Now we have a feature which consistently works on the two supported platforms.

  • BASTA #2 – What Are Products Such as Lightswitch Good For?

         

    Following on from my last post about customer chats on the booth; today I was chatting to a guy who had an interesting question, “What Are Products Such as Lightswitch Good For?” Now that question is an important question because we have XAF a product which, although more advanced in its feature set, operates in the same space as Lightswitch. So let’s see if we can answer that question…

    Time is Money
    XAF helps you bring your solution to your customers faster because it scaffolds the UI and database aspects of the application for you. The “plumbing'” boilerplate code for object persistence, via database, can take a long time to write, and it doesn’t really have any baring on the problem you are trying to solve. The same applies of simple CRUD UI forms or views. Having this done for you, by the framework, is a boon in terms of time saved.

    Clearing the Swamp or Fighting Alligators
    There is an old saying which goes something like, “When you are up to your ass in alligators, it’s hard to remember your goal was to drain the swamp”. The same thing applies in software engineering. The more code you have to write, like database “plumbing” code, the further away you get from the core of the problem you have to solve. How many times have you been working on trying to solve problem X for your company, only to become mired in ADO.Net – or similar – code, which really has nothing to do with solving the problem at hand? XAF’s scaffolding of this code for you, frees you to concentrate on your company’s problem, that is after all, where your subject matter expertise lies.

    Consistent UI Across the Enterprise
    If you are like me then you are a software engineer and just do not have the eye for the finer points of UI design. Well I say finer points, I really mean any points whatsoever. My UI design always look like they have been painted by a small child on a bad day. Now the default UI generated by XAF may not be “your cup of tea”, but if it’s better than you could do yourself then it will give you a professional and consistent look and feel across your enterprise.

    Consistent Data Access Model
    In my last post I touched on the fact that I’ve worked in enterprises where every architect has their own ideas about what a DAL should look like and so you see a slightly different approach in every project that you encounter. XAF, by virtue of it’s data access scaffolding, will give you a consistent approach throughout all of your applications.

    Write Once Run Everywhere
    It has long been a utopian dream that an engineer could write code once and have it run on every necessary platform. Well we are a long way from that dream aren’t we? I mean the code required to have your application run on a winforms platform is different from the code required to run it on a webforms platform, right? We’ll I’m not saying we are fully there yet with every platform, but XAF will allow you to define your application once and it will take care of the code required to have your application run on both winforms and webforms platforms – you don’t have to do anything extra.

    After explaining these things to the guy, and giving him a demo of XAF, he went away impressed and with a better understanding of the benefits that frameworks can bring to a developer. I hope you have too.

  • XPO – Stored Procedure Support Coming in V2010 Vol 2

         

    Unless you work in a very small software shop, or are a ‘one man band’, the chances are you don’t ‘own’ the data that you are trying to connect to. Between you and it there will normally be a DBA. You know the type, a big burly bruiser of a guy, telling you ‘if your name ain’t on the list you ain’t getting in’. You have to get passed this ‘gatekeeper’ if you want to access any of ‘his’ data. Because of this, any good ORM tool has to have ‘out of the box’ support for stored procedures, and I’m delighted to say that, as of V10.2, XPO will have this support.

    With the new release there’ll be two ways of working with stored procedures in XPO:

    1. Direct calling of existing stored procedures and the ability to handle returned data;
    2. Mapping of persistent classes on views in the database with help of INSTEAD-OF triggers and stored procedures.

    I guess most people will be using the first variant, so let’s go ahead and check out how that’ll work. Firstly, two methods have been introduced into the Session class:

    1. SelectedData ExecuteSproc(string sprocName, params OperandValue[] parameters), where sprocName is the name of the stored procedure you wish to execute and parameters are the parameters you wish to pass to this stored procedure. This method returns the result in a SelectedData instance.
    2. ICollection<T> GetObjectsFromSproc<T>(string sprocName, params OperandValue[] parameters). This method enables you to load data returned by the stored procedure into non-persistent classes.

    The Persistent Class Wizard has been extended to help you out when working with stored procedures. So let’s take a look at that now:

    To use the Wizard choose the Add New Item command:

    Add New Item Dialog

    Then select the Persistent Classes 10.2 item template, change the file name as required and press Add, specify the connection information and hit next

    Persistent Classes Wizard

    In the next step, the wizard displays a list of tables and their columns that can be mapped to persistent objects. We will select the EmployeeSplit table (just for demo purposes) and then press Next:

    Generate Persistent Classes Wizard - Select EmployeeSplit Table

    Now select the stored procedures we interested in, and select the required columns that will be included in the result set returned by stored procedure then press Finish.

    Persistent class wizard

    In this example we will choose the CustOrdersOrders stored procecdure, which will return the OrderID, OrderDate, RequireDate, ShippedDate columns. Just for this demo we won’t include the ShippedDate column.

    After we press Finish, the wizard will generate the following code:

    using System;
    using DevExpress.Xpo;
    namespace Northwind {
    
        [Persistent("EmployeeSplit_xpoView")]
        public class EmployeeSplit : XPLiteObject {
            int fID;
            [Key]
            public int ID {
                get { return fID; }
                set { SetPropertyValue<int>("ID", ref fID, value); }
            }
            string fExtension;
            [Size(4)]
            public string Extension {
                get { return fExtension; }
                set { SetPropertyValue<string>("Extension", ref fExtension, value); }
            }
            string fPhotoPath;
            [Size(255)]
            public string PhotoPath {
                get { return fPhotoPath; }
                set { SetPropertyValue<string>("PhotoPath", ref fPhotoPath, value); }
            }
            public EmployeeSplit(Session session) : base(session) { }
            public EmployeeSplit() : base(Session.DefaultSession) { }
            public override void AfterConstruction() { base.AfterConstruction(); }
        }
    
        [NonPersistent]
        public class CustOrdersOrders : PersistentBase {
            int fOrderID;
            public int OrderID {
                get { return fOrderID; }
                set { SetPropertyValue<int>("OrderID", ref fOrderID, value); }
            }
            DateTime fOrderDate;
            public DateTime OrderDate {
                get { return fOrderDate; }
                set { SetPropertyValue<DateTime>("OrderDate", ref fOrderDate, value); }
            }
            DateTime fRequiredDate;
            public DateTime RequiredDate {
                get { return fRequiredDate; }
                set { SetPropertyValue<DateTime>("RequiredDate", ref fRequiredDate, value); }
            }
            public CustOrdersOrders(Session session) : base(session) { }
            public CustOrdersOrders() : base(Session.DefaultSession) { }
            public override void AfterConstruction() { base.AfterConstruction(); }
        }
        public static class NorthwindSprocHelper {
    
            public static DevExpress.Xpo.DB.SelectedData ExecCustOrdersOrders(Session session, string CustomerID){
                return session.ExecuteSproc("CustOrdersOrders", CustomerID);
            }
    
            static LoadDataMemberOrderItem[] CustOrdersOrdersOrderArray = {new LoadDataMemberOrderItem(0, "OrderID"),
    new LoadDataMemberOrderItem(1, "OrderDate"), new LoadDataMemberOrderItem(2, "RequiredDate")}; public static System.Collections.Generic.ICollection<CustOrdersOrders> ExecCustOrdersOrdersIntoObjects(
    Session session, string CustomerID){ return session.GetObjectsFromSproc<CustOrdersOrders>(CustOrdersOrdersOrderArray, "CustOrdersOrders",
    CustomerID); } public static XPDataView ExecCustOrdersOrdersIntoDataView(Session session, string CustomerID){ DevExpress.Xpo.DB.SelectedData sprocData = session.ExecuteSproc("CustOrdersOrders", CustomerID); return new XPDataView(session.Dictionary, session.GetClassInfo(typeof(CustOrdersOrders)),
    CustOrdersOrdersOrderArray, sprocData); } public static void ExecCustOrdersOrdersIntoDataView(XPDataView dataView, Session session,
    string CustomerID){ DevExpress.Xpo.DB.SelectedData sprocData = session.ExecuteSproc("CustOrdersOrders", CustomerID); dataView.PopulatePropertiesOrdered(session.GetClassInfo(typeof(CustOrdersOrders)),
    CustOrdersOrdersOrderArray); dataView.LoadOrderedData(CustOrdersOrdersOrderArray, sprocData); } } }

    In this example we’re really only interested in the CustOrdersOrders class, which was generated for our stored procedure. Notice that this class is non-persistent (it’s decorated with the [NonPersistent] attribute. The properties of this class correspond to columns of the result set we configured earlier.

    The static NorthwindSprocHelper class is a helper class that works with stored procedures from the Northwind database. This class provides the following methods:

    • ExecCustOrdersOrders – calls the CustOrdersOrders stored procedure via the ExecSproc method and returns a result set.
    • ExecCustOrdersOrdersIntoObjects – calls the stored procedure via the GetObjectsFromSproc methods and returns a collection of CustOrdersOrders objects.
    • ExecCustOrdersOrdersIntoDataView – calls the stored procedure via the ExecSproc method and returns a XPDataView class instance containing the results from the stored procedure execution. This method also has an overload that also calls the stored procedure but fills the XPDataView object passed to it as a parameter.

    The following code snipped demonstrates how to use the XPDataView class in your code:

    public static bool CheckRequiredDate(Session session) { 
        XPDataView view = new XPDataView(session); 
        NorthwindSprocHelper.ExecCustOrdersOrdersIntoDataView(view, session, "HUNGC"); 
        foreach(DataViewRecord record in view) { 
            if(record["RequiredDate"] != null && ((DateTime)record["RequiredDate"]) > DateTime.Now) { 
                return true; 
            } 
        } 
        return false; 
    } 

    It’s worth mentioning that XPDataView can be used as a data source. To demonstrate this usage, let’s create a new Windows Forms application and add a new Form into it. Then drop the GridControl(gridControl1), UnitOfWork(unitOfWork1)and XPDataView(xpDataView1)components onto this form:

    XPDataView as a data source

    In the property grid for the unitOfWork1 component set the connection string to the Northwind database:

    XpoProvider=MSSqlServer;data source=localhost;integrated security=SSPI;initial catalog=Northwind

    Properties Dialog - Setting the connection String

    Then set the Session property of the xpDataView1 to unitOfWork1.

    Properties Dialog - Setting the Session Property

    Finally, select xpDataView1 as a data source of the gridControl1 component.

    Grid Control Tasks Dialog

    To load the data from the stored procedure write the following code in the form’s Load event handler:

    private void Form1_Load(object sender, EventArgs e) { 
        NorthwindSprocHelper.ExecCustOrdersOrdersIntoDataView(xpDataView1, session, "HUNGC"); 
    } 

    Then run the application to see the data:

    The Finished Application

    That’s it for this post, in the next one we’ll look at the second method of using stored procedures in XPO 10.2. Until then, happy XPO’ing! :-)

  • BASTA #1 – Why Would I Use an ORM Tool?

         

    The DevExpress booth at BASTA The best thing about being at a conference like BASTA is the great developers you get to meet. And the best thing about meeting with good developers is the great conversations that you have. Recently at DevExpress we decided it would be a good idea to broadcast some of these conversations to the wider DevExpress audience. To that end, I want to surface a conversation I had with a BASTA attendee today regarding ORM’s and why he would want to use one. In my view, a developer should consider using an ORM tool if he finds himself in one of the following scenarios:

    You Work on a Team That Shares a DBA With Several Teams
    Within software engineering there are always going to be more developers that DBAs, I mean has anyone here ever worked on a team where the DBAs outnumbered the devs? No, exactly. Of course this can cause problems.

    Firstly, the DBA might be working on things for one of the other teams that he supports and the work for your team may have a lower priority. It may be high priority to you, but a low priority as far as the enterprise is concerned, and so you are just going to have to wait.

    Secondly, even if the DBA is working on stuff for your team, the functionality that you require still might not be the top priority for him.

    Here, an ORM tool can help you by abstracting the data access functionality away from you and taking over the services of a DBA. If your ORM tool supports the provider model, where there are specific providers for your RDBMS, then the SQL produced will be tailored to your specific needs and can be just as good as code created by your DBA. This can remove or reduce the bottleneck caused by the shared DBA.

    You Work in a Small Developer Shop That Does Not Have a DBA
    Without a DBA to help you out, the SQL side of object persistence can be off putting. You know that you know C# or VB or (insert language of choice), but when it comes to SQL, you can hack away sure, but you know your code is going to be sub-optimal at best. In this scenario, you can abstract that complexity away by using an ORM tool. The ORM tool can act as an RBDMS subject matter expert, allowing you to execute a “save” command, safe in the knowledge that the ORM vendor knows the best RDBMS specific method of achieving what you want.

    You Want to Rationalize The Data Access Layers Used Within Your Organization
    We all know this scenario. We were told at university that one of the main benefits of OO was code reuse, and yet in most organizations you can look around at a number of projects and see the Data Access Layer implemented in a slightly different way each time. Use an ORM tool to prevent this from happening. Instead of each architect going with his or her favourite pattern, simply use the ORM as the DAL and your code will be the same from project to project.

    Your Code Needs to be RDBMS Portable
    So you’ve written a great application, so good every enterprise is going to want to use it. It’s going to make you millions. The downside, apart from deciding how you are going to spend all that money, is the fact that each enterprise uses one of a dozen RDBMSs and of course they *must* have your app be compatible with the one they use. What a pain! Or at least it would be if you didn’t use an ORM. By using an ORM you abstract away the difference from RDBMS to RSBMS and moving from one to another can now be as simple as changing providers.

    Now I’m not going to pretend that this list is exhaustive, but it contains the reasons that come most readily to my mind. I’m sure you can think of your own reasons for using an ORM tool – if so, feel free to add them in the comments, I’d be interested to read them (and to steal them to use the next time someone asks me why they should use an ORM tool). :-)

     

    Update: Guys, I said post your reasons for using ORMs, not post your opinions of which ORM tools you like. Remember the ‘house rules’ no using our web / forums for advertising competitor products, it’s not polite. ;-)

  • BASTA – Mainz, Germany, September 20th–24th (See the new XAF Features!)

         

    On Sunday Rachel and I will be leaving for the BASTA conference in Mainz, Germany, where we’ll be manning the DevExpress booth. I’ll leave her to say a little about what we’ll be doing there next week, but let’s let the organizers tell you a little about their conference.

    “BASTA! is well known for its program of expert speakers - there's hardly a conference in Europe that can boast as many expert speakers as BASTA! We also put a special focus on sessions and workshops, to provide delegates with hands-on experience: useful programming tips, insights into new technologies, and the invaluable experience of networking and sharing ideas with other experts.”

    So, if you are a customer already, or a developer who loves really great technology, and you are attending the conference, why don’t you stop by our booth for a chat and a demonstration?

    As an added bonus for customers and others interested in XAF, I’ll have an early copy of V2010 Vol 2 and I’ll be showing off all the latest and greatest functionality like the dashboard and key performance indicators.

    Hope to see you there! Smile

  • XAF – Model Editor Miscellaneous Enhancements (coming in v2010 vol2 )

         

    Recently, we announced the multi-selection functionality that will be available in XAF Model Editor v2010 vol2 when it ships. But what else is coming? Well, let’s take a look.

    Property Grid Enhancements

    The property grid now comes with a cool new mini toolbar.

    Mini Toolbar on The Property Grid

    Which has the following buttons:

    Categorized/Alphabetic Buttons

    As you’ll remember, property categories were introduced in the 10.1 release, and now, just like Visual Studio property window, the Categorized and Alphabetic buttons allow you to switch between categorized and alphabetic views.

    Undo Changes Button

    This button duplicates the functionality of the Undo Changes button, which is displayed to the right of the property value with focus. It is active when the value of the property with focus is modified (displayed in bold), and resets that property value to its default value. It acts in the same way as the node context menu’s Reset Differences command, but it is applied to a single property. This button has the CTRL+Z shortcut assigned.

    Open Object Button

    This button moves the focus to the node associated with the currently selected value. For instance, in the image above, clicking this button allows you to navigate to the Contact_ListView node automatically. This button has the ALT+O shortcut assigned.

    Open Source Property Button

    When the value with focus is calculated, this button moves the focus to the source property. For instance, you can shift focus to a column’s Caption, click this button and the Caption property of the corresponding class member will be given focus automatically. This button has the ALT+N shortcut assigned. Alternatively, you can give focus to a calculated property and then click its name to navigate to the source property.

    Layout Customization Improvements

    Sometimes you want to modify the layout of a Details View via the Model Editor’s property grid, rather than by using the layout customization mode. This approach is more complex than following the visual prompts, but it provides full control over what your layout will look like. Now it is possible to create a new Layout Item and then choose its ViewItem property from the dropdown list, instead of typing it manually. The list include items that are available in the DetailView | Items node and are absent in the current layout.

    Select ViewItem From Dropdown

    Do you know what’s really annoying? You spend ages getting the layout of your view just right, then you add another property to the business class, and BAM! Code gets generated and messes with your beautiful UI. Well not anymore! Another improvement we’ve made is that you can now configure a view layout and “freeze” it once and forever. We introduced a new property on the new DetailView node called FreezeLayout. When it is set to True, the current state of the layout is copied to the model differences and so the default layout generation is ignored. If you use it though, then you are responsible for manually adding any new layout items. Of course, this property is set to False by default. Additionally, the ListView node now exposes the FreezeColumnIndices property which acts similarly – it freezes the column sorting and visibility customizations. With this property set to True, the current state of columns indexes is copied to the model differences, and so any changes on previous layers will be ignored. Columns that are generated later will have the Index property set to -1, and, therefore, will be hidden. You will need to unhide them manually, when required. This property is set to False by default, too. Smile

    Validation of the Property Values

    Validation of the property values, specified via the Model Editor, is now supported. For instance, let’s see what happens when you add a new CreatableItem node, leave its ModelClass value empty and attempt to navigate to another node in the tree:

    Property Value Validation

    As you see, the Validation Result window is invoked. To resolve an issue, you should either supply the required value, or delete the current node if it was created by mistake.

    We are planning to improve this feature to support more complex validation scenarios.

    Incremental Search in the Nodes Tree

    We have implemented experimental support of incremental search through expanded nodes in the tree. How does it work? Well, when a node has focus and you start typing, then the node where the Id starts with the characters you typed, will be given focus. We are planning to make further improvements to this feature in the near future. If you are interested, please track the following suggestion:

    Model Editor - Improving filtering/searching nodes capabilities in the application model treelist

    That’s all for this post, until the next time, happy XAF’ing

  • Does Testing Make You a Bad Programmer?

         

    If you’d asked me that question last week, I’d have said no, of course not. Actually, I’d probably have laughed at you and then said no, of course not. But today, I’m not so sure. So what’s changed? Simple really, last Wednesday I went to my son’s parent’s evening at the local Army Cadet Force Detachment, where he’s a member. There’s an obvious connection there, right? Well no, there’s not, and it took the lesson a few days to congeal in my mind. Let me tell you what I saw and why it made me form the question in the title:

    Army Cadet Force – The Way it Was
    First let me set the scene for you. I was a member of the ACF for 4 years, between 1984 and 1988, rising to the rank of Company Sergeant Major. At that time the assault course was completed by teams of cadets, against the clock and wearing battle order webbing. It featured in every military skills competition I ever competed in. The assault course at Barry Buddon, our weekend training area, is a third of a mile long and is a severe test of physical strength, endurance and team work. It is one of the longest and most demanding assault courses in the UK. No matter how introverted a cadet might be, you could always tell his character by the way he tackled the assault course.

    The Modern Army Cadet Force
    Fast forward 20 years and things have changed. A “risk aware” culture now permeates the ACF. The first thing that went was the battle order webbing, cadets are no longer allowed to wear it whilst crossing the assault course, I mean if you fell it could trap you in one of the water obstacles, couldn’t it? Next the name, assault course is a little “war-like” isn’t it? It should be changed to something a little more soft and cuddly, and so it is now an “obstacle course”. Then helmets. Helmet should definitely be worn, cadets might bump their heads whilst going through the tunnels or be hit in the head by a returning rope on the rope swing. Next the clock was removed. Cadets are no longer allowed to race across the assault course. Trying to beat a time, may cause them to take unacceptable risks. Finally, all of the water obstacles should be drained of water down to just below knee height, anymore and a cadet might drown if they couldn’t regain the edge. There is even talk of cadets having to wear safety harnesses and have a safety rope when completing the high obstacles. I haven’t been to the assault course since I left the cadets. On Wednesday night, I eyed the beast of yesterday, only to find that it’d had it’s teeth pulled and it’s claws blunted. It was a little sad to see if I’m honest.

    Diminishing Returns on Health And Safety
    Still, you have to balance that with the safety of the cadets, and they are much less likely to hurt themselves now, right? Well, no actually. In the 4 years that I was a cadet, I didn’t see a single bad accident on the assault course. In fact, I saw no accidents whatsoever, excluding the odd grazed knee and the like. But on Wednesday, there were a couple of cadets who hurt themselves quite badly, despite the ridiculous health and safety measures. Why? Simple, the cadets of today have no respect for the assault course. They are used to operating in an environment where every danger has been removed or blunted that they fail to take even the most basic precautions for themselves.

    In my day, apart from the odd shout of encouragement or instruction, there wasn’t a word uttered – everyone was concentrating on their role in the team, squeezing out every wasted second, everyone was giving every ounce of strength they had to finish in a fast time. Not now. Cadets were strolling along, exchanging the gossip of the day, totally oblivious to the dangers that might still lurk – because it is impossible to remove every danger from an environment. Consequently, a couple of the cadets took a tumble and hurt themselves, one of them pretty badly.

    You see, it’s clear to me that there is a diminishing return on health and safety measures. When helmets were introduced, I’m sure there was a reduction in the number of head injuries sustained. Now that number wasn’t big to begin with, because we were all aware of the danger, but reducing that small number was certainly a good thing. However, as each successive layer of health and safety was added there was a diminishing return because for every one person you protected, another was injured because they had become oblivious to the danger. On Wednesday night I saw more cadets injured taking a leisurely stroll across the assault course, than I saw during the whole of my cadet career.

    The Danger of Working in an Over-Tested Environment
    It occurred to me that this was not a function of something that the Army Cadet Force was doing, but instead it was a function of human nature. The more safe an environment is perceived to be, the less careful we humans are. Naturally then, this scenario is applicable to software engineering. With the current focus on TDD and automated unit testing, it stands to reason that the modern generation of engineer perceives him or herself to be working in a safe environment. I mean all the tests pass, so the code must be okay, right?

    Unfortunately, as the above shows, that is not the case. Just as the ACF can't remove all of the dangers from the assault course - so each cadet must take responsibility for their own safety whilst completing it - so tests can’t verify that your code works under every possible execution scenario. As a programmer, you must take responsibility for your own code’s safe execution. Before the fashion for TDD and automated unit testing, programmers were adept at writing defensive code, just as my generation of cadets were adept at ducking when they saw a heavy rope coming. The newest generation of programmers must not allow the presence of unit tests to lull them into a false sense that their code operates in a “safe” environment. If that happens, if they rely only on tests and do not learn how to code defensively, then the answer to the question posed in the title of this blog post will be yes, testing does make you a bad programmer.

  • XAF – New Localization Tool (coming in v2010 vol 2)

         

    Localization of an XAF application is not fun. As a developer you have to look through the whole Application Model to find and translate all of the localizable values and, being human, you can only get that wrong by missing something. You could always get a professional translator to do it, but that’s not really practical, as he would have to have Visual Studio and XAF installed. Thankfully, things are going to get much better with the upcoming v2010 vol2 release. Now there will be a Localization action in the Model Editor Toolbar, this will invoke the Localization form. This form provides a grid editor with all of the localizable properties’ names, paths, and values. A toolbar with the set of actions is also provided:

    Localization Form

    So, what can you do within this form? Well pretty much everything you’d ever need to do to get your application localized:

    • Manually translate all the localizable values.
    • Export the localizable properties’ values to a CSV file and pass it to a professional translator. This file can be viewed and edited in any spreadsheet or plaintext editor.
    • Import the CSV file with the translations.
    • Reuse the CSV, file with common localizable values, in other XAF applications.
    • Perform an automatic translation using the Google Translate service.
    • Apply sorting and filters. There are several predefined filters, and you can specify a custom one.

    Okay, let’s have a closer look at some of these features. Here, we assume that the target language aspect and localizable resources are already added to the Application Model (see the Localization help topic).

    Manual Localization

    To manually localize a value, via the form, do the following:

    In the Localization form, select the target language via the Translation Language action.

    Select Translation Language Action

    Apply the Untranslated non-calculated filter via the Filters action.

    Filter Action

    This filter selects properties which have no localized values, except for those whose values are calculated based on other values. For instance, OwnMembers node’s Caption properties will be displayed, and Columns node’s Caption properties will be hidden.

    Translate all the values in the Translated Value column. Pressing the ENTER key moves the focus to the next row. If there are several properties with the same Default Language value, the Copying dialog is displayed after pressing ENTER:

    Copying Dialog

    If you choose Yes, all these values will be translated at once, and the next value to be translated will be given the focused. The “Use always” checkbox allows you to skip this dialog for the next time. This setting will be in effect until you close the Localization form. Note that the Is Translated value is not changed instantly, this allows us to avoid applying the filter to values that have just been translated. Is Translated will be modified after saving changes.

    When you have finished with the translation, click Save or press CTRL+S to pass the changes to the Model Editor. The translated properties’ Is Translated checkbox becomes checked after saving.

    Localization Save

    Now you can click Reload, to reload values from the model, and apply the Calculated filter to see if there are calculated values to be modified.

    Localization Filter

    After saving changes and closing the Localization form, you can see the localized values in the Model Editor. Don’t forget to save the changes in the Model Editor!

    Import/Export the Localization

    The Localization form provides Import and Export actions. These actions can be used in the following scenarios:

    If you have hired a professional translator, you can use the Export action to create a CSV file containing the rows, currently selected in the Localization form. This file will include the same columns as you see in the Localization form, except for Is Translated and Is Calculated. You can pass the CSV file to the translator and ask him to edit the values, in the Translated Value column, using a spreadsheet or plaintext editor of his choice.

    Localization Import/Export

    The translated CSV file can be loaded back via the Import action. This action replaces the current, default and translated values with values found in the specified CSV file. Rows with invalid node path or property name are skipped.

    Localization Form

    XAF applications have a common localizable values set. You can create the basic localization to be reused in other XAF applications. To do this, create an empty XAF solution, and optionally, add frequently used modules and business classes. Localize this application, and export the localization to a CSV file. Later, you can import the basic localization from this file when localizing XAF applications.

    Use the Google Translate Service

    If you are not fluent in a target language, and you can’t afford the services of a professional translator, you can use the Google Translate service directly from the Localization form. First, select the values to be translated, then, click the Google Translate action, or press CTRL+G. In the dialog, select the original language and click Translate. The selected values will be passed to the Google Translate service. The values in the grid will be modified with the results from the service. If the translation is time-consuming, the progress bar will be displayed. Of course, an active internet connection is required. Don’t forget that an automatic translation is not perfect and requires manual reviewing. Winking smile

    Using Google Translate From The Localization Form

    That’s all for this post, until next time, happy XAF’ing! Smile

  • LightSwitch Illuminates Application Development

         

    Late last month Microsoft release a beta version of LightSwitch, the latest member of the Visual Studio 2010 family.

    LightSwitch, The Developer Story 
    With LightSwitch you can build custom applications and get the UI scaffolded for you by using pre-configured screen templates that give your application a familiar look and feel. LightSwitch also provides prewritten code and other reusable components to handle routine application tasks, you know, the day to day stuff that can be so tedious for developers. Of course, it’s not all “plug and play” programming, if you need to write custom code, you can use Visual Basic .NET or C#. Applications written in LightSwitch can deploy to the desktop or browser. In the final version of LightSwitch, you’ll be able to deploy your applications to the cloud too.

    LightSwitch And Data
    with Lightswitch you can attach your application to existing data sources, including MS Sql Server, Azure SQL, Sharepoint and other third-party data sources. Also, after release, LightSwitch will support Access. Creating data is done via a grid view:

    LightSwitch Create Table Dialog

    And you can connect to existing data by using the standard data wizard:

    LightSwitch Attach Data Source Wizard

    Reporting, LightSwitch’s Missing Functionality
    As of the moment, and probably at release time too, there is no built in reporting functionality for LightSwitch, instead, data can be exported to Excel for analysis or to Word for reporting.

    LightSwitch and XAF Battle Tyranny!
    Although, at first glance, it may appear that LightSwitch competes with our own XAF product, on closer examination you will see that they are really allies in the same battle. What battle is that you ask? It’s the battle that all corporate developers must fight at least once in their lives, the battle against the business’ love of pushing multi-user, departmental scale applications into desktop tools such as Access and Excel. Before we look at how LightSwitch helps XAF in this battle, let us pause for a moment and examine how it is that we find ourselves fighting this same battle over and over.

    That’s Another Fine (Corporate) Mess You’ve Gotten Me Into!
    There are many things that can cause a department to start to rely on desktop tools to perform tasks like this for which they were never designed, some are deliberate, and others are accidental. An example of an accidental reliance can be seen in what I call the “Honey I blew up the app!” effect. This is where one guy thinks it would be really useful to have a database of Doodahs, so he creates one for himself and hosts it on his desktop. This is what Access, for example, is designed for and so he’s a happy bunny at this stage. Soon other people in the office get to hear about the Doodah database and they think its a great idea and they say, “hey how about sharing that buddy?”, and being a good pal he does, he puts the Access file on some central share somewhere so his friends can use it too. Time passes and word of the Great Doodah Database spreads far throughout the company and before you know where you are, you have a mission critical piece of functionality contained within, what is essentially, a desktop application.

    An example of a deliberate reliance on these tools is what I call the “Three Wise Monkeys” effect. This happens when management in a company – for example a bus company – decide that IT is not their core activity or skill set and they no longer wish to have the expertise in house. They outsource their IT to a specialist company who, rightly, put in place processes for having software written, deployed and maintained. This is all very sensible, however, at the “coal face” it means that before, when a line manager wanted a simple application written, he’d walk down the hall and speak to “Joe” about it, now he has to fill in 3 copies of 4 forms and send them to 5 different people and then wait 6 months for his software. The result is that he can’t get anything done. Never fear though because there is a “get out of jail free” card. Although the company has outsourced all the application development and their systems are now managed for them, the outsourcing company decreed that Microsoft Office, and other applications of a desktop nature, were deemed to be “Products of Personal Effectiveness” and so are not covered by the agreement. This means that the outsourcing company wont give you support because you don’t know how to remove the bold emphasise on your latest and greatest report, for instance. Of course, it also means that our hero can once again walk down the hall and speak to “Joe”, only this time he gets him to build an Access database.

    LightSwitch And XAF Ride to the Rescue!
    However it happens we, as developers, know that this is a common story with a predictably bad ending: “Hey Joe, you know that Access database that we use to control our country’s nuclear deterrent, you did remember to back it up before you replaced your desktop machine, right?!”. It’s clear that developers need tools which are as easy to use as Access, Excel and their like, but actually allow them to develop robust and scalable applications. XAF and LightSwitch compliment each other in this exact space. Where LightSwitch offers the developer an easy way to create or connect to data, XAF offers the flexibility of reuse once the object model is created. Where XAF offers support for the traditional desktop and ASP.Net market, LightSwitch supports the emerging Silverlight technology. Where LightSwitch offers the ease of exporting data to Microsoft Office for reporting and analysis purposes, XAF offers a full and rich reporting experience through XtraReports. Clearly these two tools offer the developer the best hope in the fight against corporate take over by marauding desktop applications. Wherever you are fighting this battle, you’ll find us here to help. Smile

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.