Κάποια στιγμή στη δουλειά χρειάστηκε να αποθηκεύουμε την xml ενός DataSet σε ένα πεδίο στη βάση - κάτι σαν extended properties για το συγκεκριμένο row.
Όταν λοιπόν θέλω να διαβάσω αυτή την xml και να φτιάξω ένα instance του DataSet απο το οποίο προήλθε ( είναι ένα typed DataSet ), πρέπει να διαβάσω αυτό το string ας πούμε απο τη βάση, και να το δώσω στο DataSet μου καλώντας την ReadXml(...).
Η αρχική υλοποίηση χρησιμοποιούσε το παλιό DataAccess App. Block ως εξής:
SqlParameter[] arParms = new
SqlParameter[3];
arParms[0] = new
SqlParameter("@ContractActivityID", SqlDbType.Int);
arParms[0].Value = contractActivityId;
arParms[1] = new
SqlParameter("@DataSetName", SqlDbType.VarChar, 120);
arParms[1].Value = dataSetName;
arParms[2] = new
SqlParameter("@LoadOriginalData", SqlDbType.Bit);
arParms[2].Value = loadOriginal;
string modXml = string.Empty;
if( tran != null )
modXml =
msData.ExecuteScalar(tran, System.Data.CommandType.StoredProcedure,
SPNAME_GetContractModificationData, arParms).ToString();
else
modXml =
msData.ExecuteScalar(CISConfig.DatabaseConnectionString,
System.Data.CommandType.StoredProcedure, SPNAME_GetContractModificationData,
arParms).ToString();
return
modXml;
.. έτσι, έπαιρνε πίσω ολόκληρο το string, και μετά το έδινε στο instance του DataSet για να το διαβάσει.
Τώρα, στην πορεία ανακαλύψαμε οτι αυτό το πράγμα μάλλον δεν ήταν και η καλύτερη υλοποίηση στον κόσμο ... αυτό το string μπορεί να ήταν αρκετά μεγάλο, και θα προτιμούσαμε να διαβάζουμε αυτή την xml σαν .. stream ? ... sequentially ? απο ένα DataReader. Θα προτιμούσα με άλλα λόγια να παίρνς πίσω ένα stream, το οποίο θα έδινα κατευθείαν στη ReadXml(...) χωρίς να μπαίνει στη μέση αυτό το string. Ίσως ήταν καλή η έμπνευση, ίσως και όχι ... αυτός είναι άλλωστε και ο λόγος για το συγκεκριμένο post.
Η δεύτερη υλοποίηση λοιπόν:
try {
if(null!=tran) {
objCmd = new SqlCommand(cmdText, tran.Connection, tran);
} else
{
SqlConnection objConn
= new
SqlConnection(CISConfig.DatabaseConnectionString);
objConn.Open();
objCmd = new SqlCommand(cmdText, objConn);
}
//
ok execute the command now .. as a SequentialReader ...
SqlDataReader reader =
objCmd.ExecuteReader(CommandBehavior.SequentialAccess);
//
hmmm ... now comes the hard part ... I need to create some sort of
//
weird way to create a stream reader or something off this "binary"
data that I'll be reading ...
if(reader.Read()){
int bufferSize = 100; //
Size of the BLOB buffer.
byte[] outbytes = new
byte[bufferSize]; // buffer chunk ...
MemoryStream
bufferStream = new MemoryStream();
// ok, now loop & read from the reader, writing to the
MemoryStream ...
long retval; // The bytes returned from GetBytes.
long startIndex = 0; //
The starting position in the BLOB output.
// Read the bytes into outbyte[] and retain the number of
bytes returned.
retval =
reader.GetBytes(0, startIndex, outbytes, 0, bufferSize);
// Continue reading and writing while there are bytes beyond
the size of the buffer.
while (retval == bufferSize) {
bufferStream.Write(outbytes,
0, bufferSize);
// Reposition the start index to the end of the last buffer
and fill the buffer.
startIndex +=
bufferSize;
retval =
reader.GetBytes(0, startIndex, outbytes, 0, bufferSize);
}
// hmmm .. perhaps the last batch wasn't exactly 100 bytes
long ?
if(retval>0){
bufferStream.Write(outbytes,
0, (int)retval);
}
// cool ... at this point, I've all the data in the
bufferStream, so I don't really need
// the Reader anymore ... so kill
it. AND REWIND THE BUFFER STREAM !!!
reader.Close();
bufferStream.Position = 0;
// and now ... create a StreamReader
to read-in the bufferData & feed the
// DataSet.ReadXml(...) method ...
using(StreamReader xmlReader =
new StreamReader(bufferStream,
System.Text.UnicodeEncoding.Unicode, true)){
try {
dataSetInstance.ReadXml(xmlReader);
}finally {
// Make sure I kill the bufferStream, otherwise it might
// consume memory for ever - or at least for a long time
...
bufferStream.Close();
bufferStream
= null;
}
}
}
}
finally{
// Hopefully this will close the connection as well if
required ...
if(null!=objCmd){
// what about the connection ??? I wonder ...
if(null==tran){
objCmd.Connection.Close();
}
objCmd.Dispose();
objCmd = null;
}
}
... τώρα, όπως είναι προφανές, δεν γλύτωσα το μέρος του buffering αυτών των δεδομένων, απλώς τώρα είναι σ'ενα memory stream το οποίο γεμίζει λίγο πιο .. ομαλά ... λίγα λίγα δεδομένα, και το σκοτώνω explicitly όταν πλεόν δε μου χρειάζεται.
ΑΛΛΑ ... κέρδισα τίποτα; Ίσως λίγο scalability ? Ίσως όμως και όχι. Και τελικά, δεν έλυσα το βασικό μου πρόβλημα, να ξεφορτωθώ το "buffer" είτε σε string είτε σε MemoryStream.
'Εχει κανείς καμμιά τρίτη ιδέα;;;
Angel
O:]