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 csharp : Sélectionner tout - Visualiser dans une fenêtre à part
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 csharp : Sélectionner tout - Visualiser dans une fenêtre à part
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 csharp : Sélectionner tout - Visualiser dans une fenêtre à part
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 csharp : Sélectionner tout - Visualiser dans une fenêtre à part
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 :
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 xml : Sélectionner tout - Visualiser dans une fenêtre à part
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>
Partager