diff --git a/.editorconfig b/.editorconfig index fc2933b..8a11f4e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,9 +7,9 @@ root = true #### Core EditorConfig Options #### # Indentation and spacing -indent_size = 4 +indent_size = 3 indent_style = tab -tab_width = 4 +tab_width = 3 # New line preferences end_of_line = crlf diff --git a/src/Connected.ServiceModel.Client.Data/DataParameterCollection.cs b/src/Connected.ServiceModel.Client.Data/DataParameterCollection.cs index 6790bac..331aef3 100644 --- a/src/Connected.ServiceModel.Client.Data/DataParameterCollection.cs +++ b/src/Connected.ServiceModel.Client.Data/DataParameterCollection.cs @@ -1,61 +1,125 @@ -using System.Data; +using System.Collections; +using System.Data.Common; namespace Connected.ServiceModel.Client.Data; -internal sealed class DataParameterCollection : List, IDataParameterCollection +internal sealed class DataParameterCollection : DbParameterCollection { - public object this[string parameterName] + private readonly object _syncRoot = new(); + public DataParameterCollection() { - get - { - foreach (var parameter in this) - { - if (string.Equals(parameter.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) - return parameter; - } + Items = new(); + } - throw new NullReferenceException(nameof(IDbDataParameter)); - } - set - { - if (value is not IDbDataParameter parameter) - throw new InvalidCastException(nameof(IDbDataParameter)); + private List Items { get; } + public override int Count => Items.Count; - var idx = IndexOf(parameterName); + public override object SyncRoot => _syncRoot; - if (idx < 0) - throw new NullReferenceException(nameof(IDbDataParameter)); + public override int Add(object value) + { + if (value is not DbParameter parameter) + throw new InvalidCastException(nameof(DbParameter)); - this[idx] = parameter; - } + Items.Add(parameter); + + return Items.Count - 1; + } + + public override void AddRange(Array values) + { + foreach (var item in values) + Add(item); + } + + public override void Clear() + { + Items.Clear(); } - public bool Contains(string parameterName) + public override bool Contains(object value) { - foreach (var parameter in this) + return Items.Contains(value); + } + + public override bool Contains(string value) + { + return Items.FirstOrDefault(f => string.Equals(f.ParameterName, value, StringComparison.OrdinalIgnoreCase)) is not null; + } + + public override void CopyTo(Array array, int index) + { + foreach (var item in array) { - if (string.Equals(parameter.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) - return true; + Insert(index, item); + index++; } + } - return false; + public override IEnumerator GetEnumerator() + { + return Items.GetEnumerator(); } - public int IndexOf(string parameterName) + public override int IndexOf(object value) { - for (var i = 0; i < Count; i++) - { - var current = this[i]; + if (value is not DbParameter parameter) + throw new InvalidCastException(nameof(DbParameter)); - if (string.Equals(current.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) - return i; - } + return Items.IndexOf(parameter); + } + + public override int IndexOf(string parameterName) + { + if (Items.FirstOrDefault(f => string.Equals(f.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) is DbParameter parameter) + return Items.IndexOf(parameter); return -1; } - public void RemoveAt(string parameterName) + public override void Insert(int index, object value) + { + if (value is not DbParameter parameter) + throw new InvalidCastException(nameof(DbParameter)); + + Items.Insert(index, parameter); + } + + public override void Remove(object value) + { + if (value is not DbParameter parameter) + throw new InvalidCastException(nameof(DbParameter)); + + Items.Remove(parameter); + } + + public override void RemoveAt(int index) + { + Items.RemoveAt(index); + } + + public override void RemoveAt(string parameterName) + { + if (Items.FirstOrDefault(f => string.Equals(f.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)) is DbParameter parameter) + Items.Remove(parameter); + } + + protected override DbParameter GetParameter(int index) + { + return Items[index]; + } + + protected override DbParameter GetParameter(string parameterName) + { + return Items.First(f => string.Equals(f.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase)); + } + + protected override void SetParameter(int index, DbParameter value) + { + Items[index] = value; + } + + protected override void SetParameter(string parameterName, DbParameter value) { - if (this[parameterName] is IDbDataParameter target) - Remove(target); + Items[IndexOf(parameterName)] = value; } } diff --git a/src/Connected.ServiceModel.Client.Data/TableConnection.cs b/src/Connected.ServiceModel.Client.Data/TableConnection.cs index d33216e..7ac64e9 100644 --- a/src/Connected.ServiceModel.Client.Data/TableConnection.cs +++ b/src/Connected.ServiceModel.Client.Data/TableConnection.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Diagnostics.CodeAnalysis; using Connected.ServiceModel.Client.Data.Remote; namespace Connected.ServiceModel.Client.Data; @@ -18,6 +19,7 @@ internal sealed class TableConnection : IDbConnection /// /// The connection string (URL) used when performing the requests. /// + [AllowNull] public string ConnectionString { get; set; } /// /// The request timeout in seconds. diff --git a/src/Connected.ServiceModel.Client.Data/TableDataCommand.cs b/src/Connected.ServiceModel.Client.Data/TableDataCommand.cs index 74ea10b..cfa634d 100644 --- a/src/Connected.ServiceModel.Client.Data/TableDataCommand.cs +++ b/src/Connected.ServiceModel.Client.Data/TableDataCommand.cs @@ -1,64 +1,96 @@ using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using System.Text; using Connected.ServiceModel.Client.Data.Remote; namespace Connected.ServiceModel.Client.Data; -internal sealed class TableDataCommand : IDbCommand +internal sealed class TableDataCommand : DbCommand { public TableDataCommand(RemoteTableService tables) { Parameters = new DataParameterCollection(); Tables = tables; } - public string CommandText { get; set; } - public int CommandTimeout { get; set; } - public CommandType CommandType { get; set; } - public IDbConnection? Connection { get; set; } - public IDataParameterCollection Parameters { get; } - - public IDbTransaction? Transaction { get; set; } - public UpdateRowSource UpdatedRowSource { get; set; } + public new DataParameterCollection Parameters { get; } public RemoteTableService Tables { get; } + [AllowNull] + public override string CommandText { get; set; } + public override int CommandTimeout { get; set; } = 120; + public override CommandType CommandType { get; set; } = CommandType.Text; + protected override DbConnection? DbConnection { get; set; } + protected override DbParameterCollection DbParameterCollection => Parameters; + protected override DbTransaction? DbTransaction { get; set; } + public override bool DesignTimeVisible { get; set; } + public override UpdateRowSource UpdatedRowSource { get; set; } + + protected override DbParameter CreateDbParameter() + { + return new TableDataParameter(); + } - public void Cancel() + public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) { + await Tables.Update(ParseCommandText()); + return 0; } - public IDbDataParameter CreateParameter() + public override int ExecuteNonQuery() { - return new TableDataParameter(); + AsyncUtils.RunSync(() => Tables.Update(ParseCommandText())); + + return 0; } - public void Dispose() + protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { + return new TableDataReader(await Tables.Query(ParseCommandText())); + } + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + return new TableDataReader(AsyncUtils.RunSync(() => Tables.Query(ParseCommandText()))); } - public int ExecuteNonQuery() + private string ParseCommandText() { - AsyncUtils.RunSync(() => Tables.Update(CommandText)); + var text = new StringBuilder(CommandText); - return 0; + foreach (IDbDataParameter parameter in Parameters) + text = text.Replace(parameter.ParameterName, ParseValue(parameter)); + + return text.ToString(); } - public IDataReader ExecuteReader() + private static string? ParseValue(IDbDataParameter parameter) { - return new TableDataReader(AsyncUtils.RunSync(() => Tables.Query(CommandText))); + if (parameter.Value is null || parameter.Value == DBNull.Value) + return "null"; + + return parameter.DbType switch + { + DbType.AnsiString or DbType.String or DbType.AnsiStringFixedLength or DbType.StringFixedLength or DbType.Xml => $"'{parameter.Value}'", + _ => parameter.Value.ToString(), + }; } - public IDataReader ExecuteReader(CommandBehavior behavior) + public override void Cancel() { - return new TableDataReader(AsyncUtils.RunSync(() => Tables.Query(CommandText))); } - public object? ExecuteScalar() + public override object? ExecuteScalar() { - throw new NotImplementedException(); + var reader = ExecuteReader(); + + if (reader.Read()) + return reader[0]; + + return null; } - public void Prepare() + public override void Prepare() { - throw new NotImplementedException(); } } diff --git a/src/Connected.ServiceModel.Client.Data/TableDataConnection.cs b/src/Connected.ServiceModel.Client.Data/TableDataConnection.cs index c765fd6..570ab25 100644 --- a/src/Connected.ServiceModel.Client.Data/TableDataConnection.cs +++ b/src/Connected.ServiceModel.Client.Data/TableDataConnection.cs @@ -1,8 +1,8 @@ using System.Data; +using System.Data.Common; using Connected.Annotations; using Connected.Data.Storage; using Connected.ServiceModel.Client.Data.Remote; -using Microsoft.Data.SqlClient; namespace Connected.ServiceModel.Client.Data; @@ -19,7 +19,7 @@ internal sealed class TableDataConnection : DatabaseConnection { if (cmd.Parameters.Count > 0) { - foreach (SqlParameter i in cmd.Parameters) + foreach (DbParameter i in cmd.Parameters) i.Value = DBNull.Value; return; @@ -30,7 +30,7 @@ internal sealed class TableDataConnection : DatabaseConnection foreach (var i in command.Operation.Parameters) { - cmd.Parameters.Add(new SqlParameter + cmd.Parameters.Add(new TableDataParameter { ParameterName = i.Name, DbType = i.Type, @@ -39,17 +39,17 @@ internal sealed class TableDataConnection : DatabaseConnection } } - protected override object GetParameterValue(IDbCommand command, string parameterName) + protected override object? GetParameterValue(IDbCommand command, string parameterName) { - if (command is SqlCommand cmd) + if (command is TableDataCommand cmd) return cmd.Parameters[parameterName].Value; return null; } - protected override void SetParameterValue(IDbCommand command, string parameterName, object value) + protected override void SetParameterValue(IDbCommand command, string parameterName, object? value) { - if (command is SqlCommand cmd) + if (command is TableDataCommand cmd) cmd.Parameters[parameterName].Value = value; } diff --git a/src/Connected.ServiceModel.Client.Data/TableDataParameter.cs b/src/Connected.ServiceModel.Client.Data/TableDataParameter.cs index 64a7dc3..b7d9ffe 100644 --- a/src/Connected.ServiceModel.Client.Data/TableDataParameter.cs +++ b/src/Connected.ServiceModel.Client.Data/TableDataParameter.cs @@ -1,16 +1,20 @@ using System.Data; +using System.Data.Common; namespace Connected.ServiceModel.Client.Data; -internal class TableDataParameter : IDbDataParameter +internal class TableDataParameter : DbParameter { - public byte Precision { get; set; } - public byte Scale { get; set; } - public int Size { get; set; } - public DbType DbType { get; set; } - public ParameterDirection Direction { get; set; } - public bool IsNullable { get; set; } - public string ParameterName { get; set; } - public string SourceColumn { get; set; } - public DataRowVersion SourceVersion { get; set; } - public object? Value { get; set; } + public override DbType DbType { get; set; } + public override ParameterDirection Direction { get; set; } + public override bool IsNullable { get; set; } + public override string? ParameterName { get; set; } + public override int Size { get; set; } + public override string? SourceColumn { get; set; } + public override bool SourceColumnNullMapping { get; set; } + public override object? Value { get; set; } + + public override void ResetDbType() + { + + } } diff --git a/src/Connected.ServiceModel.Client.Data/TableDataReader.cs b/src/Connected.ServiceModel.Client.Data/TableDataReader.cs index 0145208..e17dd39 100644 --- a/src/Connected.ServiceModel.Client.Data/TableDataReader.cs +++ b/src/Connected.ServiceModel.Client.Data/TableDataReader.cs @@ -1,164 +1,221 @@ -using System.Data; +using System.Collections; +using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Nodes; namespace Connected.ServiceModel.Client.Data; -internal sealed class TableDataReader : IDataReader +internal sealed class TableDataReader : DbDataReader { public TableDataReader(JsonArray items) { Items = items; } - public object this[int i] => throw new NotImplementedException(); + public override object this[int ordinal] => throw new NotImplementedException(); - public object this[string name] => throw new NotImplementedException(); + public override object this[string name] => throw new NotImplementedException(); - public int Depth => throw new NotImplementedException(); + private JsonArray Items { get; } + private int Index { get; set; } = -1; + private JsonObject? Current => Index < 0 ? null : Items[Index] as JsonObject; + public override int Depth => 1; + public override int FieldCount => Current is null ? 0 : Current.Count; - public bool IsClosed => throw new NotImplementedException(); + public override bool HasRows => Items.Any(); - public int RecordsAffected => throw new NotImplementedException(); + public override bool IsClosed => false; - public int FieldCount => throw new NotImplementedException(); + public override int RecordsAffected => Items.Count; - public JsonArray Items { get; } - - public void Close() + public override bool GetBoolean(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public void Dispose() + public override byte GetByte(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public bool GetBoolean(int i) + public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) { - throw new NotImplementedException(); - } + var value = GetValue(ordinal); + var bytes = Convert.FromBase64String(value); - public byte GetByte(int i) - { - throw new NotImplementedException(); + buffer ??= new byte[bufferOffset + length]; + + for (var i = 0; i < length; i++) + buffer[bufferOffset + i] = bytes[dataOffset + i]; + + return length; } - public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) + public override char GetChar(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public char GetChar(int i) + public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) { - throw new NotImplementedException(); + var value = GetValue(ordinal); + var bytes = Convert.FromBase64String(value); + + buffer ??= new char[bufferOffset + length]; + + for (var i = 0; i < length; i++) + buffer[bufferOffset + i] = (char)bytes[dataOffset + i]; + + return length; } - public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) + public override string GetDataTypeName(int ordinal) { - throw new NotImplementedException(); + /* + * TODO: resolve appropriate type + */ + return typeof(string).Name; } - public IDataReader GetData(int i) + public override DateTime GetDateTime(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public string GetDataTypeName(int i) + public override decimal GetDecimal(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public DateTime GetDateTime(int i) + public override double GetDouble(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public decimal GetDecimal(int i) + public override IEnumerator GetEnumerator() { - throw new NotImplementedException(); + return Items.GetEnumerator(); } - public double GetDouble(int i) + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] + public override Type GetFieldType(int ordinal) { - throw new NotImplementedException(); + /* + * TODO: resolve appropriate type + */ + return typeof(string); } - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] - public Type GetFieldType(int i) + public override float GetFloat(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public float GetFloat(int i) + public override Guid GetGuid(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public Guid GetGuid(int i) + public override short GetInt16(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public short GetInt16(int i) + public override int GetInt32(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public int GetInt32(int i) + public override long GetInt64(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public long GetInt64(int i) + public override string GetName(int ordinal) { - throw new NotImplementedException(); + if (Current is null) + throw new NullReferenceException(); + + if (Current.ElementAt(ordinal).Key is string key) + return key; + + throw new NullReferenceException(); } - public string GetName(int i) + public override int GetOrdinal(string name) { - throw new NotImplementedException(); + if (Current is null) + throw new NullReferenceException(); + + for (var i = 0; i < Current.Count; i++) + { + if (string.Equals(Current.ElementAt(i).Key, name, StringComparison.OrdinalIgnoreCase)) + return i; + } + + return -1; } - public int GetOrdinal(string name) + public override string GetString(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public DataTable? GetSchemaTable() + public override object GetValue(int ordinal) { - throw new NotImplementedException(); + return GetValue(ordinal); } - public string GetString(int i) + public override int GetValues(object[] values) { - throw new NotImplementedException(); + if (Current is null) + throw new NullReferenceException(); + + for (var i = 0; i < Current.Count; i++) + { + var value = Current.ElementAt(i); + + if (value.Value is not null) + values[i] = value.Value.AsValue().GetValue(); + } + + return Current.Count; } - public object GetValue(int i) + public override bool IsDBNull(int ordinal) { - throw new NotImplementedException(); + return false; } - public int GetValues(object[] values) + public override bool NextResult() { - throw new NotImplementedException(); + return false; } - public bool IsDBNull(int i) + public override bool Read() { - throw new NotImplementedException(); + if (Items.Count >= Index + 1) + { + Index++; + + return true; + } + + return false; } - public bool NextResult() + private T GetValue(int ordinal) { - throw new NotImplementedException(); + return GetCurrentValue(ordinal).GetValue(); } - public bool Read() + private JsonValue GetCurrentValue(int ordinal) { - //var items = AsyncUtils.RunSync(() => Service.Query(CommandText)); + if (Current is null) + throw new NullReferenceException(); - return false; + if (Current.ElementAt(ordinal).Value is not JsonValue value) + throw new NullReferenceException(); + + return value; } } diff --git a/src/Connected.ServiceModel.Client.Data/TableReader.cs b/src/Connected.ServiceModel.Client.Data/TableReader.cs index e395ca6..5cb120d 100644 --- a/src/Connected.ServiceModel.Client.Data/TableReader.cs +++ b/src/Connected.ServiceModel.Client.Data/TableReader.cs @@ -67,7 +67,7 @@ internal sealed class TableReader : TableCommand, IStorageReader public async Task OpenReader() { if (Connection is null) - return default; + throw new NullReferenceException(nameof(Connection)); return await Connection.OpenReader(this); } diff --git a/src/Connected.ServiceModel.Client/Net/ConnectedServerOps.cs b/src/Connected.ServiceModel.Client/Net/ConnectedServerOps.cs index f6f64d3..98753cc 100644 --- a/src/Connected.ServiceModel.Client/Net/ConnectedServerOps.cs +++ b/src/Connected.ServiceModel.Client/Net/ConnectedServerOps.cs @@ -2,9 +2,9 @@ namespace Connected.ServiceModel.Client.Net; -internal sealed class SelectUrl : ServiceFunction +internal sealed class SelectUrl : ServiceFunction { - private const string Root = "https://localhost:61599";//"https://connected.tompit.com"; + private const string Root = "https://localhost:7069";//"https://connected.tompit.com"; protected override async Task OnInvoke() { await Task.CompletedTask;