diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 45489aa1..bb942cdb 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ Sebastien Ros Sebastien Ros - net6.0 + net7.0 true portable https://github.com/sebastienros/yessql diff --git a/src/YesSql.Abstractions/IConfiguration.cs b/src/YesSql.Abstractions/IConfiguration.cs index 423be7a4..8b80dde6 100644 --- a/src/YesSql.Abstractions/IConfiguration.cs +++ b/src/YesSql.Abstractions/IConfiguration.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Data; +using YesSql.Indexes; namespace YesSql { @@ -90,6 +91,13 @@ public interface IConfiguration /// Gets or sets the identity column size. Default is . /// IdentityColumnSize IdentityColumnSize { get; set; } + + /// + /// Index type cache provider + /// This interface needs to be reimplemented when dynamic type and multi-tenant scenarios are used, + /// using tenant separated cache media insteadexTypeCacheProvider + /// + IndexTypeCacheProvider IndexTypeCacheProvider { get; set; } } public static class ConfigurationExtensions diff --git a/src/YesSql.Abstractions/IContentSerializer.cs b/src/YesSql.Abstractions/IContentSerializer.cs index 8ddd6b1c..eb548f97 100644 --- a/src/YesSql.Abstractions/IContentSerializer.cs +++ b/src/YesSql.Abstractions/IContentSerializer.cs @@ -22,12 +22,5 @@ public interface IContentSerializer /// The type of the object to deserialize. /// The deserialized object. object Deserialize(string content, Type type); - - /// - /// Deserializes an object to a dynamic instance. - /// - /// The instance representing the object to deserialize. - /// The deserialized object. - dynamic DeserializeDynamic(string content); } } diff --git a/src/YesSql.Abstractions/Indexes/DescribeContext.cs b/src/YesSql.Abstractions/Indexes/DescribeContext.cs index 22a3bab9..d15c060c 100644 --- a/src/YesSql.Abstractions/Indexes/DescribeContext.cs +++ b/src/YesSql.Abstractions/Indexes/DescribeContext.cs @@ -6,7 +6,7 @@ namespace YesSql.Indexes { public class DescribeContext : IDescriptor { - private readonly Dictionary> _describes = new Dictionary>(); + private readonly Dictionary> _describes = []; public IEnumerable Describe(params Type[] types) { @@ -25,21 +25,31 @@ public IEnumerable Describe(params Type[] types) }); } + public IMapFor For(Type indexType) => For(indexType); + public IMapFor For() where TIndex : IIndex { - return For(); + return For(typeof(TIndex)); } - public IMapFor For() where TIndex : IIndex + public IMapFor For() where TIndex : IIndex => For(typeof(TIndex)); + + public IMapFor For(Type indexType) where TIndex : IIndex { + ArgumentNullException.ThrowIfNull(indexType, nameof(indexType)); + List descriptors; if (!_describes.TryGetValue(typeof(T), out descriptors)) { - descriptors = _describes[typeof(T)] = new List(); + descriptors = _describes[typeof(T)] = []; } - var describeFor = new IndexDescriptor(); + var describeFor = new IndexDescriptor() + { + IndexType = indexType, + }; + descriptors.Add(describeFor); return describeFor; diff --git a/src/YesSql.Abstractions/Indexes/DescribeFor.cs b/src/YesSql.Abstractions/Indexes/DescribeFor.cs index e376c209..68366e69 100644 --- a/src/YesSql.Abstractions/Indexes/DescribeFor.cs +++ b/src/YesSql.Abstractions/Indexes/DescribeFor.cs @@ -51,8 +51,9 @@ public class IndexDescriptor : IDescribeFor, IMapFor private Func _filter; public PropertyInfo GroupProperty { get; set; } - public Type IndexType { get { return typeof(TIndex); } } + public Type IndexType { get; internal set; } = typeof(TIndex); + public Func Filter => _filter; public IGroupFor Map(Func> map) diff --git a/src/YesSql.Abstractions/Indexes/IndexTypeCacheProvider.cs b/src/YesSql.Abstractions/Indexes/IndexTypeCacheProvider.cs new file mode 100644 index 00000000..a857d556 --- /dev/null +++ b/src/YesSql.Abstractions/Indexes/IndexTypeCacheProvider.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using YesSql.Serialization; + +namespace YesSql.Indexes +{ + public class IndexTypeCacheProvider + { + private static readonly ConcurrentDictionary PropertyAccessors = new(); + private static readonly ConcurrentDictionary TypeProperties = new(); + + public virtual PropertyInfoAccessor GetPropertyAccessor(PropertyInfo property) => PropertyAccessors.GetOrAdd(property, p => new PropertyInfoAccessor(p)); + + public virtual PropertyInfo[] GetTypeProperties(Type type) + { + if (TypeProperties.TryGetValue(type.FullName, out var pis)) + { + return pis; + } + + var properties = type.GetProperties().Where(IsWriteable).ToArray(); + TypeProperties[type.FullName] = properties; + + return properties; + } + + public virtual void UpdateCachedType(Type type) + { + if (TypeProperties.TryRemove(type.FullName, out var pis)) + { + foreach (var prop in pis) + { + PropertyAccessors.TryRemove(prop, out _); + } + } + + var properties = type.GetProperties().Where(IsWriteable).ToArray(); + TypeProperties[type.FullName] = properties; + } + + protected bool IsWriteable(PropertyInfo pi) + { + return + pi.Name != nameof(IIndex.Id) && + // don't read DocumentId when on a MapIndex as it might be used to + // read the DocumentId directly from an Index query + pi.Name != "DocumentId" + ; + } + } +} diff --git a/src/YesSql.Core/Serialization/PropertyInfoAccessor.cs b/src/YesSql.Abstractions/Serialization/PropertyInfoAccessor.cs similarity index 100% rename from src/YesSql.Core/Serialization/PropertyInfoAccessor.cs rename to src/YesSql.Abstractions/Serialization/PropertyInfoAccessor.cs diff --git a/src/YesSql.Core/Commands/IndexCommand.cs b/src/YesSql.Core/Commands/IndexCommand.cs index c0e0b234..8a782b74 100644 --- a/src/YesSql.Core/Commands/IndexCommand.cs +++ b/src/YesSql.Core/Commands/IndexCommand.cs @@ -15,16 +15,15 @@ namespace YesSql.Commands { public abstract class IndexCommand : IIndexCommand { + private const string _separator = ", "; protected const string ParameterSuffix = "_$$$"; - protected readonly IStore _store; + private static IndexTypeCacheProvider IndexTypeCacheProvider; + protected static PropertyInfo[] KeysProperties = [typeof(IIndex).GetProperty("Id")]; - private static readonly ConcurrentDictionary PropertyAccessors = new(); - private static readonly ConcurrentDictionary TypeProperties = new(); private static readonly ConcurrentDictionary InsertsList = new(); private static readonly ConcurrentDictionary UpdatesList = new(); - - protected static PropertyInfo[] KeysProperties = new[] { typeof(IIndex).GetProperty("Id") }; + protected readonly IStore _store; public abstract int ExecutionOrder { get; } @@ -33,6 +32,7 @@ public IndexCommand(IIndex index, IStore store, string collection) Index = index; _store = store; Collection = collection; + IndexTypeCacheProvider = store.Configuration.IndexTypeCacheProvider; } public IIndex Index { get; } @@ -47,13 +47,13 @@ public static void ResetQueryCache() UpdatesList.Clear(); } - protected static void GetProperties(DbCommand command, object item, string suffix, ISqlDialect dialect) + protected void GetProperties(DbCommand command, object item, string suffix, ISqlDialect dialect) { var type = item.GetType(); foreach (var property in TypePropertiesCache(type)) { - var accessor = PropertyAccessors.GetOrAdd(property, p => new PropertyInfoAccessor(p)); + var accessor = IndexTypeCacheProvider.GetPropertyAccessor(property); var value = accessor.Get(item); @@ -65,16 +65,9 @@ protected static void GetProperties(DbCommand command, object item, string suffi } } - protected static PropertyInfo[] TypePropertiesCache(Type type) + protected PropertyInfo[] TypePropertiesCache(Type type) { - if (TypeProperties.TryGetValue(type.FullName, out var pis)) - { - return pis; - } - - var properties = type.GetProperties().Where(IsWriteable).ToArray(); - TypeProperties[type.FullName] = properties; - return properties; + return IndexTypeCacheProvider.GetTypeProperties(type); } protected string Inserts(Type type, ISqlDialect dialect) @@ -102,7 +95,7 @@ protected string Inserts(Type type, ISqlDialect dialect) sbColumnList.Append(dialect.QuoteForColumnName(property.Name)); if (i < allProperties.Length - 1) { - sbColumnList.Append(", "); + sbColumnList.Append(_separator); } } @@ -113,14 +106,14 @@ protected string Inserts(Type type, ISqlDialect dialect) sbParameterList.Append("@").Append(property.Name).Append(ParameterSuffix); if (i < allProperties.Length - 1) { - sbParameterList.Append(", "); + sbParameterList.Append(_separator); } } if (typeof(MapIndex).IsAssignableFrom(type)) { // We can set the document id - sbColumnList.Append(", ").Append(dialect.QuoteForColumnName("DocumentId")); + sbColumnList.Append(_separator).Append(dialect.QuoteForColumnName("DocumentId")); sbParameterList.Append(", @DocumentId").Append(ParameterSuffix); } @@ -164,7 +157,7 @@ protected string Updates(Type type, ISqlDialect dialect) values.Append(dialect.QuoteForColumnName(property.Name) + " = @" + property.Name + ParameterSuffix); if (i < allProperties.Length - 1) { - values.Append(", "); + values.Append(_separator); } } @@ -174,16 +167,6 @@ protected string Updates(Type type, ISqlDialect dialect) return result; } - private static bool IsWriteable(PropertyInfo pi) - { - return - pi.Name != nameof(IIndex.Id) && - // don't read DocumentId when on a MapIndex as it might be used to - // read the DocumentId directly from an Index query - pi.Name != "DocumentId" - ; - } - public abstract bool AddToBatch(ISqlDialect dialect, List queries, DbCommand batchCommand, List> actions, int index); private record CompoundKey(string Dialect, string Type, string Schema, string Prefix, string Collection); diff --git a/src/YesSql.Core/Configuration.cs b/src/YesSql.Core/Configuration.cs index 4bac7823..51575a67 100644 --- a/src/YesSql.Core/Configuration.cs +++ b/src/YesSql.Core/Configuration.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Data; using YesSql.Data; +using YesSql.Indexes; using YesSql.Serialization; using YesSql.Services; @@ -15,7 +16,7 @@ public Configuration() { IdentifierAccessorFactory = new PropertyAccessorFactory("Id"); VersionAccessorFactory = new PropertyAccessorFactory("Version"); - ContentSerializer = new JsonContentSerializer(); + ContentSerializer = new DefaultContentSerializer(); IdGenerator = new DefaultIdGenerator(); IsolationLevel = IsolationLevel.ReadCommitted; TablePrefix = ""; @@ -24,6 +25,7 @@ public Configuration() Logger = NullLogger.Instance; ConcurrentTypes = new HashSet(); TableNameConvention = new DefaultTableNameConvention(); + IndexTypeCacheProvider = new IndexTypeCacheProvider(); } public IAccessorFactory IdentifierAccessorFactory { get; set; } @@ -42,5 +44,6 @@ public Configuration() public ICommandInterpreter CommandInterpreter { get; set; } public ISqlDialect SqlDialect { get; set; } public IdentityColumnSize IdentityColumnSize { get; set; } = IdentityColumnSize.Int32; + public IndexTypeCacheProvider IndexTypeCacheProvider { get; set; } } } diff --git a/src/YesSql.Core/Provider/ServiceCollectionExtensions.cs b/src/YesSql.Core/Provider/ServiceCollectionExtensions.cs index e085c3c8..25130197 100644 --- a/src/YesSql.Core/Provider/ServiceCollectionExtensions.cs +++ b/src/YesSql.Core/Provider/ServiceCollectionExtensions.cs @@ -21,7 +21,7 @@ public static IServiceCollection AddDbProvider( var config = new Configuration(); setupAction.Invoke(config); - services.AddSingleton(StoreFactory.CreateAndInitializeAsync(config).GetAwaiter().GetResult()); + services.AddSingleton(StoreFactory.CreateAndInitializeAsync(config).GetAwaiter().GetResult()); return services; } diff --git a/src/YesSql.Core/Serialization/DefaultContentSerializer.cs b/src/YesSql.Core/Serialization/DefaultContentSerializer.cs new file mode 100644 index 00000000..3515a57e --- /dev/null +++ b/src/YesSql.Core/Serialization/DefaultContentSerializer.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace YesSql.Serialization +{ + public class DefaultContentSerializer : IContentSerializer + { + private readonly JsonSerializerOptions _options; + + public DefaultContentSerializer() + { + _options = new(); + _options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + _options.Converters.Add(UtcDateTimeJsonConverter.Instance); + _options.Converters.Add(DynamicJsonConverter.Instance); + } + + public DefaultContentSerializer(JsonSerializerOptions options) + { + _options = options; + } + + public object Deserialize(string content, Type type) + { + return JsonSerializer.Deserialize(content, type, _options); + } + + public string Serialize(object item) + { + return JsonSerializer.Serialize(item, _options); + } + } +} diff --git a/src/YesSql.Core/Serialization/DynamicJsonConverter.cs b/src/YesSql.Core/Serialization/DynamicJsonConverter.cs new file mode 100644 index 00000000..e67285ec --- /dev/null +++ b/src/YesSql.Core/Serialization/DynamicJsonConverter.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace YesSql.Serialization +{ + public class DynamicJsonConverter : JsonConverter + { + public static readonly DynamicJsonConverter Instance = new(); + + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.Null: + return null; + case JsonTokenType.False: + return false; + case JsonTokenType.True: + return true; + case JsonTokenType.String: + return reader.GetString(); + case JsonTokenType.Number: + { + if (reader.TryGetInt32(out var i)) + return i; + if (reader.TryGetInt64(out var l)) + return l; + // BigInteger could be added here. + if (reader.TryGetDouble(out var d)) + return d; + + throw new JsonException("Cannot parse number"); + } + case JsonTokenType.StartArray: + { + var list = new List(); + while (reader.Read()) + { + switch (reader.TokenType) + { + default: + list.Add(Read(ref reader, typeof(object), options)); + break; + case JsonTokenType.EndArray: + return list; + } + } + throw new JsonException(); + } + case JsonTokenType.StartObject: + IDictionary dict = new ExpandoObject(); + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.EndObject: + return dict; + case JsonTokenType.PropertyName: + var key = reader.GetString(); + reader.Read(); + dict[key] = Read(ref reader, typeof(object), options); + break; + default: + throw new JsonException(); + } + } + throw new JsonException(); + default: + throw new JsonException(string.Format("Unknown token {0}", reader.TokenType)); + } + } + + public override void Write( + Utf8JsonWriter writer, + object objectToWrite, + JsonSerializerOptions options) => + JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options); + } +} diff --git a/src/YesSql.Core/Serialization/JsonContentSerializer.cs b/src/YesSql.Core/Serialization/JsonContentSerializer.cs deleted file mode 100644 index bc40cff5..00000000 --- a/src/YesSql.Core/Serialization/JsonContentSerializer.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace YesSql.Serialization -{ - public class JsonContentSerializer : IContentSerializer - { - private readonly static JsonSerializerSettings _jsonSettings = new() - { - TypeNameHandling = TypeNameHandling.Auto, - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - NullValueHandling = NullValueHandling.Ignore - }; - - public object Deserialize(string content, Type type) - { - return JsonConvert.DeserializeObject(content, type, _jsonSettings); - } - - public dynamic DeserializeDynamic(string content) - { - return JsonConvert.DeserializeObject(content, _jsonSettings); - } - - public string Serialize(object item) - { - return JsonConvert.SerializeObject(item, _jsonSettings); - } - } -} diff --git a/src/YesSql.Core/Serialization/UtcDateTimeJsonConverter.cs b/src/YesSql.Core/Serialization/UtcDateTimeJsonConverter.cs new file mode 100644 index 00000000..0011c4e9 --- /dev/null +++ b/src/YesSql.Core/Serialization/UtcDateTimeJsonConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace YesSql.Serialization +{ + public class UtcDateTimeJsonConverter : JsonConverter + { + public static readonly UtcDateTimeJsonConverter Instance = new(); + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Debug.Assert(typeToConvert == typeof(DateTime)); + + if (!reader.TryGetDateTime(out DateTime value)) + { + value = DateTime.UtcNow; + } + + return value; + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToUniversalTime()); + } + } +} diff --git a/src/YesSql.Core/Store.cs b/src/YesSql.Core/Store.cs index bd5de1e4..094f3825 100644 --- a/src/YesSql.Core/Store.cs +++ b/src/YesSql.Core/Store.cs @@ -250,7 +250,7 @@ internal IEnumerable CreateDescriptors(Type target, string coll } } - return context.Describe(new[] { target }).ToList(); + return context.Describe([target]).ToList(); } private static Func MakeDescriptorActivator(Type type) diff --git a/src/YesSql.Core/YesSql.Core.csproj b/src/YesSql.Core/YesSql.Core.csproj index c6061e19..25ac38c5 100644 --- a/src/YesSql.Core/YesSql.Core.csproj +++ b/src/YesSql.Core/YesSql.Core.csproj @@ -5,7 +5,6 @@ - diff --git a/test/YesSql.Tests/CoreTests.cs b/test/YesSql.Tests/CoreTests.cs index 9da1eaa2..166ee51a 100644 --- a/test/YesSql.Tests/CoreTests.cs +++ b/test/YesSql.Tests/CoreTests.cs @@ -6401,6 +6401,94 @@ await session.SaveAsync(new Article Assert.Equal(10, result); } + [Fact] + public async Task PopulateIndexUsingGenericType() + { + var _configuration1 = CreateConfiguration(); + var store = await StoreFactory.CreateAndInitializeAsync(_configuration1); + await using (var connection = store.Configuration.ConnectionFactory.CreateConnection()) + { + await connection.OpenAsync(); + + await using var transaction = await connection.BeginTransactionAsync(store.Configuration.IsolationLevel); + var builder = new SchemaBuilder(store.Configuration, transaction); + + await builder + .DropMapIndexTableAsync(); + + await builder + .CreateMapIndexTableAsync(column => column + .Column(nameof(PropertyIndex.Name), col => col.WithLength(254)) + .Column(nameof(PropertyIndex.ForRent)) + .Column(nameof(PropertyIndex.IsOccupied)) + .Column(nameof(PropertyIndex.Location), col => col.WithLength(500)) + ); + + await builder + .AlterTableAsync(nameof(PropertyIndex), table => table + .CreateIndex("IDX_Property", "Name", "ForRent", "IsOccupied")); + + await transaction.CommitAsync(); + } + + + + var typeDef = new DynamicTypeDef() + { + NameSpace = "TestDynamicIndexNameSpace", + ClassName = "PropertyIndex", + Fields = new[] { + new DynamicField { Name="Name",FieldType=typeof(string)}, + new DynamicField { Name="ForRent",FieldType=typeof(bool)}, + new DynamicField { Name="IsOccupied",FieldType=typeof(bool)}, + new DynamicField { Name = "Location", FieldType = typeof(string)} + } + }; + + var dynamicType = DynamicTypeGeneratorSample.GenType(typeDef); + + PropertyDynamicIndexProvider.IndexTypeCache[dynamicType.FullName] = dynamicType; + + await using var session = store.CreateSession(); + session.RegisterIndexes([new PropertyDynamicIndexProvider()]); + + var property = new Property + { + Name = new string('*', 40), + IsOccupied = true, + ForRent = true, + Location = new string('*', 500) + }; + + await session.SaveAsync(property); + await session.SaveChangesAsync(); + + var testProperties = await session.Query(x => x.Id == 1).ListAsync(); + Assert.NotEmpty(testProperties); + session.Dispose(); + + + // In production environment, we may change this type at any time + + var changedType = DynamicTypeGeneratorSample.GenType(typeDef); + + Assert.NotEqual(changedType, dynamicType); + + // update index type cache + store.Configuration.IndexTypeCacheProvider?.UpdateCachedType(changedType); + + PropertyDynamicIndexProvider.IndexTypeCache[dynamicType.FullName] = changedType; + await using var session2 = store.CreateSession(); + session2.RegisterIndexes([new PropertyDynamicIndexProvider()]); + var testPropEntity = testProperties.FirstOrDefault(); + testPropEntity.Name = "Tom"; + await session2.SaveAsync(testPropEntity); + await session2.SaveChangesAsync(); + var result2 = await session2.Query(x=>x.Name=="Tom").ListAsync(); + Assert.Single(result2); + Assert.Equal("Tom", result2.FirstOrDefault().Name); + } + #region FilterTests [Fact] diff --git a/test/YesSql.Tests/Indexes/DynamicTypeGeneratorSample.cs b/test/YesSql.Tests/Indexes/DynamicTypeGeneratorSample.cs new file mode 100644 index 00000000..9b833ca1 --- /dev/null +++ b/test/YesSql.Tests/Indexes/DynamicTypeGeneratorSample.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using YesSql.Indexes; + +namespace YesSql.Tests.Indexes; +public class DynamicTypeGeneratorSample +{ + public static Type GenType(DynamicTypeDef dynamicTypeDef) + { + var nameSpace = dynamicTypeDef.NameSpace; + var indexTypeFullName = dynamicTypeDef.NameSpace + "." + dynamicTypeDef.ClassName; + var assemblyName = "DynamicTypesAssembly"; + // Create the dynamic assembly + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly( + new AssemblyName(assemblyName), AssemblyBuilderAccess.Run); + + // Create a dynamic module in the assembly + var moduleBuilder = assemblyBuilder.DefineDynamicModule(nameSpace); + // Define the type + var typeBuilder = moduleBuilder.DefineType(indexTypeFullName, TypeAttributes.Public); + + typeBuilder.SetParent(typeof(MapIndex)); + + foreach (var item in dynamicTypeDef.Fields) + { + BuildProperties(typeBuilder, item); + } + + // Create the type + var dynamicType = typeBuilder.CreateType(); + // Return the dynamic type + return dynamicType; + } + + private static PropertyBuilder BuildProperties(TypeBuilder typeBuilder, DynamicField item) + { + var propertyName = item.Name; + var propType = item.FieldType; + + // Define the property + var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propType, null); + var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); + + // Define the getter method + var getterBuilder = typeBuilder.DefineMethod("get_" + propertyName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propType, Type.EmptyTypes); + + ILGenerator getterIL = getterBuilder.GetILGenerator(); + getterIL.Emit(OpCodes.Ldarg_0); + getterIL.Emit(OpCodes.Ldfld, fieldBuilder); + getterIL.Emit(OpCodes.Ret); + + + // Define the setter method + var setterBuilder = typeBuilder.DefineMethod("set_" + propertyName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { propType }); + // Define the setter method + var setterIL = setterBuilder.GetILGenerator(); + setterIL.Emit(OpCodes.Ldarg_0); + setterIL.Emit(OpCodes.Ldarg_1); + setterIL.Emit(OpCodes.Stfld, fieldBuilder); + setterIL.Emit(OpCodes.Ret); + + // Set the getter and setter methods for the property + propertyBuilder.SetGetMethod(getterBuilder); + propertyBuilder.SetSetMethod(setterBuilder); + + return propertyBuilder; + } +} + +public class DynamicTypeDef +{ + public string NameSpace { get; set; } + public string ClassName { get; set; } + public IEnumerable Fields { get; set; } +} + +public class DynamicField +{ + public string Name { get; set; } + public Type FieldType { get; set; } +} \ No newline at end of file diff --git a/test/YesSql.Tests/Indexes/PropertyIndex.cs b/test/YesSql.Tests/Indexes/PropertyIndex.cs index 377becf8..6b2cde98 100644 --- a/test/YesSql.Tests/Indexes/PropertyIndex.cs +++ b/test/YesSql.Tests/Indexes/PropertyIndex.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using YesSql.Indexes; using YesSql.Tests.Models; @@ -26,4 +28,43 @@ public override void Describe(DescribeContext context) }); } } + + public class PropertyDynamicIndexProvider : IndexProvider + { + public static Dictionary IndexTypeCache = new Dictionary(); + + public override void Describe(DescribeContext context) + { + foreach (var dynamicType in IndexTypeCache.Values) + { + context + .For(dynamicType) + .Map(property => + { + // It's just a test. We only have one type + var obj = Activator.CreateInstance(dynamicType); + foreach (var prop in dynamicType.GetProperties()) + { + switch (prop.Name) + { + case "Name": + prop.SetValue(obj, property.Name); + break; + case "ForRent": + prop.SetValue(obj, property.ForRent); + break; + case "IsOccupied": + prop.SetValue(obj, property.IsOccupied); + break; + case "Location": + prop.SetValue(obj, property.Location); + break; + } + } + + return (MapIndex)obj; + }); + } + } + } } diff --git a/test/YesSql.Tests/Models/Animal.cs b/test/YesSql.Tests/Models/Animal.cs index 4f553378..47440844 100644 --- a/test/YesSql.Tests/Models/Animal.cs +++ b/test/YesSql.Tests/Models/Animal.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace YesSql.Tests.Models { @@ -6,7 +6,7 @@ public class Animal { public string Name { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string Color { get; set; } } } diff --git a/test/YesSql.Tests/Models/Drawing.cs b/test/YesSql.Tests/Models/Drawing.cs index bad6e985..b8057b1b 100644 --- a/test/YesSql.Tests/Models/Drawing.cs +++ b/test/YesSql.Tests/Models/Drawing.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text.Json.Serialization; namespace YesSql.Tests.Models { @@ -12,6 +13,9 @@ public Drawing() public IList Shapes { get; set; } } + [JsonPolymorphic] + [JsonDerivedType(typeof(Square), nameof(Square))] + [JsonDerivedType(typeof(Circle), nameof(Circle))] public abstract class Shape { public long Id { get; set; } diff --git a/test/YesSql.Tests/YesSql.Tests.csproj b/test/YesSql.Tests/YesSql.Tests.csproj index d5c532e0..0ed1020c 100644 --- a/test/YesSql.Tests/YesSql.Tests.csproj +++ b/test/YesSql.Tests/YesSql.Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net7.0;net8.0 latest YesSql.Tests YesSql.Tests