SqlException
instance. The post links to a solution on another blog. Since I have been doing a bit of work using reflection lately I thought I should follow up on the original post and explain exactly what the problem is, how it's solved, and also include the full source code (in case the original post on the other blog is lost).So, here we go. My original problem was that I needed to be able to throw an instance of
SqlException
with a specific error code. SqlException
cannot be instantiated directly, however, because its constructor is marked as private. The only way to get around this is to use the very useful tools in the System.Reflection
namespace and get at the private constructor via exploration of the metadata emitted by the System.Data
assembly.Before I go on, if you are not familiar with what reflection is I recommend that you first read about it here. Also, when working with reflection to discover and create types Lutz Roeder's Reflector is invaluable. Download it!
Now we're equipped for the task at hand. Using Reflector we'll first inspect the
SqlException
class to discover what's required to create an instance, and then we'll use the System.Reflection
namespace to write a set of methods that allow us to create a SqlException
instance with the required state. It really isn't difficult, but writing this kind of code (using System.Reflection
) does not feel natural at first pass, so I'll go through it here step by step.1. Inspect
SqlException
with Reflector.Open Reflector and load the
System.Data
assembly. Expand the System.Data.dll
node, and then the System.Data.SqlClient
node. Scroll down until you find the SqlException
node and expand this also. When you expand this node you'll find that the SqlException
class has two constructors (both private), three methods (one public, one private, one internal), and several public properties.One of the private constructors takes a string (an error message) and a
SqlErrorCollection
instance as parameters. This is the constructor we'll be working with. Using System.Reflection
we'll create an instance of SqlException
by passing in a string of our choosing, plus an instance of SqlException
that we'll also create. But before we get carried away, let's have a look at the SqlErrorCollection
class.2. Inspect
SqlErrorCollection
with Reflector.SqlErrorCollection
is listed just above SqlException
in the node list. Expand it and you'll find one internal constructor that takes no parameters (how useful for us!), one internal Add(SqlError)
method (probably a bit more useful), three public methods, two public properties, and two inherited properties.So, in order to create an instance of
SqlErrorCollection
for our SqlException
constructor it seems we'll have to utilise System.Reflection
a bit more. Now, an empty SqlErrorCollection
is probably not going to be that useful to us either, so we'll have to create an instance of SqlError
and add it to the collection using the Add(SqlError)
method.3. Inspect
SqlError
with Reflector.SqlError
is listed just above the SqlErrorCollection
in the node list. When you expand it you'll see that it is quite simple, with an internal, seven-parameter constructor, and a single public method (overriding Object.ToString()
).Armed with this knowledge, the list of steps to complete our task is as follows:
- Create a
SqlError
with an appropriate error message and error code. - Create a
SqlErrorCollection
and add theSqlError
instance from step 1. - Create a
SqlException
instance by passing in an error message and theSqlErrorCollection
from step 2.
Almost there, but not quite. How exactly do we go about doing this? Before we carry on, it's time to have a look at the
System.Reflection
namespace. Put simply the System.Reflection
namespace allows you to discover the internal structure of a type, invoke methods, properties or constructors, and even create types during runtime. All of this is accomplished through the use of special purpose classes, some of which we will use here (I'm not going to discuss System.Reflection more in-depth here as it really deserves a long post on its own). Specifically we will be making use of the
ConstructorInfo
class (for calling constructors) and the MethodInfo
class (for calling methods). Instances of these classes are returned by calling GetMethod()
or GetConstructor()
, respectively, on a type. For example, calling typeof(SqlErrorCollection).GetConstructor(...params...)
returns an instance of ConstructorInfo
that we can use to invoke the constructor. In fact, the parameters we pass to GetConstructor
performs a search on the type (in this case SqlErrorCollection
) and returns a ConstructorInfo
instance that corresponds to the constructor found in the search. (If the constructor is not found, the ConstructorInfo
instance returned is null
.)Let's a have a bit of a closer look.
GetConstructor()
has three overloads, but I will only look at the second overload here as it is exactly what we need. The method takes four parameters: BindingFlags
, Binder
, Type[]
, and ParameterModifier[]
. The first parameter is a bitmap constructed of stringing together individual BindingFlags
that we use to indicate what type of constructor we're after (e.g. a non-public constructor). The second parameter, Binder
, allows us to pass in a special instance of a Binder
class that can be used to select a specific overload etc. The third parameter is an array of Type
, and this array specifies the types and number of the parameters in the constructor declaration. The fourth and last parameter is an array of ParameterModifier
- each ParameterModifier
in this array can be used to specify which parameters are to be passed by reference and so on.We only need to worry about two of these parameters, namely the
BindingFlags
and the Type
array. The two others we'll pass in as nulls and let the framework use the built in default behaviour.GetMethod()
is similar to GetConstructor()
but has a string parameter that can be used to pass in the name of the method to find. If the method we're looking for has no overloads we only need to specify the BindingFlags
for the method and we're done! Otherwise we can also pass in a Type
array to specify which overload, exactly, we're after.Now we're both armed with knowledge and equipped with some nifty tools to get the job done. Let's get started on our first task, creating a
SqlError
instance.Creating the
SqlError
instance.The constructor on
SqlError
takes seven parameters of different types. To search for this constructor using the GetConstructor()
method we'll have to declare at Type
array that defines these types in the correct order. Also, when it comes to actually invoking the constructor we'll need to pass in some actual parameters, and we'll have to declare these in an array of Object
. Then we'll get a reference to the SqlError
constructor by calling GetConstructor()
on its type, and finally create an instance of SqlError
by invoking the constructor with our parameters. Put together in a method it looks like this (there are some default parameters used here; you can replace these with something more dynamic if you like):
private static SqlError GetError(int errorCode, string message)
{
object[] parameters = new object[] { errorCode, (byte)0, (byte)10, "server", message, "procedure", 0 };
Type[] types = new Type[] { typeof(int), typeof(byte), typeof(byte), typeof(string), typeof(string), typeof(string), typeof(int) };
ConstructorInfo constructor = typeof(SqlError).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
SqlError error = (SqlError)constructor.Invoke(parameters);
return error;
}
Creating the
SqlErrorCollection
instance.This is a simpler task than creating a
SqlError
instance because the SqlErrorCollection
constructor doesn't take any parameters. All we have to do is get a reference to the constructor and then invoke it. Later we'll invoke the Add()
method to add the SqlError
instance returned by the method we wrote in the previous step. A method to create a SqlErrorCollection
instance looks like this:
private static SqlErrorCollection GetErrorCollection()
{
ConstructorInfo constructor = typeof(SqlErrorCollection).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
SqlErrorCollection collection = (SqlErrorCollection)constructor.Invoke(new object[] { });
return collection;
}
Creating the
SqlException
instance.This step basically does the same as what we've seen in the previous two steps, with the difference being that we invoke a method, and we use the instances of
SqlError
and SqlErrorCollection
created by the two methods that we have already written. In short, we create an instance of SqlErrorCollection
, an instance of SqlError
, call GetMethod()
on the SqlErrorCollection
type to get a reference to the Add()
method, then add the SqlError
instance to the collection. Finally we wrap it all up by instantiating a SqlException
by getting a reference to its constructor and passing in an error message along with the SqlErrorCollection
. As a method it looks like this:
public static SqlException CreateSqlException(string errorMessage, int errorNumber)
{
SqlErrorCollection collection = GetErrorCollection();
SqlError error = GetError(errorNumber, errorMessage);
MethodInfo addMethod = collection.GetType().GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance);
addMethod.Invoke(collection, new object[] { error });
Type[] types = new Type[] { typeof(string), typeof(SqlErrorCollection) };
object[] parameters = new object[] { errorMessage, collection };
ConstructorInfo constructor = typeof(SqlException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
SqlException exception = (SqlException)constructor.Invoke(parameters);
return exception;
}
And that's it. Putting it neatly together in a class, we've got ourselves a little
SqlExceptionStub
that is very handy for testing certain expected database exceptions. The complete code is below.And that's that!
using System;
using System.Data.SqlClient;
using System.Reflection;
namespace SnowValley.Tools.Tests.Utility
{
public class SqlExceptionStub
{
public static SqlException CreateSqlException(string errorMessage, int errorNumber)
{
SqlErrorCollection collection = GetErrorCollection();
SqlError error = GetError(errorNumber, errorMessage);
MethodInfo addMethod = collection.GetType().GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Instance);
addMethod.Invoke(collection, new object[] { error });
Type[] types = new Type[] { typeof(string), typeof(SqlErrorCollection) };
object[] parameters = new object[] { errorMessage, collection };
ConstructorInfo constructor = typeof(SqlException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
SqlException exception = (SqlException)constructor.Invoke(parameters);
return exception;
}
private static SqlError GetError(int errorCode, string message)
{
object[] parameters = new object[] { errorCode, (byte)0, (byte)10, "server", message, "procedure", 0 };
Type[] types = new Type[] { typeof(int), typeof(byte), typeof(byte), typeof(string), typeof(string), typeof(string), typeof(int) };
ConstructorInfo constructor = typeof(SqlError).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
SqlError error = (SqlError)constructor.Invoke(parameters);
return error;
}
private static SqlErrorCollection GetErrorCollection()
{
ConstructorInfo constructor = typeof(SqlErrorCollection).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
SqlErrorCollection collection = (SqlErrorCollection)constructor.Invoke(new object[] { });
return collection;
}
}
}
No comments:
Post a Comment