1 pièce(s) jointe(s)
Problème datatable.Load() sur un IDataReader custom
Bonjour,
Pour faire suite à mon message précédent à propos d'une implémentation custo de IDbConnection, j'en suis maintenant à IDataReader.
Si une âme charitable souhaite se pencher dessus, voici le code complet :
XmlConnection.cs
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
namespace AZToolBox.Xml
{
public class XmlConnection : IDbConnection
{
private bool open = false;
internal XmlReader? Reader;
private bool disposedValue;
public XmlConnection() : this(null)
{
}
public XmlConnection(string? connectionString)
{
ConnectionString = connectionString;
}
[AllowNull]
public string ConnectionString { get; set; }
/// <summary>
/// Not used
/// </summary>
public int ConnectionTimeout { get; set; }
public string Database
{
get
{
if (Reader is not null && State == ConnectionState.Open)
{
return Reader.BaseURI;
}
else
{
return string.Empty;
}
}
}
public ConnectionState State
{
get
{
if (open)
{
return ConnectionState.Open;
}
else
{
return ConnectionState.Closed;
}
}
}
public IDbTransaction BeginTransaction()
{
throw new NotImplementedException();
}
public IDbTransaction BeginTransaction(IsolationLevel il)
{
throw new NotImplementedException();
}
public void ChangeDatabase(string databaseName)
{
throw new NotImplementedException();
}
public void Close()
{
if (Reader is not null)
{
Reader.Close();
Reader.Dispose();
Reader = null;
}
open = false;
}
public XmlCommand CreateCommand()
{
return new XmlCommand(this);
}
IDbCommand IDbConnection.CreateCommand() => CreateCommand();
public void Open()
{
if (ConnectionString is null)
{
throw new Exception("Pas de chaîne de connexion spécifiée !");
}
Reader = XmlReader.Create(ConnectionString);
open = true;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: supprimer l'état managé (objets managés)
Close();
}
// TODO: libérer les ressources non managées (objets non managés) et substituer le finaliseur
// TODO: affecter aux grands champs une valeur null
disposedValue = true;
}
}
// // TODO: substituer le finaliseur uniquement si 'Dispose(bool disposing)' a du code pour libérer les ressources non managées
// ~XmlConnection()
// {
// // Ne changez pas ce code. Placez le code de nettoyage dans la méthode 'Dispose(bool disposing)'
// Dispose(disposing: false);
// }
public void Dispose()
{
// Ne changez pas ce code. Placez le code de nettoyage dans la méthode 'Dispose(bool disposing)'
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
} |
XmlCommand.cs
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
| using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Xml.XPath;
namespace AZToolBox.Xml
{
public class XmlCommand : IDbCommand
{
private XmlConnection _connection;
private CommandType _commandType;
private bool disposedValue;
private string _query = string.Empty;
private List<ColumnDefinition> _columns = [];
internal XmlCommand(XmlConnection connection)
{
_connection = connection;
_commandType = CommandType.Text;
}
[AllowNull]
public string CommandText
{
get
{
return $"select {string.Join(',', _columns)} from {_query}";
}
set
{
_query = string.Empty;
_columns = [];
if (value is null)
{
throw new ArgumentNullException("value");
}
int s = value.IndexOf("select ");
int f = value.IndexOf(" from ");
if (s != -1 && f != -1)
{
string columns = value.Substring(s + "select ".Length, f - (s + "select ".Length));
_columns.AddRange(columns.Split([',']).Select(c => (ColumnDefinition)c));
_query = value.Substring(f + " from ".Length);
}
else if (value.Length > 0)
{
throw new InvalidExpressionException(value);
}
}
}
public int CommandTimeout { get; set; }
public CommandType CommandType
{
get
{
return _commandType;
}
set
{
if (value != CommandType.Text)
{
throw new NotImplementedException(value.ToString());
}
else
{
_commandType = value;
}
}
}
public IDbConnection? Connection
{
get
{
return _connection;
}
set
{
XmlConnection? connection = value as XmlConnection;
if (connection is not null)
{
_connection = connection;
}
else
{
throw new ArgumentNullException(nameof(value));
}
}
}
public IDataParameterCollection Parameters => throw new NotImplementedException();
public IDbTransaction? Transaction { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public UpdateRowSource UpdatedRowSource { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public void Cancel()
{
throw new NotImplementedException();
}
public IDbDataParameter CreateParameter()
{
throw new NotImplementedException();
}
public int ExecuteNonQuery()
{
throw new NotImplementedException();
}
public XmlDataReader ExecuteReader() => ExecuteReader(CommandBehavior.Default);
public XmlDataReader ExecuteReader(CommandBehavior behavior)
{
if (_connection.State != ConnectionState.Open)
{
throw new Exception("La connection doit être ouverte !");
}
XPathDocument _document = new XPathDocument(_connection.Reader!);
XPathNavigator _navigator = _document.CreateNavigator();
XPathNodeIterator _iterator = _navigator.Select(_query);
return new XmlDataReader(_iterator, _columns);
}
IDataReader IDbCommand.ExecuteReader() => ExecuteReader(CommandBehavior.Default);
IDataReader IDbCommand.ExecuteReader(CommandBehavior behavior) => ExecuteReader(behavior);
public object? ExecuteScalar()
{
XmlDataReader _reader = (ExecuteReader() as XmlDataReader)!;
_reader.Read();
object? res = _reader.GetValue(0);
_reader.Close();
return res;
}
/// <summary>
/// Does nothing
/// </summary>
public void Prepare()
{
// Do nothing
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: supprimer l'état managé (objets managés)
}
// TODO: libérer les ressources non managées (objets non managés) et substituer le finaliseur
// TODO: affecter aux grands champs une valeur null
disposedValue = true;
}
}
// // TODO: substituer le finaliseur uniquement si 'Dispose(bool disposing)' a du code pour libérer les ressources non managées
// ~XmlCommand()
// {
// // Ne changez pas ce code. Placez le code de nettoyage dans la méthode 'Dispose(bool disposing)'
// Dispose(disposing: false);
// }
public void Dispose()
{
// Ne changez pas ce code. Placez le code de nettoyage dans la méthode 'Dispose(bool disposing)'
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
internal class ColumnDefinition
{
public string Name { get; set; }
public string Value { get; set; }
public ColumnDefinition(string definition)
{
int a = definition.IndexOf(" as ");
if (a != -1)
{
Value = definition.Substring(0, a).Trim();
Name = definition.Substring(a + " as ".Length).Trim();
}
else
{
Value = definition.Trim();
Name = Value;
}
}
public override string ToString()
{
return $"{Value} as {Name}";
}
public static implicit operator string(ColumnDefinition c)
{
return c.ToString();
}
public static implicit operator ColumnDefinition(string c)
{
return new ColumnDefinition(c);
}
}
} |
Et enfin XmlDataReader.cs
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
| using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.XPath;
namespace AZToolBox.Xml
{
public class XmlDataReader : IDataReader
{
private bool disposedValue;
private bool closed = true;
private XPathNodeIterator _iterator;
private List<ColumnDefinition> _columns;
internal XmlDataReader(XPathNodeIterator iterator, List<ColumnDefinition> columns)
{
_iterator = iterator;
_columns = columns;
closed = false;
}
public object this[int i] => GetValue(i);
public object this[string name] => GetValue(GetOrdinal(name));
/// <summary>
/// Always zero
/// </summary>
public int Depth => 0;
public bool IsClosed => closed;
/// <summary>
/// Always zero
/// </summary>
public int RecordsAffected => 0;
public int FieldCount => _columns.Count;
public void Close() => closed = true;
public bool GetBoolean(int i) => bool.Parse(GetString(i));
public byte GetByte(int i) => byte.Parse(GetString(i));
public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length)
{
int l = -1;
if (buffer is not null)
{
byte[] bytes = Encoding.UTF8.GetBytes(GetString(i), (int)fieldOffset, length);
bytes.CopyTo(buffer!, bufferoffset);
l = bytes.Length;
}
return l;
}
public char GetChar(int i)
{
string s = GetString(i);
if (s.Length > 1)
{
throw new InvalidCastException();
}
else
{
return GetString(i)[0];
}
}
public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length)
{
int l = -1;
if (buffer is not null)
{
char[] chars = GetString(i).ToCharArray((int)fieldoffset, length);
chars.CopyTo(buffer!, bufferoffset);
l = chars.Length;
}
return l;
}
public IDataReader GetData(int i)
{
throw new NotImplementedException();
}
public string GetDataTypeName(int i) => DataType.Text.ToString();
public DateTime GetDateTime(int i) => DateTime.Parse(GetString(i));
public decimal GetDecimal(int i) => decimal.Parse(GetString(i));
public double GetDouble(int i) => double.Parse(GetString(i));
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)]
public Type GetFieldType(int i) => typeof(string);
public float GetFloat(int i) => float.Parse(GetString(i));
public Guid GetGuid(int i) => Guid.Parse(GetString(i));
public short GetInt16(int i) => short.Parse(GetString(i));
public int GetInt32(int i) => int.Parse(GetString(i));
public long GetInt64(int i) => long.Parse(GetString(i));
public string GetName(int i) => _columns[i].Name;
public int GetOrdinal(string name) => _columns.FindIndex(a => a.Name == name);
public DataTable? GetSchemaTable()
{
return null;
/*
DataTable schemaTable = new();
schemaTable.Columns.Add("ColumnName", typeof(string));
schemaTable.Columns.Add("ColumnOrdinal", typeof(int));
schemaTable.Columns.Add("DataType", typeof(Type));
schemaTable.Columns.Add("IsReadOnly", typeof(bool));
for (int i = 0; i < FieldCount; i++)
{
DataRow row = schemaTable.NewRow();
row["ColumnName"] = GetName(i);
row["ColumnOrdinal"] = i;
row["DataType"] = GetFieldType(i);
row["IsReadOnly"] = true;
schemaTable.Rows.Add(row);
}
return schemaTable;
*/
}
public string GetString(int i)
{
XPathNavigator? nav = _iterator.Current!.SelectSingleNode(_columns[i].Value);
if (nav is null)
{
return "null";
}
else if (nav.IsEmptyElement)
{
return string.Empty;
}
else if (nav.IsNode)
{
return nav.Value;
}
throw new NotImplementedException();
}
public object GetValue(int i)
{
string s = GetString(i);
if (s == "null")
{
return DBNull.Value;
}
else
{
return s;
}
}
public int GetValues(object[] values)
{
int read = 0;
for (int i = 0; i < _columns.Count && i < values.Length; i++)
{
values[i] = GetValue(i);
read++;
}
return read;
}
public bool IsDBNull(int i) => GetString(i) == "null";
public bool NextResult() => false;
public bool Read() => _iterator.MoveNext();
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: supprimer l'état managé (objets managés)
}
// TODO: libérer les ressources non managées (objets non managés) et substituer le finaliseur
// TODO: affecter aux grands champs une valeur null
disposedValue = true;
}
}
// // TODO: substituer le finaliseur uniquement si 'Dispose(bool disposing)' a du code pour libérer les ressources non managées
// ~XmlDataReader()
// {
// // Ne changez pas ce code. Placez le code de nettoyage dans la méthode 'Dispose(bool disposing)'
// Dispose(disposing: false);
// }
public void Dispose()
{
// Ne changez pas ce code. Placez le code de nettoyage dans la méthode 'Dispose(bool disposing)'
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
} |
Le code tel quel fonctionne plutôt bien.
Il y a plein de choses qui pourraient être améliorées, notamment la gestion des types en se basant sur un XSD, etc.
Ce qui me pose problème ici, c'est que lorsque je tente d'utiliser mon code :
Form1.cs
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| using AZToolBox.Xml;
using System.Data;
namespace WinFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
using (XmlConnection cnx = new("test.xml"))
{
cnx.Open();
XmlCommand cmd = cnx.CreateCommand();
cmd.CommandText = "select @firstname as Nom, @lastname as Prenom, @birth as Age from /personnes/personne[substring(@birth, 1, 4) > '2024' or (substring(@birth, 1, 4) = '2024' and substring(@birth, 6, 2) > '03')]";
XmlDataReader xmlDataReader = cmd.ExecuteReader();
DataTable dataTable = new();
dataTable.Load(xmlDataReader);
dataGridView1.DataSource = dataTable;
cnx.Close();
}
}
}
} |
Le dataTable.Load(xmlDataReader); appelle XmlDataReader.GetSchemaTable() dont le code est en commentaire (remplacé par un return null) ci-dessus, XmlDataReader.cs ligne 121 et suivantes.
Le code en commentaire est celui que m'a généré Copilot, et que j'ai tenté de compléter avec ce que j'ai trouvé dans la documentation de Systel.Data.SqlClient.GetSchemaTable()
https://learn.microsoft.com/en-us/do...ckFrom=net-9.0
Pourtant si je réactive le code de la méthode, je me retrouve avec l'erreur suivante :
Pièce jointe 663850
Je n'arrive pas à obtenir le code de la fonction DataTable.Load() pour avoir plus de détails sur l'origine de l'erreur.
Dans mon code que j'ai aucun argument qui s'appelle Arg_ParamName_Name ou column.
J'aimerais pourtant avoir un schema propre, ce qui me permettra dans le future d'avoir une meilleure gestion des types (actuellement tout est géré en string, ce qui fait le job avec du XML...)
-- Edit : ah, oui, et un exemple de fichier xml
test.xml
Code:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="utf-8" ?>
<personnes>
<personne firstname="Toto1" lastname="Titi1" birth="2024-01-01"/>
<personne firstname="Toto2" lastname="Titi2" birth="2024-02-01"/>
<personne firstname="Toto3" lastname="Titi3" birth="2024-03-01"/>
<personne firstname="Toto3" lastname="Titi4" birth="2024-04-01"/>
<personne firstname="Toto4" lastname="Titi5" birth="2024-05-01"/>
<personne firstname="Toto5" lastname="Titi6" birth="2024-06-01"/>
<personne firstname="Toto6" lastname="Titi7" birth="2024-07-01"/>
<personne firstname="Toto7" lastname="Titi8" birth="2024-08-01"/>
</personnes> |