3.2 Optimizing the Context Class
Although the context class will track your
objects, you can help the context class by ensuring that your table
classes support the INotifyPropertyChanging
and INotifyPropertyChanged
interfaces. Implementing these interfaces has the additional benefit of
assisting with data binding in XAML. Therefore, it is recommended that
all your table classes support this interface, like so:
[Table]
public class Game : INotifyPropertyChanging, INotifyPropertyChanged
{
// ...
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
void RaisePropertyChanging(string propName)
{
if (PropertyChanging != null)
{
PropertyChanging(this,
new PropertyChangingEventArgs(propName));
}
}
}
Implementing both interfaces will add the PropertyChanging
and PropertyChanged
events to your class. As seen here, creating a simple helper method to
raise these events is a common practice. Now that the interfaces are
implemented, you have to use them. This involves calling the helper
methods in each property setter. The original Game
class
used automatic properties to expose the columns, but because you need to
call the helper method, you need standard properties:
[Table]
public class Game : INotifyPropertyChanged
{
int _id;
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int Id
{
get { return _id; }
set
{
RaisePropertyChanging("Id");
_id= value;
RaisePropertyChanged("Id");
}
}
string _name;
[Column]
public string Name
{
get { return _name; }
set
{
RaisePropertyChanging("Name");
_name = value;
RaisePropertyChanged("Name");
}
}
DateTime? _releaseDate;
[Column]
public DateTime? ReleaseDate
{
get { return _releaseDate; }
set
{
RaisePropertyChanging("ReleaseDate");
_releaseDate = value;
RaisePropertyChanged("ReleaseDate");
}
}
double? _price;
[Column]
public double? Price
{
get { return _price; }
set
{
RaisePropertyChanging("Price");
_price = value;
RaisePropertyChanged("Price");
}
}
// ...
}
You should notice that each property now has a backing field member (for example, _id
for the Id
property) and calls the RaisePropertyChanging
and RaisePropertyChanged
methods with the name of the property when the setter is called. By
using these interfaces, the memory footprint of the context object is
much smaller because it uses these interfaces to monitor changes.
In addition to these interfaces, you can improve
the size of your update and delete queries by including a version
member of your class:
[Table]
public class Game : INotifyPropertyChanging, INotifyPropertyChanged
{
// ...
[Column(IsVersion = true)]
private Binary _version;
}
The version column (IsVersion = true
) is optional but will improve the performance of change tracking when using database data. The version must be of type Binary
from the System.Data.Linq
namespace. It can be a private field (so it’s not visible to users) but does need to be marked as IsVersion = true
for LINQ to SQL to consider it the version column.
Finally, if your database is only performing
queries, you can tell the context class that you do not want to monitor
any change management. You would accomplish this by setting the context
class’s ObjectTrackingEnabled
property to false,
like so:
using (var ctx = new AppContext())
{
ctx.ObjectTrackingEnabled = false;
var qry = from g in ctx.Games
where g.Price < 19.99
orderby g.ReleaseDate descending
select g;
var results = qry.ToList();
}
By disabling change management, the context
object will be much more lightweight. Also, because the context is not
necessary for tracking the change, you can create it locally and dispose
of it when the query is complete. You usually would keep the context
around for the lifetime of the page or application so that it can
monitor and batch those changes back to the database, but because you
are only reading from the database, the lifetime can be shortened if
needed.