Вообще говоря, вы не пользуетесь достаточным преимуществом оператора использования и обрабатываете все это самостоятельно. К сожалению, вы делаете это неправильно, потому что если у вас есть реализация IDisposable, которая генерирует исключение при вызове Dispose, другие вызовы Dispose не выполняются. Если вы используете оператор using, будут вызваны все реализации IDisposable.Dispose, независимо от того, насколько они вложены.
Давайте сначала пройдемся по потоку LoadTextFromDBToADODBS. Проблема массового в том, что вы делитесь соединением, когда этого не следует делать. Вы должны создать соединение для вашей операции, использовать его, а затем закрыть его. Здесь дело не в этом.
Итак, давайте предположим, что вы создаете свое соединение отдельным методом, например так:
SqlConnection CreateConnection()
{
// Create the connection here and return it.
return ...;
}
Вам также понадобится следующая структура для правильного управления ссылками COM:
struct ComReference<T> : IDisposable where T : class, new()
{
private T reference;
public T Reference { get { return reference; } }
public static ComReference<T> Create()
{
// Create the instance.
ComReference<T> retVal = new ComReference<T>();
// Set the reference.
retVal.reference = new T();
// Return.
return retVal;
}
public ComReference<T> Release()
{
// Create a copy for return.
// Note, this is copied on the stack.
ComReference<T> retVal = this;
// Set this reference to null;
this.reference = null;
// Return the reference.
return retVal;
}
public void Dispose()
{
// If there is a reference, then release.
Marshal.ReleaseComObject(reference);
}
}
Вы хотите управлять своими ссылками COM с этим, чтобы вы освободили их, когда закончите с ними, а не с помощью сборки мусора. COM полагается на детерминированную финализацию, и вы не можете игнорировать это только потому, что находитесь в .NET. Структура выше использует IDisposable (и тот факт, что это структура и нюансы, которые сопровождают ее), чтобы помочь сделать это детерминированным способом.
Параметр типа T
будет типом класса, созданным для взаимодействия COM, в случае потока это будет ADODB.StreamClass.
Ваш LoadTextFromDBToADODBStream тогда выглядит так:
ComReference<StreamClass> LoadTextFromDBToADODBStream(int idParameter,
string parameterName, string sqlString, ref int size)
{
int bytesReturned;
int chunkSize = 65536;
int offSet = 0;
// Create the command.
using (SqlCommand cmd = new SqlCommand())
{
// Set the parameters.
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 0;
cmd.CommandText = sqlString;
// See (1).
using (SqlConnection connection = CreateConnection())
{
// Set the connection on the command.
cmd.Connection = connection;
// Create the parameter and add to the parameters.
SqlParameter cmdParameter = new SqlParameter(
parameterName, idParameter);
cmd.Parameters.Add(cmdParameter);
// Create the reader.
using (SqlDataReader dr = cmd.ExecuteReader(
CommandBehavior.SequentialAccess))
{
dr.Read();
// See (2)
if (!dr.HasRows)
{
// Return an empty instance.
return new ComReference<StreamClass>();
}
// Create the stream here. See (3)
using (ComReference<StreamClass> adoStreamClass =
ComReference<StreamClass>.Create())
{
// Get the stream.
StreamClass adoStream = adoStreamClass.Reference;
// Open the stream.
adoStream.Type = StreamTypeEnum.adTypeText;
adoStream.Open(Type.Missing,
ConnectModeEnum.adModeUnknown,
StreamOpenOptionsEnum.adOpenStreamUnspecified,
"", "");
// Create the byte array.
byte[] byteChunk = new byte[chunkSize];
// See (4)
Encoding readBytes = Encoding.Unicode;
// Cycle.
do
{
bytesReturned = (int)dr.GetBytes(0, offSet,
byteChunk, 0, chunkSize);
size += bytesReturned;
if (bytesReturned > 0)
{
if (bytesReturned < chunkSize)
{
Array.Resize(ref byteChunk,
bytesReturned);
}
adoStream.WriteText(
readBytes.GetString(byteChunk),
StreamWriteEnum.stWriteChar);
adoStream.Flush();
}
offSet += bytesReturned;
} while (bytesReturned == chunkSize);
// Release the reference and return it.
// See (5).
return adoStreamClass.Release();
}
}
}
}
}
Примечания:
- Здесь вы хотите создать соединение. Вы хотите использовать его в операторе использования, потому что хотите быть уверены, что ваши ресурсы очищены, в случае успеха или неудачи.
- Здесь проще замкнуть код и вернуть новый экземпляр
ComReference<StreamClass>
. Когда вы создаете это, он возвращает структуру, которая не имеет ссылки (что вам нужно, в отличие от вызова статического метода Create).
- При вызове статического метода Create вы создаете новый экземпляр ADODB.StreamClass. Вы хотите убедиться, что если что-то пойдет не так, это будет устранено после выпуска (поскольку это реализация интерфейса COM и зависит от детерминированной финализации).
- Нет необходимости создавать новую кодировку UnicodeEncoding. Вы можете просто использовать свойство Unicode в классе Encoding для использования готового экземпляра.
- В вызове release вы устанавливаете в качестве значения поля NULL для экземпляра в текущем стеке и передаете его в
ComReference<StreamClass>
, который возвращается. Таким образом, ссылка StreamClass все еще жива, и когда Dispose вызывается для переменной стека, она не передает эту ссылку ReleaseComObject.
Переход к коду, который вызывает LoadTextFromDBToADODBStream:
// See (1)
using (ComReference<StreamClass> adoStreamClass =
LoadTextFromDBToADODBStream(resultID, "@result_id",
"some sql statement", ref size))
{
// Set to the class instance. See (2)
StreamClass adoStream = adoStreamClass.Reference;
if (adoStream.Size == 0)
{
success = false;
}
else
{
adoStream.Position = 0;
DataTable table = new DataTable();
// See (3)
using (ComReference<RecordsetClass> rsClass =
ComReference<RecordsetClass>.Create())
{
Recordset rs = rsClass.Reference;
rs.Open(adoStream, Type.Missing, CursorTypeEnum.adOpenStatic,
LockTypeEnum.adLockBatchOptimistic, -1);
if (adoStream != null)
{
adoStream.Close();
adoStream = null;
}
source.SourceRows = rs.RecordCount;
table.TableName = "Source";
source.Dataset = new DataSet();
source.Dataset.Tables.Add(table);
// See (4)
using (OleDbDataAdapter adapter = new OleDbDataAdapter())
{
adapter.MissingSchemaAction =
MissingSchemaAction.AddWithKey;
adapter.Fill(source.Dataset.Tables[0], rs);
}
}
}
}
- Это будет получать возвращаемое значение вызова Release в LoadTextFromDBToADODBStream. Он будет содержать действующую ссылку на созданный там ADODB.Stream, и оператор using будет гарантировать, что он очищен после оставления области действия.
- Как и раньше, это облегчает обращение к прямой ссылке, вместо того, чтобы всегда вызывать
adoStreamClass.Reference.<method>
- Используя другую ссылку, на этот раз
ComReference<RecordsetClass>
.
- Пусть компилятор сделает за вас грязную работу.
Используя больше оператор using, вы можете очистить большую часть кода, который затрудняет чтение. Кроме того, в общем, вы устраняли некоторые проблемы с ресурсами, которые могли возникнуть при исключении, а также обрабатывали реализации COM, которые не были корректно утилизированы.