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.Formatters;
 | 
				
			||||||
using Connected.Expressions.Languages;
 | 
					using Connected.Expressions.Languages;
 | 
				
			||||||
using Connected.Interop;
 | 
					using Connected.Interop;
 | 
				
			||||||
using System.Linq.Expressions;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Connected.ServiceModel.Client.Data;
 | 
					namespace Connected.ServiceModel.Client.Data;
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
@ -14,7 +14,12 @@ internal sealed class CqlFormatter : SqlFormatter
 | 
				
			|||||||
		  : base(language)
 | 
							  : base(language)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		Context = context;
 | 
							Context = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							HideColumnAliases = true;
 | 
				
			||||||
 | 
							HideTableAliases = true;
 | 
				
			||||||
 | 
							UseBracketsInWhere = false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public ExpressionCompilationContext Context { get; }
 | 
						public ExpressionCompilationContext Context { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static new string Format(ExpressionCompilationContext context, Expression expression)
 | 
						public static new string Format(ExpressionCompilationContext context, Expression expression)
 | 
				
			||||||
@ -36,6 +41,11 @@ internal sealed class CqlFormatter : SqlFormatter
 | 
				
			|||||||
		else
 | 
							else
 | 
				
			||||||
			base.WriteAggregateName(aggregateName);
 | 
								base.WriteAggregateName(aggregateName);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected override void WriteTableName(string tableSchema, string tableName)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Write(tableName);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	protected override Expression VisitMemberAccess(MemberExpression m)
 | 
						protected override Expression VisitMemberAccess(MemberExpression m)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		if (m.Member.DeclaringType == typeof(string))
 | 
							if (m.Member.DeclaringType == typeof(string))
 | 
				
			||||||
 | 
				
			|||||||
@ -37,12 +37,7 @@ internal sealed class CqlLanguage : QueryLanguage
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public override string Quote(string name)
 | 
						public override string Quote(string name)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		if (name.StartsWith("[") && name.EndsWith("]"))
 | 
							return name;
 | 
				
			||||||
			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)
 | 
						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.Languages;
 | 
				
			||||||
using Connected.Expressions.Translation;
 | 
					using Connected.Expressions.Translation;
 | 
				
			||||||
using Connected.Expressions.Translation.Rewriters;
 | 
					using Connected.Expressions.Translation.Rewriters;
 | 
				
			||||||
using System.Linq.Expressions;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Connected.ServiceModel.Client.Data;
 | 
					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 System.Data;
 | 
				
			||||||
 | 
					using Connected.ServiceModel.Client.Data.Remote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Connected.ServiceModel.Client.Data;
 | 
					namespace Connected.ServiceModel.Client.Data;
 | 
				
			||||||
/// <summary>
 | 
					/// <summary>
 | 
				
			||||||
@ -6,6 +7,14 @@ namespace Connected.ServiceModel.Client.Data;
 | 
				
			|||||||
/// </summary>
 | 
					/// </summary>
 | 
				
			||||||
internal sealed class TableConnection : IDbConnection
 | 
					internal sealed class TableConnection : IDbConnection
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						public TableConnection(RemoteTableService tables, string connectionString)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Tables = tables;
 | 
				
			||||||
 | 
							ConnectionString = connectionString;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public RemoteTableService Tables { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// <summary>
 | 
						/// <summary>
 | 
				
			||||||
	/// The connection string (URL) used when performing the requests. 
 | 
						/// The connection string (URL) used when performing the requests. 
 | 
				
			||||||
	/// </summary>
 | 
						/// </summary>
 | 
				
			||||||
@ -60,15 +69,14 @@ internal sealed class TableConnection : IDbConnection
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public IDbCommand CreateCommand()
 | 
						public IDbCommand CreateCommand()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		throw new NotImplementedException();
 | 
							return new TableDataCommand(Tables);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void Dispose()
 | 
						public void Dispose()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		throw new NotImplementedException();
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	/// <summary>
 | 
						/// <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.
 | 
						/// always in open state.
 | 
				
			||||||
	/// </summary>
 | 
						/// </summary>
 | 
				
			||||||
	public void Open()
 | 
						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.Data.Storage;
 | 
				
			||||||
 | 
					using Connected.ServiceModel.Client.Data.Remote;
 | 
				
			||||||
using Microsoft.Data.SqlClient;
 | 
					using Microsoft.Data.SqlClient;
 | 
				
			||||||
using System.Data;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Connected.ServiceModel.Client.Data;
 | 
					namespace Connected.ServiceModel.Client.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[ServiceRegistration(ServiceRegistrationMode.Auto, ServiceRegistrationScope.Transient)]
 | 
					[ServiceRegistration(ServiceRegistrationMode.Auto, ServiceRegistrationScope.Transient)]
 | 
				
			||||||
internal sealed class TableDataConnection : DatabaseConnection
 | 
					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)
 | 
						protected override void SetupParameters(IStorageCommand command, IDbCommand cmd)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@ -54,6 +57,6 @@ internal sealed class TableDataConnection : DatabaseConnection
 | 
				
			|||||||
	{
 | 
						{
 | 
				
			||||||
		await Task.CompletedTask;
 | 
							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.Annotations;
 | 
				
			||||||
using Connected.Data.Schema;
 | 
					using Connected.Data.Schema;
 | 
				
			||||||
 | 
					using Connected.Entities.Annotations;
 | 
				
			||||||
using Connected.Entities.Storage;
 | 
					using Connected.Entities.Storage;
 | 
				
			||||||
 | 
					using Connected.Interop;
 | 
				
			||||||
using Connected.Middleware;
 | 
					using Connected.Middleware;
 | 
				
			||||||
 | 
					using Connected.ServiceModel.Client.Data.Remote;
 | 
				
			||||||
 | 
					using Connected.ServiceModel.Client.Data.Schema;
 | 
				
			||||||
using Connected.ServiceModel.Client.Net;
 | 
					using Connected.ServiceModel.Client.Net;
 | 
				
			||||||
using Connected.ServiceModel.Data;
 | 
					using Connected.ServiceModel.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -10,13 +14,15 @@ namespace Connected.ServiceModel.Client.Data;
 | 
				
			|||||||
[Priority(2)]
 | 
					[Priority(2)]
 | 
				
			||||||
internal sealed class TableSchemaMiddleware : MiddlewareComponent, ISchemaMiddleware
 | 
					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;
 | 
							Storage = storage;
 | 
				
			||||||
 | 
							Remote = remote;
 | 
				
			||||||
		Connected = connected;
 | 
							Connected = connected;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	private IStorageProvider Storage { get; }
 | 
						private IStorageProvider Storage { get; }
 | 
				
			||||||
	public IConnectedServer Connected { get; }
 | 
						private RemoteTableService Remote { get; }
 | 
				
			||||||
 | 
						private IConnectedServer Connected { get; }
 | 
				
			||||||
	public Type ConnectionType => typeof(TableDataConnection);
 | 
						public Type ConnectionType => typeof(TableDataConnection);
 | 
				
			||||||
	public string DefaultConnectionString { get; private set; } = default!;
 | 
						public string DefaultConnectionString { get; private set; } = default!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,7 +37,7 @@ internal sealed class TableSchemaMiddleware : MiddlewareComponent, ISchemaMiddle
 | 
				
			|||||||
		/*
 | 
							/*
 | 
				
			||||||
		 * This middleware supports all ITableEntity<> entities.
 | 
							 * This middleware supports all ITableEntity<> entities.
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		return entityType.IsAssignableTo(typeof(ITableEntity<,>));
 | 
							return entityType.ImplementsInterface(typeof(ITableEntity<>));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public async Task Synchronize(Type entity, ISchema schema)
 | 
						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)
 | 
						private async Task Synchronize(ISchema schema, string connectionString)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		//var args = new SchemaExecutionContext(Storage, schema, connectionString);
 | 
							var args = new SchemaExecutionContext(schema, Remote);
 | 
				
			||||||
		///*
 | 
					 | 
				
			||||||
		// * 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);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		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.Evaluation;
 | 
				
			||||||
using Connected.Expressions.Query;
 | 
					using Connected.Expressions.Query;
 | 
				
			||||||
using Connected.Expressions.Translation;
 | 
					using Connected.Expressions.Translation;
 | 
				
			||||||
 | 
					using Connected.Interop;
 | 
				
			||||||
using Connected.ServiceModel.Data;
 | 
					using Connected.ServiceModel.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Connected.ServiceModel.Client.Data;
 | 
					namespace Connected.ServiceModel.Client.Data;
 | 
				
			||||||
@ -82,19 +83,18 @@ internal class TableStorageProvider : QueryProvider, IStorageExecutor, IStorageM
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public bool SupportsEntity(Type entityType)
 | 
						public bool SupportsEntity(Type entityType)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		return entityType.IsAssignableTo(typeof(ITableEntity<,>));
 | 
							return entityType.ImplementsInterface(typeof(ITableEntity<>));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public IStorageOperation CreateOperation<TEntity>(TEntity entity)
 | 
						public IStorageOperation CreateOperation<TEntity>(TEntity entity)
 | 
				
			||||||
		where TEntity : IEntity
 | 
							where TEntity : IEntity
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		//var builder = new AggregatedCommandBuilder<TEntity?>();
 | 
							var builder = new AggregatedCommandBuilder<TEntity?>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//if (builder.Build(entity) is not StorageOperation operation)
 | 
							if (builder.Build(entity) is not StorageOperation operation)
 | 
				
			||||||
		//	throw new NullReferenceException(nameof(StorageOperation));
 | 
								throw new NullReferenceException(nameof(StorageOperation));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//return operation;
 | 
							return operation;
 | 
				
			||||||
		return null;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public IStorageReader<TEntity> OpenReader<TEntity>(IStorageOperation operation, IStorageConnection connection)
 | 
						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>
 | 
					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()
 | 
						protected override async Task<string?> OnInvoke()
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		await Task.CompletedTask;
 | 
							await Task.CompletedTask;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user