Validate a Property Against a Parent Collection

April 11th, 2012

I was monitoring my Twitter feed, like I always do, and saw a tweet come through asking about how to validate an object’s property against it’s parent ObservableCollection in the Infragistics XamDataGrid.  What this person was trying to accomplish was to validate a duplicate item in his data source.  They have a POCO object the implements the IDataErrorInfo interface, and an ObservableColletion<POCO> as the data source.  They want to check the ObservableCollection<POCO> for a pre-existing item whenever a property value in a POCO changes.  Obviously this is not a XamDataGrid issue, but rather an object design issue.  So I decided to whip up a quick and dirty solution to solve this particular issue.

The approach I am going to take is to simply keep track of the parent collection from within the POCO object itself.  I really like interfaces, so I started out by defining a simple interface my POCOs will implement.

public interface IHasParent
{
    object Parent { get; set; }
}

Now let’s take a look at the simple POCO I will be using in this example.

public class Person : INotifyPropertyChanged, IDataErrorInfo
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    public string this[string columnName]
    {
        get
        {
            if (columnName == "FirstName")
            {
                if (String.IsNullOrWhiteSpace(FirstName))
                    return "First name cannot be empty";
            }

            if (columnName == "LastName")
            {
                if (String.IsNullOrWhiteSpace(LastName))
                    return "Last name cannot be empty";
            }

            return String.Empty;
        }
    }

    public string Error
    {
        get { return String.Empty; }
    }
}

Notice that we implement both the INotifyPropertyChanged interfaces as well as the IDataErrorInfo interface.  The Next thing we need is a custom ObservableCollection to use as our data source.

public class HasParentObservableCollection<T> : ObservableCollection<T>
{
    protected override void InsertItem(int index, T item)
    {
        //set the parent object when a new item is added to our collection
        if (item != null && item is IHasParent)
            (item as IHasParent).Parent = this;

        base.InsertItem(index, item);
    }
}

Notice that we are casting our item as the IHasParent interface and setting the Parent property accordingly.  The next thing we need to do is have our POCO class implement the IHasPerson interface.

public class Person : INotifyPropertyChanged, IDataErrorInfo, IHasParent
{
    public object Parent { get; set; }

    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            OnPropertyChanged("LastName");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    public string this[string columnName]
    {
        get
        {
            if (columnName == "FirstName")
            {
                if (String.IsNullOrWhiteSpace(FirstName))
                    return "First name cannot be empty";
            }

            if (columnName == "LastName")
            {
                if (String.IsNullOrWhiteSpace(LastName))
                    return "Last name cannot be empty";

                if (Parent != null && Parent is IList<Person>)
                {
                    var list = (IList<Person>) Parent;
                    if (list.Count(x => x.LastName == LastName) > 1)
                        return "This last name already exists.  Please use a different last name.";
                }
            }

            return String.Empty;
        }
    }

    public string Error
    {
        get { return String.Empty; }
    }
}

Notice how we implemented the check for a duplicate last name.  We simply checked to make sure we are dealing with the LastName property.  Then we cast the Parent as an IList<Person> so that we can perform a simply LINQ query against it.  We check the parent collection for any results that match the LastName property.  If more than one is returned we have a duplicate.

So let’s test this baby using the XamDataGrid as the original poster was attempting to do.  First create our UI.

<igDP:XamDataGrid Name="xamDataGrid1">
    <igDP:XamDataGrid.FieldLayouts>
        <igDP:FieldLayout>
            <igDP:Field Name="FirstName" Label="First Name" />
            <igDP:Field Name="LastName" Label="Last Name" />
        </igDP:FieldLayout>
    </igDP:XamDataGrid.FieldLayouts>
    <igDP:XamDataGrid.FieldLayoutSettings>
        <igDP:FieldLayoutSettings AddNewRecordLocation="OnTop"
                                  AllowAddNew="True"
                                  AutoGenerateFields="False"
                                  SupportDataErrorInfo="RecordsAndCells"
                                  DataErrorDisplayMode="Highlight" />
    </igDP:XamDataGrid.FieldLayoutSettings>
</igDP:XamDataGrid>

Next let’s hook up some data to this bad boy:

public MainWindow()
{
    InitializeComponent();

    var people = new HasParentObservableCollection<Person>();
    people.Add(new Person(){ FirstName = "Brian", LastName = "Lagunas"});
    xamDataGrid1.DataSource = people;
}

Now let’s run the app, type a duplicate last name, and see what we get.

collection_validated

Cool. Works as expected.  Now there are a number of ways to accomplish this task.  There are even frameworks out there that have already solved this problem for you such as CSLA.  I hope this simple approach helps you find a solution that works for you.

Download the Source.

  • Mario Ienasescu

    Maybe your code doesn’t need this performance, but I would change this:

    if (item != null && item is IHasParent)    (item as IHasParent).Parent = this; 

    to this:

    var 
    itemWithParent = item as IHasParent;
    if (itemWithParent != null)    itemWithParent.Parent = this; 

    It’s a good design to cache the casting and not checking for null three times. 

    • http://elegantcode.com Brian Lagunas

      Like I said, “quick and dirty”. It comes with the “demo code” disclaimer.