Good question, actually. Unfortunately, one for which I believe there is no good answer. There's actually an active Microsoft Connect ticket about this very issue.
Normally, if you want to stream results and you just need a regular SqlDataReader
, you can use the ExecuteReader
overload that takes a CommandBehavior
, and specifically CommandBehavior.CloseConnection
. If a reader is created using this command behavior, then when you Close
(or Dispose
) the reader, it also closes the underlying connection, so you never have to worry about disposing the SqlConnection
.
Unfortunately, there's no equivalent overload of ExecuteXmlReader
. You have to dispose the SqlConnection
explicitly.
One way around this would be to implement your own XmlReader
descendant, wrapping the real XmlReader
obtained from ExecuteXmlReader
and forcing the connection closed on close.
The basic idea is just to derive from XmlReader
and wrap both the real XmlReader
and the SqlConnection
itself. Something like this:
class SqlXmlReader : XmlReader
{
private SqlConnection connection;
private XmlReader reader;
public SqlXmlReader(SqlCommand cmd)
{
if (cmd == null)
throw new ArgumentNullException("cmd");
this.connection = cmd.Connection;
this.reader = cmd.ExecuteXmlReader();
}
public override void Close()
{
reader.Close();
connection.Close();
}
}
This takes the connection and the reader directly from the SqlCommand
so there's no chance of a connection/reader mismatch. You need to implement the rest of the XmlReader
methods and properties too - it's just a lot of boring method proxying:
public override int AttributeCount
{
get { return reader.AttributeCount; }
}
public override string BaseURI
{
get { return reader.BaseURI; }
}
public override int Depth
{
get { return reader.Depth; }
}
public override bool EOF
{
get { return reader.EOF; }
}
public override string GetAttribute(int i)
{
return reader.GetAttribute(i);
}
public override string GetAttribute(string name, string namespaceURI)
{
return reader.GetAttribute(name, namespaceURI);
}
public override string GetAttribute(string name)
{
return reader.GetAttribute(name);
}
public override bool HasValue
{
get { return reader.HasValue; }
}
public override bool IsEmptyElement
{
get { return reader.IsEmptyElement; }
}
public override string LocalName
{
get { return reader.LocalName; }
}
public override string LookupNamespace(string prefix)
{
return reader.LookupNamespace(prefix);
}
public override bool MoveToAttribute(string name, string ns)
{
return reader.MoveToAttribute(name, ns);
}
public override bool MoveToAttribute(string name)
{
return reader.MoveToAttribute(name);
}
public override bool MoveToElement()
{
return reader.MoveToElement();
}
public override bool MoveToFirstAttribute()
{
return reader.MoveToFirstAttribute();
}
public override bool MoveToNextAttribute()
{
return reader.MoveToNextAttribute();
}
public override XmlNameTable NameTable
{
get { return reader.NameTable; }
}
public override string NamespaceURI
{
get { return reader.NamespaceURI; }
}
public override XmlNodeType NodeType
{
get { return reader.NodeType; }
}
public override string Prefix
{
get { return reader.Prefix; }
}
public override bool Read()
{
return reader.Read();
}
public override bool ReadAttributeValue()
{
return reader.ReadAttributeValue();
}
public override ReadState ReadState
{
get { return reader.ReadState; }
}
public override void ResolveEntity()
{
reader.ResolveEntity();
}
public override string Value
{
get { return reader.Value; }
}
Dull, dull, dull, but it works. This reader will close the connection for you when it's done, same as a SqlDataReader
opened with CommandBehavior.CloseConnection
.
Last thing to do would be to create an extension method to make this easier to use:
public static class SqlExtensions
{
public static XmlReader ExecuteSafeXmlReader(this SqlCommand cmd)
{
return new SqlXmlReader(cmd);
}
}
Once you have this, instead of writing:
XmlReader xr = cmd.ExecuteXmlReader();
You write:
XmlReader xr = cmd.ExecuteSafeXmlReader();
That's it. Now when WCF closes your reader, it will automatically close the underlying connection.
(Disclaimer: This hasn't officially been tested, but I can see no reason why it wouldn't work, unless WCF does not actually close the reader. Be sure to actually test this against a live SQL connection to make sure that it really doesn't leak connections.)