Хороший вопрос, на самом деле. К сожалению, один, на который я считаю, нет хорошего ответа. На самом деле есть активная Microsoft Connect заявка на эту проблему.
Обычно, если вы хотите транслировать результаты и вам просто нужна обычная SqlDataReader
, вы можете использовать перегрузку ExecuteReader
, которая принимает CommandBehavior
, в частности CommandBehavior.CloseConnection
. Если читатель создан с использованием этого командного поведения, то когда вы Close
(или Dispose
) читатель, он также закрывает базовое соединение, поэтому вам не придется беспокоиться об утилизации SqlConnection
.
К сожалению, эквивалентная перегрузка ExecuteXmlReader
отсутствует. Вы должны распоряжаться SqlConnection
явно.
Одним из способов решения этой проблемы является реализация собственного потомка XmlReader
, оборачивая действительное XmlReader
, полученное из ExecuteXmlReader
, и принудительно закрывая соединение при закрытии.
Основная идея состоит в том, чтобы просто извлечь из XmlReader
и обернуть как действительный XmlReader
, так и сам SqlConnection
. Примерно так:
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();
}
}
Это берет соединение и считыватель непосредственно с SqlCommand
, поэтому нет вероятности несоответствия соединения / считывателя. Вам также необходимо реализовать остальные методы и свойства XmlReader
- это просто скучный метод прокси:
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; }
}
Скучно, скучно, скучно, но это работает. Этот ридер закроет для вас соединение, когда оно будет сделано, так же, как SqlDataReader
, открытый с CommandBehavior.CloseConnection
.
Последнее, что нужно сделать, это создать метод расширения, чтобы упростить его использование:
public static class SqlExtensions
{
public static XmlReader ExecuteSafeXmlReader(this SqlCommand cmd)
{
return new SqlXmlReader(cmd);
}
}
Если у вас есть это, вместо того, чтобы писать:
XmlReader xr = cmd.ExecuteXmlReader();
Вы пишете:
XmlReader xr = cmd.ExecuteSafeXmlReader();
Вот и все. Теперь, когда WCF закроет ваш ридер, он автоматически закроет основное соединение.
(Отказ от ответственности: это официально не проверялось, но я не вижу причин, почему это не сработало бы, если WCF фактически не закрывает читателя. Обязательно проверьте это на работающем соединении SQL, чтобы убедиться, что что он действительно не пропускает соединения.)