Filter through a table to search through foreign key data

the title of this question may sound confusing so I shall try to explain myself the best way I can.

I'm creating an application that has many tables with many foreign key constraints. For example, in my case I have a student. Each student table has a foreign key relation with a parental details table and medical details table.

To make my application easy to use, I have implemented a series of different filters to help a user search through large amounts of data.

I have one listview to display the student records, and same with the parental and medical details. However, what I want to do is to search through the student records based on a set of criteria from the parental details. For example, search for a parental name. Eg; is a student has a parent called Bob, the listview will filter the students who's parent is called Bob.

This is what I've tried;

   //Constructor;
   StudentList = new ObservableCollection<StudentViewModel>(GetStudents());

   CollectionViewSource.GetDefaultView(StudentList).Filter = new Predicate<object>(MainFilter);

   //Properties
   private string contactNameSearch;
   public string ContactNameSearch
   {
       get { return contactNameSearch; }
       set
       {
           contactNameSearch = value;
           CollectionViewSource.GetDefaultView(StudentList).Refresh();
           OnPropertyChanged("ContactNameSearch");
       }
   }

   private bool FilterContactNameSearch(object obj)
   {
       StudentContactViewModel item = obj as StudentContactViewModel;
       if (item == null) return false;
       if (String.IsNullOrWhiteSpace(ContactNameSearch)) return true;

       if (ContactNameSearch.Trim().Length == 0) return true;

       if (item.Name1.ToLower().Contains(ContactNameSearch.ToLower())) return true;
       return false;
   }

   public bool MainFilter(object o)
   {
       return FilterContactNameSearch(o); // &... and more filters
   }

   //Xaml
   <TextBox Height="23" Name="txtContactName" Width="100" 
                    Text="{Binding ContactNameSearch, UpdateSourceTrigger=PropertyChanged}"/>

The code snippet above it what I have implemented for my other filter, which all of them work fine. However, when I bind this property to the textbox within my application, all of a sudden the data which populates the listview vanishes. This link here shows the before and after

If I put a breakpoint around the ContactNameSearch property, I also get a "No Window Source" dialog appear.

I have put together a small sample which can be found by click here

Therefore, my question is; Am I implementing this correctly and if not, is there an alternative way of doing it?

Answers


When provide users with filtering functionality in my applications, I tend to use two collections. One has the whole unfiltered collection and once filled, that remains unchanged. The other is to only contain items from the first collection that match the given filter condition(s). This second collection is the one that is data bound to the ItemsControl.ItemsSource property in the UI.

public ObservableCollection<YourDataType> DataTypes
{
    get { return dataTypes; }
    set
    {
        if (dataTypes != value) 
        {
            dataTypes = value; 
            NotifyPropertyChanged("DataTypes");
            FilterDataTypes();
        }
    }
}

public ObservableCollection<YourDataType> FilteredDataTypes // Data bind this one
{
    get { return filteredDataTypes ?? (filteredDataTypes = DataTypes); }
    private set { filteredDataTypes = value; NotifyPropertyChanged("FilteredDataTypes"); }
}

Now, your FilterDataTypes method basically needs to filter the DataTypes collection by whatever means you see fit and populate the FilteredDataTypes collection. This example uses a string input that is data bound to a filter box in the UI and the actual filter condition is in the CheckFields method.

private void FilterDataTypes()
{
    filteredDataTypes = new ObservableCollection<YourDataType>();
    string filterText = FilterText.Trim().ToLower();
    if (filterText == string.Empty)
    {
        foreach (YourDataType dataType in DataTypes)
        {
            FilteredDataTypes.Add(dataType);
        }
    }
    else
    {
        foreach (YourDataType dataType in DataTypes.Where(m => CheckFields(m)))
        {
            FilteredDataTypes.Add(dataType);
        }
    }
    NotifyPropertyChanged("FilteredDataTypes");
}

private bool CheckFields(YourDataType dataType)
{
    string filterText = FilterText.Trim().ToLower();
    return filterText == string.Empty ? true : 
        dataType.Parent.Name.ToLower().Contains(filterText);
}

public string FilterText
{
    get { return filterText; }
    set
    {
        if (filterText != value)
        {
            filterText = value;
            NotifyPropertyChanged("FilterText");
            FilterDataTypes(); // <-- Filters collection when value is changed
        }
    }
}

Now this simple example only has one filter input, but you can add as many as you want using other methods like the CheckFields method, but based on the values of other data bound properties:

private void FilterDataTypes()
{
    filteredDataTypes = new ObservableCollection<YourDataType>();
    string filterText = FilterText.Trim().ToLower();
    if (filterText == string.Empty)
    {
        foreach (YourDataType dataType in DataTypes)
        {
            FilteredDataTypes.Add(dataType);
        }
    }
    else
    {
        foreach (YourDataType dataType in 
            DataTypes.Where(m => CheckFields(m) | CheckOptions(m)))
        {
            FilteredDataTypes.Add(dataType);
        }
    }
    NotifyPropertyChanged("FilteredDataTypes", "DataTypesCount");
}

public YourDataType SelectedItem // <-- Data bind to ItemsControl.SelectedItem
{
    get { return selectedItem; }
    set
    {
        if (selectedItem != value)
        {
            selectedItem = value;
            NotifyPropertyChanged("SelectedItem");
            FilterDataTypes();
        }
    }
}

private bool CheckOptions(YourDataType dataType) // <-- And use to filter collection
{
    string filterText = SelectedItem.Name.Trim().ToLower();
    return filterText == string.Empty ? true : 
        dataType.Doctor.Name.ToLower().Contains(filterText);
}

Need Your Help

how to get inspect element code using c# 2

c# asp.net xpath html-agility-pack inspect

I want to get text from URL but text is not shown in source code. I can see it only in inspect element . Is there anyway, in C# to get the contents of Inspect element of the page. I try htmlagility...

Passing values through Ajax to Controller MVC 4

jquery ajax asp.net-mvc-4

Just trying to make a simple pass with Ajax. Values artID and v are giving the correct values. Get Ajax error every time.