Click here to Skip to main content
15,885,546 members
Articles / Desktop Programming / WPF
Article

Using EntitySpaces Business Objects in XAML

Rate me:
Please Sign up or sign in to vote.
4.42/5 (9 votes)
2 Nov 2007CPOL14 min read 63.3K   390   27   14
An exploration of how to use the EntitySpaces business object framework within a WPF application.
EntitySpaces Northwind Customer Editor

Table of Contents

  1. Introduction
  2. Pre-requisites
  3. Running the Sample Application
  4. ESN Business Layer Setup
  5. Developing the ESN User Interface
    1. Viewing Data from Business Entities
    2. The Editor Controls
    3. Handling Many-to-Many Relationships
  6. The EntitySpaces Collection Confusion
  7. Saving and Stuff
  8. Conclusion

Introduction

Developing an effective business layer is possibly the most difficult task of any database-driven application. Sure, there are challenges in everything, but the business layer is like the mutual friend of two people who hate each other: it's the thing that everyone leans on to tie things together and keep peace and harmony. The business layer interface needs to represent the database in a concise, effective, and flexible manner. The business layer also has to behave consistently when dealing with a combination of entities from the database and freshly-created entities in memory (without DB-assigned IDs/defaults, or calculated columns). As databinding methods between UI technologies and business layer technologies get more complex, it can be very painful if you choose two technologies that do not work well together.

Early on in my team's project, we were leaning pretty heavily towards Windows Presentation Foundation (WPF) and XAML to create our user interface. In order to continue with WPF/XAML, we needed to find a business layer framework that worked well with its databinding methodology. My first pick was, dOOdads. It quickly became apparent that d00dads did not work well with XAML, for reasons that aren't worth going into. After examining a few other frameworks, we did a lot of work with the EntitySpaces (ES) trial version, and found that it worked pretty well with XAML. Here are some of the reasons we liked ES:

  • Rich & Intuitive - EntitySpaces does what you want, how you want it; almost everything I want is there, and it's easy to use
  • Dynamic Queries - rather than using custom stored procedures or writing ad-hoc queries, ES provides a way to create dynamic, type-safe queries in code
  • Simple code generation - regenerating all business objects is simple, and uses partial classes so generated code does not overlap with user code
  • Hierarchical Properties - in addition to foreign key ID properties, typed reference properties are generated for objects referenced by foreign keys
  • Nullable Properties - generated properties use nullable types so we can tell the difference between null and empty/0 values
  • Custom Collection - these are handy because they provide a way to implement custom collection-level code while still providing common collection functionality
  • Good Support - free software is nice, but the support alone may be worth the cost--the ES team responded to some show-stopping issues very quickly

It hasn't all been perfect, but we've done pretty well with ES and XAML. The EntitySpaces team resolved a few issues, and we found work-arounds to some others. My intent with this article is to walk you through our journey of discovery and show you how to avoid the pitfalls. I hope that it helps you make an informed decision when choosing your presentation and business layer technologies.

Pre-requisites

The two technologies you should be familiar with before reading this are Windows Presentation Foundation and EntitySpaces. This is not intended to be a tutorial in either technology, but rather a demonstration of how to use the two together. If you're not familiar with the EntitySpaces Framework, I suggest you first familiarize yourself with the framework by reading the documentation available at the EntitySpaces Web site. I recommend starting with the Getting Started PDF or the Walk-through video presentation. In this article, I will only cover the specific steps that are important to get your business entities to work with WPF Databinding. As for WPF, you will need at least a basic knowledge of XAML and Databinding to understand the topics covered in this article.

To develop your own applications using EntitySpaces for your business layer, you will need to Download MyGeneration and the EntitySpaces trial version. Both are free downloads, but the EntitySpaces trial is limited to 45 days of use.

Running the Sample Application

There are four things you need to do before you can run the sample application for this article:

  1. Download the EntitySpaces trial edition
  2. Install the Northwind database on your SQL Server (The Northwind creation script is included in the zip file)
  3. Edit the connection string in ESN.WindowsClient.exe.config to connect to your database
  4. Add the following references to both projects:
    • EntitySpaces.Core
    • EntitySpaces.Interfaces
    • EntitySpaces.SqlClientProvider
    • EntitySpaces.Loader

Once all that's set, build the solution and you're good to go.

ESN Business Layer Setup

The first important step for using EntitySpaces in XAML comes in the code generation. WPF supports dynamic DataBinding via the INotifyPropertyChanged interface, so make sure you select the option 'Support INotifyPropertyChanged' when generating your business entities. Without this option, you will still be able to databind to EntitySpaces entities, but they will not reflect changes that occur in the underlying business layer.

You can see the options I used to generate the business objects for this project below:

MyGeneration - ES Generated Master Template #1 MyGeneration - ES Generated Master Template #2

And here's what the generated Customers class looks like:

esCustomers class Customers class

The generated classes are ready to go as soon as they're created. You can use the ES Custom Master Template with similar settings to create separate files (partial classes) to contain user-created code, but it's not necessary. For this project, I only added custom code to one class (covered later).

Developing the ESN User Interface

The user interface for the EntitySpaces Northwind Customer Editor is split into three areas:

  1. A grid to view customer data on all customers
  2. A grid to edit the data for the selected customer
  3. An area for matching customers with customer types (demographics).

The customer view grid is a piece of cake. Here's the XAML I used to define it:

Viewing Data from Business Entities

XML
<ListView Grid.Row="0" Grid.ColumnSpan="3" Name="CustomersListBox" 
        SelectionMode="Single" IsSynchronizedWithCurrentItem="True"
              ItemsSource="{Binding ElementName=ThisWindow, Path=Customers}" 
                ToolTip="Press Ctrl + S to save changes.">
  <ListView.View>
    <GridView>
      <GridViewColumn Header="Company Name" 
            DisplayMemberBinding="{Binding CompanyName}"/>
      <GridViewColumn Header="Contact Name" 
            DisplayMemberBinding="{Binding ContactName}"/>
      <GridViewColumn Header="Contact Title" 
            DisplayMemberBinding="{Binding ContactTitle}"/>
      <GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}"/>
      <GridViewColumn Header="City" DisplayMemberBinding="{Binding City}"/>
      <GridViewColumn Header="Region" DisplayMemberBinding="{Binding Region}"/>
      <GridViewColumn Header="Postal Code" DisplayMemberBinding="{Binding PostalCode}"/>
      <GridViewColumn Header="Country" DisplayMemberBinding="{Binding Country}"/>
      <GridViewColumn Header="Phone" DisplayMemberBinding="{Binding Phone}"/>
      <GridViewColumn Header="Fax" DisplayMemberBinding="{Binding Fax}"/>
      <GridViewColumn CellTemplate="{StaticResource DeleteCustomerTemplate}"/>
    </GridView>
  </ListView.View>
</ListView>

The first thing you need to realize is that the data source for the items in this ListView is an EntitySpaces collection (CustomersCollection). The ElementName in the binding refers to the Window itself (declared with Name="ThisWindow"), so the Customers property can be found in the code-behind for MainWindow.xaml (MainWindow.xaml.cs). Here's the property definition:

C#
private Biz.CustomersCollection _customers = null;
public Biz.CustomersCollection Customers
{
    get
    {
        if (_customers == null)
            ResetCustomers();

        return _customers;
    }
}

void ResetCustomers()
{
    _customers = new Biz.CustomersCollection();
    _customers.Query.OrderBy(_customers.Query.ContactName.Ascending);
    _customers.Query.Load();
}

Dynamically loading data from your database with EntitySpaces couldn't be easier. Since I didn't specify a where clause here, the _customers collection will be filled with all records in the Customers table (sorted by ContactName). It's important that collections like this should be instance variables defined on the Window/Control itself. In a tabbed application, I originally used static properties that were shared across multiple tabs for efficiency, but that caused some weird behavior where different controls referencing the collection would share the same selected item (changing it in one would affect the others).

Once the data source is set up, defining the columns is a piece of cake. Each column binds to a public property on the business entity class (Customers). Any public property can be a binding source for XAML controls, but the INotifyPropertyChanged interface and a proper implementation on each property is necessary for the binding to update automatically when the underlying data changes. The options I used when generating my business objects created property changed notification for all properties based on database columns, and it's easy to add PropertyChanged support to your own properties in the user-created code files.

The final point of interest in the customers grid is the column that uses a CellTemplate instead of DisplayMemberBinding. The static resource it references can be found in the Window.Resources section. It's a fairly trivial template, but is intended to demonstrate how you can use templates to display data or provide interaction in a more flexible way using more complex controls. You could just as easily template all columns with TextBoxes and/or ComboBoxes to allow inline editing in the grid, but I wanted to keep it simple in this example.

The Editor Controls

Ignoring the unimportant layout and label controls, the TextBoxes that allow you to edit customer data are just as simple as the viewer grid.

XML
<TextBox Grid.Row="0" Grid.Column="2" 
    Text="{Binding CompanyName, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="2" Grid.Column="2" 
    Text="{Binding ContactName, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="4" Grid.Column="2" 
    Text="{Binding ContactTitle, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="6" Grid.Column="2" 
    Text="{Binding Address, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="8" Grid.Column="2" 
    Text="{Binding City, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="10" Grid.Column="2" 
    Text="{Binding Region, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="12" Grid.Column="2" 
    Text="{Binding PostalCode, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="14" Grid.Column="2" 
    Text="{Binding Country, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="16" Grid.Column="2" 
    Text="{Binding Phone, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="18" Grid.Column="2" 
    Text="{Binding Fax, UpdateSourceTrigger=PropertyChanged}"/>

By default, Bindings to TextBox.Text are two-way, so whenever the text changes, the binding will write the text back to the Binding source (which in turn will update the GridView). The only thing I needed to change from the default was the UpdateSourceTrigger property. Without this setting, changes would not be passed to the source until the TextBox loses focus, so the GridView would be out of synch with the text as the user is typing. Although this example only uses TextBoxes and string properties, Binding to generated classes will work similarly for other controls and data types. However, in some cases, you may need to use a converter to convert the source data to the type of the property you're setting. Hmmm, that sounds like a segue...

Handling Many-to-Many Relationships

The EntitySpaces templates generate different property definitions based on the type of foreign key reference (one-to-one, one-to-many, many-to-many). For the Northwind Customers table, ES generates two properties and two methods for managing the many-to-many relationship with CustomerDemographics.

C#
// Properties
CustomerCustomerDemoCollection CustomerCustomerDemoCollectionByCustomerID
CustomerDemographicsCollection UpToCustomerDemographicsCollection

// Methods
void AssociateCustomerDemographicsCollection(CustomerDemographics)
void DissociateCustomerDemographicsCollection(CustomerDemographics)

The properties give you access to the collection of association records in the linking table and the collection of associated Demographics, respectively. You can either manually create and delete associations via the CustomerCustomerDemoCollectionByCustomerID or use the provided Associate and Dissociate methods. Any changes to associations will be saved to the database when you save the Customer. So how do you put all this together to make an effective user interface? Good question. Let's dig into it.

First off, we need to display all the available CustomerDemographics so the user can choose which to associate with the selected Customer. Then we need to display whether each demographic is associated. I decided to represent this through a CheckBox list. WPF doesn't actually have a pre-defined CheckBoxList control, but that's ok. We can use a generic ItemsControl with a template to display CheckBoxes.

XML
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" 
    VerticalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding ElementName=ThisWindow, Path=Demographics}">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel Orientation="Vertical"/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <CheckBox Margin="4,2,10,2" Content="{Binding CustomerDesc}" 
            Click="CustomerType_Click">
        <CheckBox.IsChecked>
          <MultiBinding Mode="OneWay" 
            Converter="{StaticResource CustomerDemographicsCheckConverter}">
            <MultiBinding.Bindings>
              <Binding ElementName="CustomersListBox" Path="SelectedItem"/>
              <Binding Path="."/><!-- The current Data Context (a CustomerDemographic)-->
            </MultiBinding.Bindings>
          </MultiBinding>
        </CheckBox.IsChecked>
      </CheckBox>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>

First things first: ItemsSource. This binds to a property that's similar to the Customers collection I created. Next, the ItemsPanel defines the layout of the items within the control. The most important part is the template that defines the content of each item. Since the ItemsSource is a CustomerDemographicsCollection, the DataContext of each item will be a CustomerDemographics object, so all bindings within will be based off that source. For example, Content="{Binding CustomerDesc}" is setting the text of each CheckBox to the description of each demographic.

The IsChecked status was the trickiest part. First off, it needs to be a MultiBinding, since I need to know both the current customer and demographic in order to determine if they're associated. Second, I need a custom MultiValueConverter to convert the values from the binding into a boolean checked status. Finally, I have to handle changes to the checked status and represent those changes in the business object. Value converters support bi-directional conversions, but it would be difficult to convert a boolean (the checked status) into a Customer and CustomerDemographic object. I decided to keep it simple, using a one-way binding and handling the Click event on the CheckBox. CheckBoxes also have Checked and Unchecked events, but those fire before the framework sets the IsChecked status, so that won't work. Why not? Because the binding is one-way, WPF will set the CheckBox's IsChecked status to a literal value after the box is clicked. Whenever a databound property is set to a literal value, it will overwrite the binding. So after the user clicks a CheckBox, its checked status will remain the same, even after I select a different customer. My solution was to reset the binding after each CheckBox click (_checkBoxBinding is a binding object equivalent to the one declared in the XAML file).

C#
void CustomerType_Click(object sender, RoutedEventArgs e)
{
    Biz.Customers currentCustomer = this.CustomersListBox.SelectedItem as Biz.Customers;

    if (currentCustomer == null)
        return;

    CheckBox check = sender as CheckBox;

    if (check == null)
        return;

    Biz.CustomerDemographics currentDemographic = 
            check.DataContext as Biz.CustomerDemographics;

    if (currentDemographic == null)
        return;

    // This will be after the check status changed
    if (check.IsChecked ?? false)
        currentCustomer.AssociateDemographic(currentDemographic);
        //currentCustomer.AssociateCustomerDemographicsCollection(currentDemographic);
    else
        currentCustomer.DissociateDemographic(currentDemographic);
        //currentCustomer.DissociateCustomerDemographicsCollection(currentDemographic);

    check.SetBinding(CheckBox.IsCheckedProperty, this._checkBoxBinding);
}

You might notice another strange thing about this Click handler: I'm not using the generated Associate and Dissociate methods. That's because the changes these methods cause are not available through any public property of the business object. They work fine if you just want to associate something and save it, but if you want to bind to the most recent in-memory associations, you're out of luck. So instead of using the generated association methods, I created my own versions that simply add records to the linking table, then I check that table for associations in the binding's converter. Here's what those methods look like:

C#
public void AssociateDemographic(CustomerDemographics demographic)
{
    CustomerCustomerDemo link = this.CustomerCustomerDemoCollectionByCustomerID.AddNew();
    link.CustomerID = this.CustomerID;
    link.UpToCustomerDemographicsByCustomerTypeID = demographic;
}

public void DissociateDemographic(CustomerDemographics demographic)
{
    foreach (CustomerCustomerDemo link in 
            this.CustomerCustomerDemoCollectionByCustomerID)
        if (link.CustomerID == this.CustomerID &&
            (object.ReferenceEquals(link.UpToCustomerDemographicsByCustomerTypeID, 
                    demographic) ||
            link.CustomerTypeID == demographic.CustomerTypeID))
            link.MarkAsDeleted();
}

The only thing I don't like about this method is that you're creating and deleting records every time the user clicks a CheckBox. If there was already a record in the database, it might get deleted and replaced by a new one, so the linking collections aren't very smart that way. At least it all just works when you click save, though; no manual manipulation is required.

The EntitySpaces Collection Confusion

There's one more snag you'll want to be aware of when working with EntitySpaces. While the individual entities can support INotifyPropertyChanged, there is no support for INotifyCollectionChanged yet (it is new to .NET 3.0). This is what WPF controls primarily use for tracking changes in collections. You would think that because they don't implement this interface, ES collections will not supply the proper change notification to databound controls, and that you wouldn't be able to see changes to the collection reflected in the UI. That is not the case, though. I'm not sure why, but they do work in most cases. The only time a control will treat an ES collection differently from a List or ObservableCollection is when it starts out empty. If you load 1 or more records into the collection, and bind to it, everything will update as expected when you add and remove records, but if the collection was empty when it was first bound, the UI control will behave very strangely as records are added and removed. I'm not sure if this is a problem with EntitySpaces or WPF, but thankfully there is a work-around. You may have noticed this strangeness in the Demographics property definition:

C#
void ResetDemographics()
{
    _demographics = new Biz.CustomerDemographicsCollection();
    _demographics.LoadAll();
    _demographics.DetachEntity(_demographics.AddNew());
    //_demographics.AddNew().MarkAsDeleted(); <-- this works too
}

Since the problem occurs when binding to a collection that is initially empty, one of my co-workers discovered that if you simply add a record, then immediately remove it after you initialize the collection, everything works fine.

Saving and Stuff

The last thing I want to bring up is amusing in that I discovered two drawbacks as I tried to implement a single feature. What I wanted to do was have a column in the Customer grid with a Save button that would allow the user to save individual users. Here's what the template would have looked like for that column:

XML
<DataTemplate x:Key="SaveCustomerTemplate">
  <Button.Style>
    <Style TargetType="Button">
      <Setter Property="IsEnabled" Value="False"/>
      <Style.Triggers>
        <DataTrigger Binding="{Binding es.IsAdded}" Value="True">
          <Setter Property="IsEnabled" Value="True"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding es.IsModified}" Value="True">
          <Setter Property="IsEnabled" Value="True"/>
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </Button.Style>
</DataTemplate>

The first problem I discovered with this setup is that the Save buttons would never become enabled, even after I changed some customer data. That's because the properties generated for database columns implement PropertyChanged notification, but the core EntitySpaces classes like the one referenced in every entity's es property do not (the es object stores information about the entity's RowState). Since the es.IsAdded and es.IsModified properties do not fire property changed events, the IsEnabled status of the button will not ever change. I reported this issue to the EntitySpaces team. They've been pretty responsive in the past to bug reports and feature requests, so I expect this to be addressed in their next release.

The second problem with the individual Save buttons is one I should have realized when I first had the idea. EntitySpaces doesn't support saving individual entities that reside in a collection. It actually throws an exception. My understanding is that this is for efficiency reasons. Obviously, it's more efficient to save the changes to an entire collection at once, but some users may have valid business reasons for saving individual objects within a collection. In addition, some built-in EntitySpaces Framework behavior may cause this exception. This actually occurred when working on this sample application:

  1. User creates a new demographic
  2. User associates that demographic with the first customer
  3. User saves the customers collection
  4. When the first customer saves, it saves the new association, which saves the new demographic
  5. Since the demographic is part of a collection, an exception is thrown and the save is aborted

In this case, I was able to work-around the problem by saving the entire Demographics collection first, but it won't always be this easy. I think it would be much better if ES just allowed users to save entities independently from the collection in which they reside.

Conclusion

So that wraps up my analysis of using the EntitySpaces business object Framework with Windows Presentation Foundation. Overall, I'd say EntitySpaces is a great Framework that has room for a little improvement. If I had to quantify it, I'd say ES is about 90% XAML-ready. You'll probably be satisfied with the functionality of ES objects and XAML in most situations, but as with any technology (XAML itself included), there are a few snags you'll need to work around. If you'd like more information or assistance with EntitySpaces (with or without WPF), I recommend visiting their Web site and support forums.

Licence Notes

The code related to EntitySpaces is not covered by any specific licence. All other code in this article is covered by the licence below.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer VirtualScopics, Inc
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralCollection binding Pin
Nigel Stratton8-Aug-08 7:56
Nigel Stratton8-Aug-08 7:56 
GeneralLINQ Pin
Jeff Firestone7-Nov-07 4:54
Jeff Firestone7-Nov-07 4:54 
GeneralRe: LINQ Pin
S.B.2-Jun-08 17:19
S.B.2-Jun-08 17:19 
GeneralRe: LINQ Pin
dakeefer18-Jun-08 9:57
dakeefer18-Jun-08 9:57 
GeneralProblem building source Pin
ChuckC24-Oct-07 11:26
ChuckC24-Oct-07 11:26 
GeneralRe: Problem building source Pin
dakeefer25-Oct-07 4:10
dakeefer25-Oct-07 4:10 
GeneralRe: Problem building source Pin
ChuckC25-Oct-07 4:26
ChuckC25-Oct-07 4:26 
GeneralRe: Problem building source Pin
dakeefer30-Oct-07 8:25
dakeefer30-Oct-07 8:25 
GeneralRe: Problem building source Pin
dakeefer30-Oct-07 9:01
dakeefer30-Oct-07 9:01 
QuestiondOOdads problems? Pin
ChuckC23-Oct-07 3:55
ChuckC23-Oct-07 3:55 
AnswerRe: dOOdads problems? [modified] Pin
dakeefer23-Oct-07 9:29
dakeefer23-Oct-07 9:29 
QuestionnHibernate? Pin
trevorde stickman22-Oct-07 21:48
trevorde stickman22-Oct-07 21:48 
AnswerRe: nHibernate? Pin
dakeefer23-Oct-07 8:25
dakeefer23-Oct-07 8:25 
AnswerRe: nHibernate? Pin
dakeefer23-Oct-07 11:09
dakeefer23-Oct-07 11:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.