Thursday, June 16, 2016

Data Caching in a WPF


In this article I will explain how you can cache object in memory in WPF application to get better performance.

Cached data will be stored in memory so you will get faster access to that data compare to accessing it from File or Database. So whenever you need to make frequent calls to some database or file, better you cache that data and read from cached object this will improve your application performance via reducing multiple calls to database or file.

Caching mechanism provided by .Net framework 4.0. Caching is available under System.Runtime.Caching namespace and you need to add this namespace in your project references.  

See below example, where multiple calls are being made to database for getting employee details based on employee name.

XAML - 
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="50" />
        <RowDefinition />
    </Grid.RowDefinitions>
    <TextBlock Text="Name of employee - " HorizontalAlignment="Center"
               VerticalAlignment="Center"
                Grid.Row="0" Grid.Column="0" />
    <TextBox Name="EmpName" VerticalContentAlignment="Center"
             Text="{Binding Path=EmployeeName, Mode=TwoWay}"
                Height="30" Width="150" HorizontalAlignment="Center"
                VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" />
    <Button Name="Search" Content="Search" Click="Search_Click"
            Grid.Row="0" Grid.Column="2" Height="30" Width="150" />
    <ListBox Height="200" Width="200" Name="EmployeeList"
                ItemsSource="{Binding}" Grid.Row="1" Grid.Column="0"
                Grid.ColumnSpan="3" />
</Grid>

Code –
public partial class MainWindow : Window
{
    ObservableCollection<string> Employees = new ObservableCollection<string>();
    public MainWindow()
    {
        InitializeComponent();
    }
    //Search Button event handler
    private void Search_Click(object sender, RoutedEventArgs e)
    {
        Employees = GetEmployeesByName(EmpName.Text);
        this.DataContext = Employees;
    }
    //Make calls to database to get employee details based on given name
    private ObservableCollection<string> GetEmployeesByName(string name)
    {
        ObservableCollection<string> employeeNames = new ObservableCollection<string>();
        string connectionString = "Data Source=MITESH-PC\\SQLEXPRESS;Initial Catalog=TestSQLDB;Integrated Security=SSPI";
        using (SqlConnection sqlConnection = new SqlConnection(connectionString))
        {
            string cmd = "select firstname + ' ' + lastname as [EmployeeName] from HR.Employees where firstname like @FirstName";
            SqlCommand command = new SqlCommand(cmd, sqlConnection);
            command.Parameters.Add(new SqlParameter("@FirstName", name));
            sqlConnection.Open();
            using (SqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    employeeNames.Add(reader["EmployeeName"].ToString());
                }
            }
            sqlConnection.Close();
        }
        return employeeNames;
    }
}


Output – 



In above example, for each employee search is costing database call. To avoid database call for each employee, we can cache all employee name at once and for each subsequent search we can get it from cached object. This will reduce database call every time we search on employee and increase performance. See below code which implements caching.

Code –
public partial class MainWindow : Window
{
    ObservableCollection<string> Employees = new ObservableCollection<string>();
    //cache object
    ObjectCache cache = MemoryCache.Default;
    public MainWindow()
    {
        InitializeComponent();
        //Gets all employess from database
        Employees = GetAllEmployees();
        //sets employees in cache
        cache.Set("AllEmployees",Employees, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(5) });
    }
    //Search Button event handler
    private void Search_Click(object sender, RoutedEventArgs e)
    {
        //Get employeelist from cached object
        ObservableCollection<string> employeeList =  cache["AllEmployees"] as ObservableCollection<string>;
        //if cache is expired or no object available in cache then set cache object again
        if (employeeList == null)
        {
            employeeList = GetAllEmployees();
            cache.Set("AllEmployees", Employees, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(5) });
        }
        //search employee name from employee list loaded from cache object
        var selecteEmployees = (from emp in Employees
                                where emp.ToLower().Contains(EmpName.Text.ToLower())
                                select emp).ToList();
        this.DataContext = selecteEmployees;
    }
    //Gets all employess from database
    private ObservableCollection<string> GetAllEmployees()
    {
        ObservableCollection<string> employeeNames = new ObservableCollection<string>();
        string connectionString = "Data Source=MITESH-PC\\SQLEXPRESS;Initial Catalog=TestSQLDB;Integrated Security=SSPI";
        using (SqlConnection sqlConnection = new SqlConnection(connectionString))
        {
            string cmd = "select firstname + ' ' + lastname as [EmployeeName] from HR.Employees";
            SqlCommand command = new SqlCommand(cmd, sqlConnection);
            sqlConnection.Open();
            using (SqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    employeeNames.Add(reader["EmployeeName"].ToString());
                }
            }
            sqlConnection.Close();
        }
        return employeeNames;
    }
}

Output – 


Output is same as above example, but in this example In-Memory caching is being used. In constructor, I loaded all the employees from database and added to the cache object. When every time user search for employee, search will be perform on in memory object and result will be returned. In this example cache expiry is set to 5 minutes but you can set as per your need. After every 5-minute cache object will get expired and employee list will be null. After cache expired, if you still need cached object then you need to load it again from database or file and add to cache again.

In memory caching is very beneficial and recommended for applications which are making multiple calls to database, file system or others. You can set cache expiry time if you want to delete or update or refresh cache object after certain time. Caching will improve performance and scalability of your application.

Hope this article helps you to understand how to cache data objects in WPF application. Please leave your feedback in comments below.


References –

See Also –


No comments:

Post a Comment