Rebase with github repository
This commit is contained in:
parent
e6a3794bad
commit
af01625242
@ -0,0 +1,50 @@
|
||||
using System.Collections.Immutable;
|
||||
using Connected.Entities;
|
||||
using Connected.Entities.Storage;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class AggregatedCommandBuilder<TEntity>
|
||||
{
|
||||
public StorageOperation? Build(TEntity entity)
|
||||
{
|
||||
if (entity is not IEntity ie)
|
||||
throw new ArgumentException(nameof(entity));
|
||||
|
||||
switch (ie.State)
|
||||
{
|
||||
case State.New:
|
||||
return BuildInsert(ie);
|
||||
case State.Default:
|
||||
return BuildUpdate(ie);
|
||||
case State.Deleted:
|
||||
return BuildDelete(ie);
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public List<StorageOperation> Build(ImmutableArray<TEntity> entities)
|
||||
{
|
||||
var result = new List<StorageOperation>();
|
||||
|
||||
foreach (var entity in entities)
|
||||
result.Add(Build(entity));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private StorageOperation? BuildInsert(IEntity entity)
|
||||
{
|
||||
return new InsertCommandBuilder().Build(entity);
|
||||
}
|
||||
|
||||
private StorageOperation? BuildUpdate(IEntity entity)
|
||||
{
|
||||
return new UpdateCommandBuilder().Build(entity);
|
||||
}
|
||||
|
||||
private StorageOperation? BuildDelete(IEntity entity)
|
||||
{
|
||||
return new DeleteCommandBuilder().Build(entity);
|
||||
}
|
||||
}
|
15
Connected.ServiceModel.Client.Data/Boot.cs
Normal file
15
Connected.ServiceModel.Client.Data/Boot.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Connected.Annotations;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
[assembly: MicroService(MicroServiceType.Provider)]
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
|
||||
internal sealed class Boot : Startup
|
||||
{
|
||||
protected override void OnConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<RemoteTableService>();
|
||||
}
|
||||
}
|
206
Connected.ServiceModel.Client.Data/CommandBuilder.cs
Normal file
206
Connected.ServiceModel.Client.Data/CommandBuilder.cs
Normal file
@ -0,0 +1,206 @@
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Connected.Entities;
|
||||
using Connected.Entities.Annotations;
|
||||
using Connected.Entities.Storage;
|
||||
using Connected.Interop;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
|
||||
internal abstract class CommandBuilder
|
||||
{
|
||||
private readonly List<IStorageParameter> _parameters;
|
||||
private readonly List<PropertyInfo> _whereProperties;
|
||||
private List<PropertyInfo> _properties;
|
||||
|
||||
protected CommandBuilder()
|
||||
{
|
||||
_parameters = new List<IStorageParameter>();
|
||||
_whereProperties = new List<PropertyInfo>();
|
||||
|
||||
Text = new StringBuilder();
|
||||
}
|
||||
|
||||
public StorageOperation? Build(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
|
||||
if (TryGetExisting(out StorageOperation? existing))
|
||||
{
|
||||
/*
|
||||
* We need to rebuild an instance since StorageOperation
|
||||
* is immutable
|
||||
*/
|
||||
var result = new StorageOperation
|
||||
{
|
||||
CommandText = existing.CommandText,
|
||||
CommandTimeout = existing.CommandTimeout,
|
||||
CommandType = existing.CommandType,
|
||||
Concurrency = existing.Concurrency
|
||||
};
|
||||
|
||||
if (result.Parameters is null)
|
||||
return result;
|
||||
|
||||
foreach (var parameter in result.Parameters)
|
||||
{
|
||||
if (parameter.Direction == ParameterDirection.Input)
|
||||
{
|
||||
if (ResolveProperty(parameter.Name) is PropertyInfo property)
|
||||
{
|
||||
result.AddParameter(new StorageParameter
|
||||
{
|
||||
Value = GetValue(property),
|
||||
Name = parameter.Name,
|
||||
Type = parameter.Type,
|
||||
Direction = parameter.Direction
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Schema = Entity.GetSchemaAttribute();
|
||||
|
||||
return OnBuild();
|
||||
}
|
||||
|
||||
protected List<PropertyInfo> Properties => _properties ??= GetProperties();
|
||||
|
||||
protected List<PropertyInfo> WhereProperties => _whereProperties;
|
||||
|
||||
protected string CommandText => Text.ToString();
|
||||
|
||||
protected IEntity Entity { get; private set; }
|
||||
|
||||
protected SchemaAttribute Schema { get; private set; }
|
||||
|
||||
private StringBuilder Text { get; set; }
|
||||
|
||||
protected abstract StorageOperation OnBuild();
|
||||
|
||||
protected abstract bool TryGetExisting(out StorageOperation? result);
|
||||
|
||||
protected List<IStorageParameter> Parameters => _parameters;
|
||||
|
||||
protected void Write(string text)
|
||||
{
|
||||
Text.Append(text);
|
||||
}
|
||||
|
||||
protected void Write(char text)
|
||||
{
|
||||
Text.Append(text);
|
||||
}
|
||||
|
||||
protected void WriteLine(string text)
|
||||
{
|
||||
Text.AppendLine(text);
|
||||
}
|
||||
|
||||
protected void Trim()
|
||||
{
|
||||
for (var i = Text.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (!Text[i].Equals(',') && !Text[i].Equals('\n') && !Text[i].Equals('\r') && !Text[i].Equals(' '))
|
||||
break;
|
||||
|
||||
if (i < Text.Length)
|
||||
Text.Length = i;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual List<PropertyInfo> GetProperties()
|
||||
{
|
||||
return Interop.Properties.GetImplementedProperties(Entity);
|
||||
}
|
||||
|
||||
protected static string ColumnName(PropertyInfo property)
|
||||
{
|
||||
var dataMember = property.FindAttribute<MemberAttribute>();
|
||||
|
||||
return dataMember is null || string.IsNullOrEmpty(dataMember.Member) ? property.Name.ToCamelCase() : dataMember.Member;
|
||||
}
|
||||
|
||||
protected static DbType ResolveDbType(PropertyInfo property)
|
||||
{
|
||||
return property.PropertyType.ToDbType();
|
||||
}
|
||||
|
||||
protected object? GetValue(PropertyInfo property)
|
||||
{
|
||||
if (IsNull(property))
|
||||
return "NULL";
|
||||
|
||||
return GetValue(property.GetValue(Entity), property.PropertyType.ToDbType());
|
||||
}
|
||||
|
||||
private static object? GetValue(object value, DbType dbType)
|
||||
{
|
||||
switch (dbType)
|
||||
{
|
||||
case DbType.Binary:
|
||||
if (value is byte[] bytes)
|
||||
return Convert.ToBase64String(bytes);
|
||||
else
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString()));
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsNull(PropertyInfo property)
|
||||
{
|
||||
var result = property.GetValue(Entity);
|
||||
|
||||
if (result is null)
|
||||
return true;
|
||||
|
||||
if (property.GetCustomAttribute<NullableAttribute>() is null)
|
||||
return false;
|
||||
|
||||
var def = Types.GetDefault(property.PropertyType);
|
||||
|
||||
return TypeComparer.Compare(result, def);
|
||||
}
|
||||
|
||||
protected StorageParameter CreateParameter(PropertyInfo property)
|
||||
{
|
||||
return CreateParameter(property, ParameterDirection.Input);
|
||||
}
|
||||
|
||||
protected StorageParameter CreateParameter(PropertyInfo property, ParameterDirection direction)
|
||||
{
|
||||
var columnName = ColumnName(property);
|
||||
var parameterName = $"@{columnName}";
|
||||
|
||||
var parameter = new StorageParameter
|
||||
{
|
||||
Direction = direction,
|
||||
Name = parameterName,
|
||||
Type = ResolveDbType(property),
|
||||
Value = GetValue(property)
|
||||
};
|
||||
|
||||
Parameters.Add(parameter);
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
private PropertyInfo ResolveProperty(string parameterName)
|
||||
{
|
||||
var propertyName = parameterName[1..];
|
||||
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
|
||||
if (Entity.GetType().GetProperty(propertyName.ToPascalCase(), flags) is PropertyInfo property)
|
||||
return property;
|
||||
|
||||
if (Entity.GetType().GetProperty(propertyName, flags) is PropertyInfo raw)
|
||||
return raw;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using Connected.Expressions;
|
||||
using System.Linq.Expressions;
|
||||
using Connected.Expressions;
|
||||
using Connected.Expressions.Formatters;
|
||||
using Connected.Expressions.Languages;
|
||||
using Connected.Interop;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
/// <summary>
|
||||
@ -14,7 +14,12 @@ internal sealed class CqlFormatter : SqlFormatter
|
||||
: base(language)
|
||||
{
|
||||
Context = context;
|
||||
|
||||
HideColumnAliases = true;
|
||||
HideTableAliases = true;
|
||||
UseBracketsInWhere = false;
|
||||
}
|
||||
|
||||
public ExpressionCompilationContext Context { get; }
|
||||
|
||||
public static new string Format(ExpressionCompilationContext context, Expression expression)
|
||||
@ -36,6 +41,11 @@ internal sealed class CqlFormatter : SqlFormatter
|
||||
else
|
||||
base.WriteAggregateName(aggregateName);
|
||||
}
|
||||
|
||||
protected override void WriteTableName(string tableSchema, string tableName)
|
||||
{
|
||||
Write(tableName);
|
||||
}
|
||||
protected override Expression VisitMemberAccess(MemberExpression m)
|
||||
{
|
||||
if (m.Member.DeclaringType == typeof(string))
|
||||
|
@ -37,12 +37,7 @@ internal sealed class CqlLanguage : QueryLanguage
|
||||
|
||||
public override string Quote(string name)
|
||||
{
|
||||
if (name.StartsWith("[") && name.EndsWith("]"))
|
||||
return name;
|
||||
else if (name.Contains('.'))
|
||||
return $"[{string.Join("].[", name.Split(SplitChars, StringSplitOptions.RemoveEmptyEntries))}]";
|
||||
else
|
||||
return $"[{name}]";
|
||||
}
|
||||
|
||||
public override Linguist CreateLinguist(ExpressionCompilationContext context, Translator translator)
|
||||
|
@ -1,8 +1,8 @@
|
||||
using Connected.Expressions;
|
||||
using System.Linq.Expressions;
|
||||
using Connected.Expressions;
|
||||
using Connected.Expressions.Languages;
|
||||
using Connected.Expressions.Translation;
|
||||
using Connected.Expressions.Translation.Rewriters;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
using System.Data;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class DataParameterCollection : List<IDbDataParameter>, IDataParameterCollection
|
||||
{
|
||||
public object this[string parameterName]
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var parameter in this)
|
||||
{
|
||||
if (string.Equals(parameter.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase))
|
||||
return parameter;
|
||||
}
|
||||
|
||||
throw new NullReferenceException(nameof(IDbDataParameter));
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value is not IDbDataParameter parameter)
|
||||
throw new InvalidCastException(nameof(IDbDataParameter));
|
||||
|
||||
var idx = IndexOf(parameterName);
|
||||
|
||||
if (idx < 0)
|
||||
throw new NullReferenceException(nameof(IDbDataParameter));
|
||||
|
||||
this[idx] = parameter;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string parameterName)
|
||||
{
|
||||
foreach (var parameter in this)
|
||||
{
|
||||
if (string.Equals(parameter.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int IndexOf(string parameterName)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
var current = this[i];
|
||||
|
||||
if (string.Equals(current.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void RemoveAt(string parameterName)
|
||||
{
|
||||
if (this[parameterName] is IDbDataParameter target)
|
||||
Remove(target);
|
||||
}
|
||||
}
|
56
Connected.ServiceModel.Client.Data/DeleteCommandBuilder.cs
Normal file
56
Connected.ServiceModel.Client.Data/DeleteCommandBuilder.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Connected.Entities.Annotations;
|
||||
using Connected.Entities.Storage;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class DeleteCommandBuilder : CommandBuilder
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, StorageOperation> _cache;
|
||||
|
||||
static DeleteCommandBuilder()
|
||||
{
|
||||
_cache = new();
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<string, StorageOperation> Cache => _cache;
|
||||
|
||||
protected override StorageOperation OnBuild()
|
||||
{
|
||||
WriteLine($"DELETE [{Schema.Schema}].[{Schema.Name}] (");
|
||||
WriteWhere();
|
||||
|
||||
var result = new StorageOperation { CommandText = CommandText };
|
||||
|
||||
foreach (var parameter in Parameters)
|
||||
result.AddParameter(parameter);
|
||||
|
||||
Cache.TryAdd(Entity.GetType().FullName, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void WriteWhere()
|
||||
{
|
||||
Write("WHERE ");
|
||||
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<PrimaryKeyAttribute>() is not null)
|
||||
{
|
||||
var columnName = ColumnName(property);
|
||||
|
||||
CreateParameter(property);
|
||||
|
||||
Write($"{ColumnName} = @{ColumnName}");
|
||||
}
|
||||
}
|
||||
|
||||
Write(";");
|
||||
}
|
||||
|
||||
protected override bool TryGetExisting(out StorageOperation? result)
|
||||
{
|
||||
return Cache.TryGetValue(Entity.GetType().FullName, out result);
|
||||
}
|
||||
}
|
67
Connected.ServiceModel.Client.Data/InsertCommandBuilder.cs
Normal file
67
Connected.ServiceModel.Client.Data/InsertCommandBuilder.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Connected.Entities.Annotations;
|
||||
using Connected.Entities.Storage;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class InsertCommandBuilder : CommandBuilder
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, StorageOperation> _cache;
|
||||
|
||||
static InsertCommandBuilder()
|
||||
{
|
||||
_cache = new();
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<string, StorageOperation> Cache => _cache;
|
||||
|
||||
protected override StorageOperation OnBuild()
|
||||
{
|
||||
WriteLine($"INSERT [{Schema.Schema}].[{Schema.Name}] (");
|
||||
|
||||
WriteColumns();
|
||||
WriteLine(")");
|
||||
Write("VALUES (");
|
||||
WriteValues();
|
||||
WriteLine(");");
|
||||
|
||||
var result = new StorageOperation { CommandText = CommandText };
|
||||
|
||||
foreach (var parameter in Parameters)
|
||||
result.AddParameter(parameter);
|
||||
|
||||
Cache.TryAdd(Entity.GetType().FullName, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void WriteColumns()
|
||||
{
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
CreateParameter(property);
|
||||
|
||||
Write($"{ColumnName(property)}, ");
|
||||
}
|
||||
|
||||
Trim();
|
||||
}
|
||||
|
||||
private void WriteValues()
|
||||
{
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<PrimaryKeyAttribute>() is not null)
|
||||
continue;
|
||||
|
||||
Write($"@{ColumnName(property)}, ");
|
||||
}
|
||||
|
||||
Trim();
|
||||
}
|
||||
|
||||
protected override bool TryGetExisting(out StorageOperation? result)
|
||||
{
|
||||
return Cache.TryGetValue(Entity.GetType().FullName, out result);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Remote;
|
||||
internal sealed class RemoteTableColumn
|
||||
{
|
||||
[Required, MaxLength(128)]
|
||||
public string Name { get; set; } = default!;
|
||||
[Required, MaxLength(128)]
|
||||
public string DataType { get; set; } = default!;
|
||||
public bool IsPartitionKey { get; set; }
|
||||
public bool IsPrimaryKey { get; set; }
|
||||
}
|
112
Connected.ServiceModel.Client.Data/Remote/RemoteTableService.cs
Normal file
112
Connected.ServiceModel.Client.Data/Remote/RemoteTableService.cs
Normal file
@ -0,0 +1,112 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Nodes;
|
||||
using Connected.Net;
|
||||
using Connected.ServiceModel.Client.Net;
|
||||
using Connected.ServiceModel.Client.Subscription;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Remote;
|
||||
|
||||
internal sealed class RemoteTableService
|
||||
{
|
||||
//TODO:load it from config or environment.
|
||||
private const string AccessToken = "Temp";
|
||||
public RemoteTableService(IHttpService http, IConnectedServer server, ISubscriptionService subscription)
|
||||
{
|
||||
Http = http;
|
||||
Server = server;
|
||||
Subscription = subscription;
|
||||
}
|
||||
|
||||
private IHttpService Http { get; }
|
||||
private IConnectedServer Server { get; }
|
||||
private ISubscriptionService Subscription { get; }
|
||||
private int SubscriptionId { get; set; }
|
||||
|
||||
public async Task<JsonArray> Query(string commandText)
|
||||
{
|
||||
await Initialize();
|
||||
|
||||
var url = await Server.SelectUrl(new ConnectedServerUrlArgs { Kind = ConnectedUrlKind.TableStorage });
|
||||
|
||||
if (await Http.Post<JsonArray>($"{url}/query", new QueryTableArgs
|
||||
{
|
||||
CommandText = commandText,
|
||||
AccessToken = AccessToken,
|
||||
Subscription = SubscriptionId
|
||||
}) is not JsonArray result)
|
||||
return new JsonArray();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ImmutableList<RemoteTableColumn>> QueryColumns(string tableName)
|
||||
{
|
||||
await Initialize();
|
||||
|
||||
var url = await Server.SelectUrl(new ConnectedServerUrlArgs { Kind = ConnectedUrlKind.TableStorage });
|
||||
|
||||
if (await Http.Post<List<RemoteTableColumn>>($"{url}/queryColumns", new TableSchemaArgs
|
||||
{
|
||||
TableName = tableName,
|
||||
AccessToken = AccessToken,
|
||||
Subscription = SubscriptionId
|
||||
}) is not List<RemoteTableColumn> result)
|
||||
return ImmutableList<RemoteTableColumn>.Empty;
|
||||
|
||||
return result.ToImmutableList();
|
||||
}
|
||||
|
||||
public async Task<bool> TableExists(string tableName)
|
||||
{
|
||||
await Initialize();
|
||||
|
||||
var url = await Server.SelectUrl(new ConnectedServerUrlArgs { Kind = ConnectedUrlKind.TableStorage });
|
||||
|
||||
return await Http.Post<bool>($"{url}/tableExists", new TableSchemaArgs
|
||||
{
|
||||
TableName = tableName,
|
||||
AccessToken = AccessToken,
|
||||
Subscription = SubscriptionId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Update(string commandText)
|
||||
{
|
||||
await Initialize();
|
||||
|
||||
var url = await Server.SelectUrl(new ConnectedServerUrlArgs { Kind = ConnectedUrlKind.TableStorage });
|
||||
|
||||
await Http.Post($"{url}/update", new UpdateTableArgs
|
||||
{
|
||||
CommandText = commandText,
|
||||
AccessToken = AccessToken,
|
||||
Subscription = SubscriptionId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task CreateTable(string tableName, List<RemoteTableColumn> columns)
|
||||
{
|
||||
await Initialize();
|
||||
|
||||
var url = await Server.SelectUrl(new ConnectedServerUrlArgs { Kind = ConnectedUrlKind.TableStorage });
|
||||
|
||||
await Http.Post($"{url}/createTable", new CreateTableArgs
|
||||
{
|
||||
Name = tableName,
|
||||
Columns = columns,
|
||||
AccessToken = AccessToken,
|
||||
Subscription = SubscriptionId
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Initialize()
|
||||
{
|
||||
if (SubscriptionId > 0)
|
||||
return;
|
||||
|
||||
if (await Subscription.Select() is not ISubscription subscription)
|
||||
throw new NullReferenceException(nameof(ISubscription));
|
||||
|
||||
SubscriptionId = subscription.Id;
|
||||
}
|
||||
}
|
33
Connected.ServiceModel.Client.Data/Remote/TableArgs.cs
Normal file
33
Connected.ServiceModel.Client.Data/Remote/TableArgs.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Connected.Annotations;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Remote;
|
||||
internal class TableArgs : ServiceArgs
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class QueryTableArgs : TableArgs
|
||||
{
|
||||
[Required]
|
||||
public string CommandText { get; set; } = default!;
|
||||
}
|
||||
|
||||
internal sealed class TableSchemaArgs : ServiceArgs
|
||||
{
|
||||
[Required, MaxLength(128)]
|
||||
public string TableName { get; set; } = default!;
|
||||
}
|
||||
|
||||
internal sealed class CreateTableArgs : ServiceArgs
|
||||
{
|
||||
[Required, MaxLength(128)]
|
||||
public string Name { get; set; } = default!;
|
||||
[NonDefault]
|
||||
public List<RemoteTableColumn> Columns { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class UpdateTableArgs : ServiceArgs
|
||||
{
|
||||
[Required]
|
||||
public string CommandText { get; set; } = default!;
|
||||
}
|
45
Connected.ServiceModel.Client.Data/Schema/ExistingSchema.cs
Normal file
45
Connected.ServiceModel.Client.Data/Schema/ExistingSchema.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using Connected.Data.Schema;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal sealed class ExistingSchema : ISchema
|
||||
{
|
||||
public ExistingSchema()
|
||||
{
|
||||
Columns = new();
|
||||
}
|
||||
|
||||
public List<ISchemaColumn> Columns { get; }
|
||||
|
||||
public string? Schema => null;
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Type { get; set; }
|
||||
|
||||
public bool Ignore { get; set; }
|
||||
|
||||
public async Task Load(SchemaExecutionContext context)
|
||||
{
|
||||
Name = context.Schema.Name;
|
||||
Type = context.Schema.Type;
|
||||
|
||||
if (!await context.Remote.TableExists(context.Schema.Name))
|
||||
return;
|
||||
|
||||
var columns = await context.Remote.QueryColumns(context.Schema.Name);
|
||||
|
||||
foreach (var column in columns)
|
||||
{
|
||||
Columns.Add(new SchemaColumn
|
||||
{
|
||||
Name = column.Name,
|
||||
//TODO:populate properties
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ISchema? other)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
43
Connected.ServiceModel.Client.Data/Schema/SchemaColumn.cs
Normal file
43
Connected.ServiceModel.Client.Data/Schema/SchemaColumn.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using Connected.Data.Schema;
|
||||
using Connected.Entities.Annotations;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal sealed class SchemaColumn : ISchemaColumn
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
public DbType DataType { get; set; }
|
||||
|
||||
public bool IsIdentity { get; set; }
|
||||
|
||||
public bool IsUnique { get; set; }
|
||||
|
||||
public bool IsIndex { get; set; }
|
||||
|
||||
public bool IsPrimaryKey { get; set; }
|
||||
|
||||
public bool IsVersion { get; set; }
|
||||
|
||||
public string? DefaultValue { get; set; }
|
||||
|
||||
public int MaxLength { get; set; }
|
||||
|
||||
public bool IsNullable { get; set; }
|
||||
|
||||
public string? Index { get; set; }
|
||||
|
||||
public int Scale { get; set; }
|
||||
|
||||
public int Precision { get; set; }
|
||||
|
||||
public DateKind DateKind { get; set; }
|
||||
|
||||
public BinaryKind BinaryKind { get; set; }
|
||||
|
||||
public int DatePrecision { get; set; }
|
||||
|
||||
public bool IsPartitionKey { get; set; }
|
||||
public PropertyInfo Property { get; set; }
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using Connected.Data.Schema;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal sealed class SchemaExecutionContext
|
||||
{
|
||||
public SchemaExecutionContext(ISchema schema, RemoteTableService remote)
|
||||
{
|
||||
Schema = schema;
|
||||
Remote = remote;
|
||||
}
|
||||
|
||||
public ExistingSchema ExistingSchema { get; set; }
|
||||
public ISchema Schema { get; }
|
||||
public RemoteTableService Remote { get; }
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal class SynchronizationCommand
|
||||
{
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal class SynchronizationQuery<T> : SynchronizationCommand
|
||||
{
|
||||
protected SchemaExecutionContext Context { get; private set; }
|
||||
|
||||
public async Task<T> Execute(SchemaExecutionContext context)
|
||||
{
|
||||
Context = context;
|
||||
|
||||
return await OnExecute();
|
||||
}
|
||||
|
||||
protected virtual async Task<T> OnExecute()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal class SynchronizationTransaction : SynchronizationCommand
|
||||
{
|
||||
protected SchemaExecutionContext Context { get; private set; }
|
||||
|
||||
public async Task Execute(SchemaExecutionContext context)
|
||||
{
|
||||
Context = context;
|
||||
|
||||
await OnExecute();
|
||||
}
|
||||
|
||||
protected virtual async Task OnExecute()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
127
Connected.ServiceModel.Client.Data/Schema/TableCreate.cs
Normal file
127
Connected.ServiceModel.Client.Data/Schema/TableCreate.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using Connected.Collections;
|
||||
using Connected.Interop;
|
||||
using Connected.ServiceModel.Annotations;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal sealed class TableCreate : TableTransaction
|
||||
{
|
||||
//private const string KeysExceptionMessage = "A ISchemaColumn cannot have a PartitionKeyAttribute and PrimaryKeyAttribute set. Use either PartitionKey or PrimaryKey attribute.";
|
||||
//private const string NoPartitionKeyMessage = "Schema must have at least one property with PartitionKeyAttribute.";
|
||||
|
||||
protected override async Task OnExecute()
|
||||
{
|
||||
var columns = new List<RemoteTableColumn>();
|
||||
|
||||
Context.Schema.Columns.SortByOrdinal();
|
||||
|
||||
foreach (var column in Context.Schema.Columns)
|
||||
{
|
||||
columns.Add(new RemoteTableColumn
|
||||
{
|
||||
DataType = CreateDataTypeMetaData(column),
|
||||
IsPartitionKey = column.Property?.FindAttribute<PartitionKeyAttribute>() is not null,
|
||||
IsPrimaryKey = column.IsPrimaryKey,
|
||||
Name = column.Name
|
||||
});
|
||||
}
|
||||
|
||||
await Context.Remote.CreateTable(Context.Schema.Name, columns);
|
||||
}
|
||||
|
||||
//private List<ISchemaColumn> PartitionKeys
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// var result = new List<ISchemaColumn>();
|
||||
|
||||
// foreach (var column in Context.Schema.Columns)
|
||||
// {
|
||||
// if (column.Property?.FindAttribute<PartitionKeyAttribute>() is not null)
|
||||
// {
|
||||
// if (column.IsPrimaryKey)
|
||||
// throw new InvalidOperationException($"{KeysExceptionMessage} ({column.Name})");
|
||||
|
||||
// result.Add(column);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
//}
|
||||
|
||||
//private List<ISchemaColumn> PrimaryKeys
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// var result = new List<ISchemaColumn>();
|
||||
|
||||
// foreach (var column in Context.Schema.Columns)
|
||||
// {
|
||||
// if (column.IsPrimaryKey)
|
||||
// {
|
||||
// if (column.Property?.FindAttribute<PartitionKeyAttribute>() is not null)
|
||||
// throw new InvalidOperationException($"{KeysExceptionMessage} ({column.Name})");
|
||||
|
||||
// result.Add(column);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
//}
|
||||
|
||||
//private string CommandText
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// var partitionKeys = PartitionKeys;
|
||||
// var primaryKeys = PrimaryKeys;
|
||||
|
||||
// if (!partitionKeys.Any())
|
||||
// throw new InvalidOperationException($"{NoPartitionKeyMessage} ({Context.Schema.Name})");
|
||||
|
||||
// var keysCount = partitionKeys.Count + primaryKeys.Count;
|
||||
// var text = new StringBuilder();
|
||||
// var name = Temporary ? TemporaryName : Context.Schema.Name;
|
||||
|
||||
// text.AppendLine($"CREATE TABLE {name}");
|
||||
// text.AppendLine("(");
|
||||
// var comma = string.Empty;
|
||||
|
||||
// for (var i = 0; i < Context.Schema.Columns.Count; i++)
|
||||
// {
|
||||
// text.AppendLine($"{comma} {CreateColumnCommandText(Context.Schema.Columns[i])}");
|
||||
|
||||
// comma = ",";
|
||||
// }
|
||||
|
||||
// text.Append("PRIMARY KEY (");
|
||||
|
||||
// if (keysCount > 1)
|
||||
// text.Append('(');
|
||||
|
||||
// for (var i = 0; i < partitionKeys.Count; i++)
|
||||
// {
|
||||
// text.Append(partitionKeys[i].Name);
|
||||
|
||||
// if (i < partitionKeys.Count - 1)
|
||||
// text.Append(',');
|
||||
// }
|
||||
|
||||
// if (keysCount > 1)
|
||||
// text.Append(')');
|
||||
|
||||
// for (var i = 0; i < primaryKeys.Count; i++)
|
||||
// {
|
||||
// text.Append(primaryKeys[i].Name);
|
||||
|
||||
// if (i < primaryKeys.Count - 1)
|
||||
// text.Append(',');
|
||||
// }
|
||||
|
||||
// text.AppendLine(");");
|
||||
|
||||
// return text.ToString();
|
||||
// }
|
||||
}
|
8
Connected.ServiceModel.Client.Data/Schema/TableExists.cs
Normal file
8
Connected.ServiceModel.Client.Data/Schema/TableExists.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal class TableExists : SynchronizationQuery<bool>
|
||||
{
|
||||
protected override async Task<bool> OnExecute()
|
||||
{
|
||||
return await Context.Remote.TableExists(Context.Schema.Name);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
using Connected.Data.Schema;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal sealed class TableSynchronize : TableTransaction
|
||||
{
|
||||
private ExistingSchema? _existingSchema;
|
||||
|
||||
private bool TableExists { get; set; }
|
||||
|
||||
protected override async Task OnExecute()
|
||||
{
|
||||
TableExists = await new TableExists().Execute(Context);
|
||||
|
||||
if (!TableExists)
|
||||
{
|
||||
await new TableCreate().Execute(Context);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
_existingSchema = new();
|
||||
|
||||
await _existingSchema.Load(Context);
|
||||
|
||||
Context.ExistingSchema = ExistingSchema;
|
||||
|
||||
//TODO: implement alter table
|
||||
|
||||
//if (ShouldRecreate)
|
||||
// await new TableRecreate(ExistingSchema).Execute(Context);
|
||||
//else if (ShouldAlter)
|
||||
// await new TableAlter(ExistingSchema).Execute(Context);
|
||||
}
|
||||
|
||||
private bool ShouldAlter => !Context.Schema.Equals(ExistingSchema);
|
||||
private bool ShouldRecreate => HasIdentityChanged || HasColumnMetadataChanged;
|
||||
|
||||
private ExistingSchema? ExistingSchema => _existingSchema;
|
||||
|
||||
private bool HasIdentityChanged
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var column in Context.Schema.Columns)
|
||||
{
|
||||
if (ExistingSchema.Columns.FirstOrDefault(f => string.Equals(f.Name, column.Name, StringComparison.OrdinalIgnoreCase)) is not ISchemaColumn existing)
|
||||
return true;
|
||||
|
||||
if (existing.IsIdentity != column.IsIdentity)
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var existing in ExistingSchema.Columns)
|
||||
{
|
||||
var column = Context.Schema.Columns.FirstOrDefault(f => string.Equals(f.Name, existing.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (column is null && existing.IsIdentity)
|
||||
return true;
|
||||
else if (column is not null && column.IsIdentity != existing.IsIdentity)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasColumnMetadataChanged
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var existing in ExistingSchema.Columns)
|
||||
{
|
||||
if (Context.Schema.Columns.FirstOrDefault(f => string.Equals(f.Name, existing.Name, StringComparison.OrdinalIgnoreCase)) is not ISchemaColumn column)
|
||||
continue;
|
||||
|
||||
if (column.DataType != existing.DataType
|
||||
|| column.MaxLength != existing.MaxLength
|
||||
|| column.IsNullable != existing.IsNullable
|
||||
|| column.IsVersion != existing.IsVersion
|
||||
|| column.Precision != existing.Precision
|
||||
|| column.Scale != existing.Scale
|
||||
|| column.DateKind != existing.DateKind
|
||||
|| column.BinaryKind != existing.BinaryKind
|
||||
|| column.DatePrecision != existing.DatePrecision)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
using Connected.Data.Schema;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data.Schema;
|
||||
internal class TableTransaction : SynchronizationTransaction
|
||||
{
|
||||
protected static string CreateColumnCommandText(ISchemaColumn column)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendFormat($"{column.Name} {CreateDataTypeMetaData(column)} ");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
protected static string CreateDataTypeMetaData(ISchemaColumn column)
|
||||
{
|
||||
return column.DataType switch
|
||||
{
|
||||
DbType.AnsiString => "text",
|
||||
DbType.Binary => "blob",
|
||||
DbType.Byte => "tinyint",
|
||||
DbType.Boolean => "boolean",
|
||||
DbType.Currency => "decimal",
|
||||
DbType.Date => "timestamp",
|
||||
DbType.DateTime => "timestamp",
|
||||
DbType.Decimal => "decimal",
|
||||
DbType.Double => "double",
|
||||
DbType.Guid => "uuid",
|
||||
DbType.Int16 => "smallint",
|
||||
DbType.Int32 => "int",
|
||||
DbType.Int64 => "bigint",
|
||||
DbType.Object => "blob",
|
||||
DbType.SByte => "smallint",
|
||||
DbType.Single => "float",
|
||||
DbType.String => "text",
|
||||
DbType.Time => "time",
|
||||
DbType.UInt16 => "int",
|
||||
DbType.UInt32 => "bigint",
|
||||
DbType.UInt64 => "float",
|
||||
DbType.VarNumeric => "decimal",
|
||||
DbType.AnsiStringFixedLength => "text",
|
||||
DbType.StringFixedLength => "text",
|
||||
DbType.Xml => "text",
|
||||
DbType.DateTime2 => "timestamp",
|
||||
DbType.DateTimeOffset => "timestamp",
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Data;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
/// <summary>
|
||||
@ -6,6 +7,14 @@ namespace Connected.ServiceModel.Client.Data;
|
||||
/// </summary>
|
||||
internal sealed class TableConnection : IDbConnection
|
||||
{
|
||||
public TableConnection(RemoteTableService tables, string connectionString)
|
||||
{
|
||||
Tables = tables;
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
|
||||
public RemoteTableService Tables { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection string (URL) used when performing the requests.
|
||||
/// </summary>
|
||||
@ -60,15 +69,14 @@ internal sealed class TableConnection : IDbConnection
|
||||
|
||||
public IDbCommand CreateCommand()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new TableDataCommand(Tables);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
/// <summary>
|
||||
/// This method doesn't do enything since the connection is stateless any is theoretically
|
||||
/// This method doesn't do anything since the connection is stateless any is theoretically
|
||||
/// always in open state.
|
||||
/// </summary>
|
||||
public void Open()
|
||||
|
64
Connected.ServiceModel.Client.Data/TableDataCommand.cs
Normal file
64
Connected.ServiceModel.Client.Data/TableDataCommand.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System.Data;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class TableDataCommand : IDbCommand
|
||||
{
|
||||
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 RemoteTableService Tables { get; }
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public IDbDataParameter CreateParameter()
|
||||
{
|
||||
return new TableDataParameter();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public int ExecuteNonQuery()
|
||||
{
|
||||
AsyncUtils.RunSync(() => Tables.Update(CommandText));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public IDataReader ExecuteReader()
|
||||
{
|
||||
return new TableDataReader(AsyncUtils.RunSync(() => Tables.Query(CommandText)));
|
||||
}
|
||||
|
||||
public IDataReader ExecuteReader(CommandBehavior behavior)
|
||||
{
|
||||
return new TableDataReader(AsyncUtils.RunSync(() => Tables.Query(CommandText)));
|
||||
}
|
||||
|
||||
public object? ExecuteScalar()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Prepare()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -1,16 +1,19 @@
|
||||
using Connected.Annotations;
|
||||
using System.Data;
|
||||
using Connected.Annotations;
|
||||
using Connected.Data.Storage;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Data;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
|
||||
[ServiceRegistration(ServiceRegistrationMode.Auto, ServiceRegistrationScope.Transient)]
|
||||
internal sealed class TableDataConnection : DatabaseConnection
|
||||
{
|
||||
public TableDataConnection(ICancellationContext context) : base(context)
|
||||
public TableDataConnection(RemoteTableService tables, ICancellationContext context) : base(context)
|
||||
{
|
||||
Tables = tables;
|
||||
}
|
||||
private RemoteTableService Tables { get; }
|
||||
|
||||
protected override void SetupParameters(IStorageCommand command, IDbCommand cmd)
|
||||
{
|
||||
@ -54,6 +57,6 @@ internal sealed class TableDataConnection : DatabaseConnection
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
|
||||
return new SqlConnection(ConnectionString);
|
||||
return new TableConnection(Tables, ConnectionString);
|
||||
}
|
||||
}
|
||||
|
16
Connected.ServiceModel.Client.Data/TableDataParameter.cs
Normal file
16
Connected.ServiceModel.Client.Data/TableDataParameter.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Data;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal class TableDataParameter : IDbDataParameter
|
||||
{
|
||||
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; }
|
||||
}
|
164
Connected.ServiceModel.Client.Data/TableDataReader.cs
Normal file
164
Connected.ServiceModel.Client.Data/TableDataReader.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class TableDataReader : IDataReader
|
||||
{
|
||||
public TableDataReader(JsonArray items)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public object this[int i] => throw new NotImplementedException();
|
||||
|
||||
public object this[string name] => throw new NotImplementedException();
|
||||
|
||||
public int Depth => throw new NotImplementedException();
|
||||
|
||||
public bool IsClosed => throw new NotImplementedException();
|
||||
|
||||
public int RecordsAffected => throw new NotImplementedException();
|
||||
|
||||
public int FieldCount => throw new NotImplementedException();
|
||||
|
||||
public JsonArray Items { get; }
|
||||
|
||||
public void Close()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool GetBoolean(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public byte GetByte(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public char GetChar(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IDataReader GetData(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetDataTypeName(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DateTime GetDateTime(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public decimal GetDecimal(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public double GetDouble(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public Type GetFieldType(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public float GetFloat(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Guid GetGuid(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public short GetInt16(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int GetInt32(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public long GetInt64(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetName(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int GetOrdinal(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DataTable? GetSchemaTable()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetString(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public object GetValue(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int GetValues(object[] values)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsDBNull(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool NextResult()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Read()
|
||||
{
|
||||
//var items = AsyncUtils.RunSync(() => Service.Query(CommandText));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
using Connected.Annotations;
|
||||
using Connected.Data.Schema;
|
||||
using Connected.Entities.Annotations;
|
||||
using Connected.Entities.Storage;
|
||||
using Connected.Interop;
|
||||
using Connected.Middleware;
|
||||
using Connected.ServiceModel.Client.Data.Remote;
|
||||
using Connected.ServiceModel.Client.Data.Schema;
|
||||
using Connected.ServiceModel.Client.Net;
|
||||
using Connected.ServiceModel.Data;
|
||||
|
||||
@ -10,13 +14,15 @@ namespace Connected.ServiceModel.Client.Data;
|
||||
[Priority(2)]
|
||||
internal sealed class TableSchemaMiddleware : MiddlewareComponent, ISchemaMiddleware
|
||||
{
|
||||
public TableSchemaMiddleware(IMiddlewareService middleware, IStorageProvider storage, IConnectedServer connected)
|
||||
public TableSchemaMiddleware(IMiddlewareService middleware, IStorageProvider storage, RemoteTableService remote, IConnectedServer connected)
|
||||
{
|
||||
Storage = storage;
|
||||
Remote = remote;
|
||||
Connected = connected;
|
||||
}
|
||||
private IStorageProvider Storage { get; }
|
||||
public IConnectedServer Connected { get; }
|
||||
private RemoteTableService Remote { get; }
|
||||
private IConnectedServer Connected { get; }
|
||||
public Type ConnectionType => typeof(TableDataConnection);
|
||||
public string DefaultConnectionString { get; private set; } = default!;
|
||||
|
||||
@ -31,7 +37,7 @@ internal sealed class TableSchemaMiddleware : MiddlewareComponent, ISchemaMiddle
|
||||
/*
|
||||
* This middleware supports all ITableEntity<> entities.
|
||||
*/
|
||||
return entityType.IsAssignableTo(typeof(ITableEntity<,>));
|
||||
return entityType.ImplementsInterface(typeof(ITableEntity<>));
|
||||
}
|
||||
|
||||
public async Task Synchronize(Type entity, ISchema schema)
|
||||
@ -41,17 +47,9 @@ internal sealed class TableSchemaMiddleware : MiddlewareComponent, ISchemaMiddle
|
||||
|
||||
private async Task Synchronize(ISchema schema, string connectionString)
|
||||
{
|
||||
//var args = new SchemaExecutionContext(Storage, schema, connectionString);
|
||||
///*
|
||||
// * Sinchronize schema object first.
|
||||
// */
|
||||
//await new SchemaSynchronize().Execute(args);
|
||||
///*
|
||||
// * Only tables are supported
|
||||
// */
|
||||
//if (string.IsNullOrWhiteSpace(schema.Type) || string.Equals(schema.Type, SchemaAttribute.SchemaTypeTable, StringComparison.OrdinalIgnoreCase))
|
||||
// await new TableSynchronize().Execute(args);
|
||||
var args = new SchemaExecutionContext(schema, Remote);
|
||||
|
||||
await Task.CompletedTask;
|
||||
if (string.IsNullOrWhiteSpace(schema.Type) || string.Equals(schema.Type, SchemaAttribute.SchemaTypeTable, StringComparison.OrdinalIgnoreCase))
|
||||
await new TableSynchronize().Execute(args);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using Connected.Expressions;
|
||||
using Connected.Expressions.Evaluation;
|
||||
using Connected.Expressions.Query;
|
||||
using Connected.Expressions.Translation;
|
||||
using Connected.Interop;
|
||||
using Connected.ServiceModel.Data;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
@ -82,19 +83,18 @@ internal class TableStorageProvider : QueryProvider, IStorageExecutor, IStorageM
|
||||
|
||||
public bool SupportsEntity(Type entityType)
|
||||
{
|
||||
return entityType.IsAssignableTo(typeof(ITableEntity<,>));
|
||||
return entityType.ImplementsInterface(typeof(ITableEntity<>));
|
||||
}
|
||||
|
||||
public IStorageOperation CreateOperation<TEntity>(TEntity entity)
|
||||
where TEntity : IEntity
|
||||
{
|
||||
//var builder = new AggregatedCommandBuilder<TEntity?>();
|
||||
var builder = new AggregatedCommandBuilder<TEntity?>();
|
||||
|
||||
//if (builder.Build(entity) is not StorageOperation operation)
|
||||
// throw new NullReferenceException(nameof(StorageOperation));
|
||||
if (builder.Build(entity) is not StorageOperation operation)
|
||||
throw new NullReferenceException(nameof(StorageOperation));
|
||||
|
||||
//return operation;
|
||||
return null;
|
||||
return operation;
|
||||
}
|
||||
|
||||
public IStorageReader<TEntity> OpenReader<TEntity>(IStorageOperation operation, IStorageConnection connection)
|
||||
|
77
Connected.ServiceModel.Client.Data/UpdateCommandBuilder.cs
Normal file
77
Connected.ServiceModel.Client.Data/UpdateCommandBuilder.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using Connected.Entities.Annotations;
|
||||
using Connected.Entities.Storage;
|
||||
|
||||
namespace Connected.ServiceModel.Client.Data;
|
||||
internal sealed class UpdateCommandBuilder : CommandBuilder
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, StorageOperation> _cache;
|
||||
|
||||
static UpdateCommandBuilder()
|
||||
{
|
||||
_cache = new();
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<string, StorageOperation> Cache => _cache;
|
||||
private bool SupportsConcurrency { get; set; }
|
||||
protected override bool TryGetExisting(out StorageOperation? result)
|
||||
{
|
||||
return Cache.TryGetValue(Entity.GetType().FullName, out result);
|
||||
}
|
||||
|
||||
protected override StorageOperation OnBuild()
|
||||
{
|
||||
WriteLine($"UPDATE [{Schema.Schema}].[{Schema.Name}] SET");
|
||||
|
||||
WriteAssignments();
|
||||
WriteWhere();
|
||||
|
||||
Trim();
|
||||
Write(';');
|
||||
|
||||
var result = new StorageOperation { CommandText = CommandText, Concurrency = SupportsConcurrency ? DataConcurrencyMode.Enabled : DataConcurrencyMode.Disabled };
|
||||
|
||||
foreach (var parameter in Parameters)
|
||||
result.AddParameter(parameter);
|
||||
|
||||
Cache.TryAdd(Entity.GetType().FullName, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void WriteAssignments()
|
||||
{
|
||||
foreach (var property in Properties)
|
||||
{
|
||||
if (property.GetCustomAttribute<PrimaryKeyAttribute>() is not null)
|
||||
{
|
||||
WhereProperties.Add(property);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameter = CreateParameter(property);
|
||||
|
||||
WriteLine($"{ColumnName(property)} = {parameter.Name},");
|
||||
}
|
||||
|
||||
Trim();
|
||||
}
|
||||
|
||||
private void WriteWhere()
|
||||
{
|
||||
WriteLine(string.Empty);
|
||||
|
||||
for (var i = 0; i < WhereProperties.Count; i++)
|
||||
{
|
||||
var property = WhereProperties[i];
|
||||
var parameter = CreateParameter(property);
|
||||
|
||||
if (i == 0)
|
||||
WriteLine($" WHERE {ColumnName(property)} = {parameter.Name}");
|
||||
else
|
||||
WriteLine($" AND {ColumnName(property)} = {parameter.Name}");
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ namespace Connected.ServiceModel.Client.Net;
|
||||
|
||||
internal sealed class SelectUrl : ServiceFunction<ConnectedServerUrlArgs, string>
|
||||
{
|
||||
private const string Root = "https://connected.tompit.com";
|
||||
private const string Root = "https://localhost:61599";//"https://connected.tompit.com";
|
||||
protected override async Task<string?> OnInvoke()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
|
Loading…
x
Reference in New Issue
Block a user