Friday, March 12, 2010

Visual Studio Just Got More Complex!

Still working my way through Julia Lerman's book Programming Entity Framework.  In chapter 12 she discusses several customizations that can be done to a EF Model, some of which will "break" the Visual Studio 2008 Designer, meaning you have to resort to looking at the XML of the *.edmx file instead of the nice visual layout.  One of these customizations is adding Complex types to an Entity.  I have been going through these examples using the Visual Studio 2010 Release Candidate and I'm happy to report that Microsoft has added full support for Complex types to the Designer!


Adding a Complex Type to Your Model
Adding a Complex type to your model is quite easy.  In the Designer, select the Model Browser (if you don't see this tab anywhere it can be access from the menus View/Other Windows/Entity Data Model Browser). In the Model Browser, expand the model node and you will see a Complex Types node.  Right-clicking on this node will add a new Complex type to your model
Rename the type to whatever you want and then right-click on the new type to start adding properties.  Each property has several settings that you can access through Visual Studio's Properties pane.  I would recommend changing the Nullable property to "True" as it will default to "False." Once you have finished building your type, you can add it to any of the entities in your model by right-clicking (sensing a pattern yet?) on the Entity and selecting Add/Complex Type from the context menu.


Mapping Complex Types to Your Model
Mapping these types is just as easy in VS 2010.  Just select the entity with the type and in the Mapping Details pane use the Value/Property drop-downs to establish the appropriate mappings from your table(s).

Binding Tips for Complex Types
Julia's book states correctly on page 339 that "Complex types are not Entity Objects." This is still correct in .NET 4.0, Complex types are based on ComplexObjects.  ComplexObject shares a common abstract sub-class with EntityObject called StructuralObject which implements the INotifyPropertyChanged interface.  This allows it to be change tracked by the EF and persisted to the database.  One problem, however is that introducing a ComplexObject into your model allows for a slight binding disconnect which you have to address manually if you want everything to work as you would expect.  Take for example the following setup:

In this instance if we assign the Detail property of an Address instance to an instance of the AddressDetail Complex type our Address entity will fire the PropertyChanged event to alert any bindings that they will need to update.  That's great, but if you edit a property of the Detail instance the PropertyChanged event will not fire for the Detail property (it does fire inside the ComplexObject, it just doesn't bubble up to the object that has the Complex type as a property (in this case the Address entity).  This may not be a problem for your particular binding implementation, but it was for mine.  In my setup, I defined a CollectionViewSource that sorted the Address entities on the Detail property:

<tns:AutoRefreshCollectionViewSource x:Key="AddressSource"  >
  <tns:AutoRefreshCollectionViewSource.SortDescriptions>
    <scm:SortDescription PropertyName="Detail" Direction="Ascending"/>
  </tns:AutoRefreshCollectionViewSource.SortDescriptions>
</tns:AutoRefreshCollectionViewSource>

Here I used the AutoRefreshCollectionViewSource class from my previous post. In order to sort on the AddressDetail type I needed to implement the IComparable interface.  In addition, I have added a "FullAddress" string property and overriden ToString() to return this value.


public partial class AddressDetail: ComplexObject, IComparable
{
  public String FullAddress
  {
    get
    {
      return (Street1 ?? "Null").Trim() +
              ", " + (City ?? "Null").Trim() +
              ", " + (StateProvince ?? "Null").Trim() +
              " " + (PostalCode ?? "Null").Trim();
    }
  }
 
  public override string ToString()
  {
    return this.FullAddress;
  }

  #region IComparable Members

  public int CompareTo(object obj)
  {
    AddressDetail otherCust = obj as AddressDetail;
    if (otherCust != null)
      return this.FullAddress.CompareTo(otherCust.FullAddress);
    else
      throw new ArgumentException("Object is not a AddressDetail");
  }

  #endregion
}

Since I overrode the ToString() method I can assign DisplayMemberPath for my ListBox directly to the Detail property of Address.  When I used this implementation it worked great until I edited on the AddressDetail classes properties and figured out that the bound ListBox would not update to reflect the new value of FullAddress.  This is because, as far as the Address entity is concerned, Detail has not changed (it is still the same object instance!).  One possible work-around for this is to add the following code to the Address partial class:


public partial class Address: EntityObject
{

  partial void OnDetailChanging(AddressDetail newDetail)
  {
    if (newDetail != null)
    {
      newDetail.PropertyChanged += new PropertyChangedEventHandler(AddressDetailChanged);
    }
  }
  
  public override string ToString()
  {
    return this.Detail.ToString();
  }

  public void AddressDetailChanged(object sender, PropertyChangedEventArgs e)
  {
    OnPropertyChanged("Detail");
  }

}

This code attaches a listener to the PropertyChanged event of the Addresses Detail object.  This listener will then fire the PropertyChanged event for the Address entity.  In this way the bound ListBox will update in response to changes in the properties of the Detail complex object.  There might be a better place to set up the listener method, but this seems to work for now.

No comments:

Post a Comment