Making a DataSet Read Only
December 7th, 2005
Let’s say you’re writing a component that makes a DataSet available to a number of clients. The DataSet is expected to persist in your application for a while, and be used in code written by many different developers.
You could carefully craft an email explaining that,
Hey everybody, this DataSet is shared. Please don’t change the data in it for the purposes of your own piece of the application. That could create some odd bugs that would be really hard to track down. If you need to do something like that, please make a copy of the DataSet first.
I suppose you could do that, if you wanted to waste your time. You could always create a copy of the DataSet before handing it over to anybody, but that may introduce some unnecessary performance issues if the DataSet is large. It feels a little like punishing everyone for the bad behavior of a few. If you wanted to actually prevent such problems, you could write a clever class like this that acts like a “lock” on the DataSet.
/// <summary> /// Class that ensures that clients keep their pesky hands /// off of a particular dataset. /// </summary> internal class DataSetLock { [Conditional("DEBUG")] public static void Install(DataSet ds) { if (ds == null) { throw new ArgumentNullException("ds"); } EventHandler thrower = delegate(object sender, EventArgs e) { string msg = string.Format( "You can't change: {0}.", ds.DataSetName); throw new InvalidOperationException(msg); }; foreach (DataTable t in ds.Tables) { t.RowChanging += new DataRowChangeEventHandler(thrower); t.RowDeleted += new DataRowChangeEventHandler(thrower); t.ColumnChanging += new DataColumnChangeEventHandler(thrower); t.TableClearing += new DataTableClearEventHandler(thrower); t.TableNewRow += new DataTableNewRowEventHandler(thrower); } } private DataSetLock() { } }
The “lock” is really an anonymous method that we attach, willy nilly, to all of the change events in the DataSet. We use lexical closure to add the DataSet name passed in to the exception message dozens of caffeine-addled developer brains will be processing shortly. [Note: if you don’t think it’s lexical closure, go talk to this guy. He’s really into the topic.]
I put the ConditionalAttribute on the Install method as well, so as long as you have reasonably good testing coverage this check will just be compiled away in retail code.
Here’s a quick usage example:
static void Main(string[] args) { DataSet ds = new DataSet(); ds.DataSetName = "My Data Set"; DataTable t = ds.Tables.Add(); t.Columns.Add("foo", typeof(string)); DataSetLock.Install(ds); // Pow! t.LoadDataRow(new object[] { "asdf" }, true); }
This prints:
System.InvalidOperationException: You can't change: My Data Set.
Enjoy.