Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow describing an index from a given Type. #525

Closed
wants to merge 52 commits into from

Conversation

hyzx86
Copy link
Contributor

@hyzx86 hyzx86 commented Dec 28, 2023

Here is an example

public class PropertyDynamicIndexProvider : IndexProvider<Property>
{
    public override void Describe(DescribeContext<Property> context)
    {
        context
            .For(typeof(PropertyIndex))  // in addition to the existing ".For<PropertyIndex>()"
            .Map(property => new PropertyIndex
            {
                Name = property.Name,
                ForRent = property.ForRent,
                IsOccupied = property.IsOccupied,
                Location = property.Location
            });
    }
}

@hyzx86 hyzx86 changed the title remake DescribeContext<>.For Support dynamic Index type Remake DescribeContext<>.For Support dynamic Index type Dec 28, 2023
@hyzx86 hyzx86 changed the title Remake DescribeContext<>.For Support dynamic Index type Remake DescribeContext Support dynamic Index type Dec 29, 2023
README.md Outdated Show resolved Hide resolved
src/YesSql.Abstractions/Indexes/DescribeContext.cs Outdated Show resolved Hide resolved
src/YesSql.Abstractions/Indexes/DescribeContext.cs Outdated Show resolved Hide resolved
src/YesSql.Abstractions/Indexes/DescribeContext.cs Outdated Show resolved Hide resolved
src/YesSql.Abstractions/Indexes/DescribeFor.cs Outdated Show resolved Hide resolved
src/YesSql.Core/Commands/IndexCommand.cs Outdated Show resolved Hide resolved
test/YesSql.Tests/Indexes/PropertyIndex.cs Show resolved Hide resolved
test/YesSql.Tests/SqliteTests.cs Outdated Show resolved Hide resolved
@hyzx86
Copy link
Contributor Author

hyzx86 commented Dec 29, 2023

Hi @MikeAlhayek , All problems resolved

MikeAlhayek
MikeAlhayek previously approved these changes Dec 29, 2023
Copy link
Collaborator

@MikeAlhayek MikeAlhayek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hyzx86 I simplified things. I also removed the update you had to the readme file. That can be part of the Wiki once this PR is merged.

@MikeAlhayek MikeAlhayek changed the title Remake DescribeContext Support dynamic Index type Allow describing an index from a given Type. Dec 29, 2023
@hyzx86
Copy link
Contributor Author

hyzx86 commented Dec 30, 2023

@MikeAlhayek Thanks for your help .
I didn't notice that this test should be moved to CoreTests because I copied it from one of the Sqlite Index tests above

@MikeAlhayek
Copy link
Collaborator

I guess since it's just to ensure the index is created using type, we could also have left it in the SQLite only. It does not matter either way. It does not hurt running it on all databases either

@@ -67,20 +68,13 @@ protected static void GetProperties(DbCommand command, object item, string suffi

protected static PropertyInfo[] TypePropertiesCache(Type type)
{
if (TypeProperties.TryGetValue(type, out var pis))
if (TypeProperties.TryGetValue(type.FullName, out var pis))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is mainly to avoid a scenario
Because dynamically typed indexes are allowed in the code, there is no guarantee that the type will not change during use,
For example, when I save the type, it has 5 fields, and then I add a new field, then I need to regenerate the type index.
Two types of inconsistent errors occur

2023-12-29 10:42:33.4084|SalesProtalDev|00-91aa5d5c1f96b614b2ebf3f2e6dfdbd5-5d2a9c17352de5d4-00||OrchardCore.ContentManagement.DefaultContentManager|ERROR|IContentHandler thrown from OrchardCore.Contents.AuditTrail.Handlers.AuditTrailContentHandler by InvalidCastException System.InvalidCastException: [A]EasyOC.DynamicIndex.AmisSchemaDIndex cannot be cast to [B]EasyOC.DynamicIndex.AmisSchemaDIndex. Type A originates from 'DynamicTypesAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' in a byte array. Type B originates from 'DynamicTypesAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' in a byte array.
at YesSql.Serialization.PropertyInfoAccessor.Invoker`2.Invoke(Object target)
at YesSql.Commands.IndexCommand.GetProperties(DbCommand command, Object item, String suffix, ISqlDialect dialect)
at YesSql.Commands.CreateIndexCommand.AddToBatch(ISqlDialect dialect, List`1 queries, DbCommand batchCommand, List`1 actions, Int32 index)
at YesSql.Session.BatchCommands()
at YesSql.Session.FlushAsync()
at YesSql.Session.FlushAsync()
at YesSql.Services.DefaultQuery.CountAsync()
at YesSql.Services.DefaultQuery.QueryIndex`1.YesSql.IQueryIndex<T>.CountAsync()
at OrchardCore.Contents.AuditTrail.Handlers.AuditTrailContentHandler.RecordAuditTrailEventAsync(String name, IContent content)
at OrchardCore.Modules.InvokeExtensions.InvokeAsync[TEvents,T1](IEnumerable`1 events, Func`3 dispatch, T1 arg1, ILogger logger) at YesSql.Serialization.PropertyInfoAccessor.Invoker`2.Invoke(Object target)
at YesSql.Commands.IndexCommand.GetProperties(DbCommand command, Object item, String suffix, ISqlDialect dialect)
at YesSql.Commands.CreateIndexCommand.AddToBatch(ISqlDialect dialect, List`1 queries, DbCommand batchCommand, List`1 actions, Int32 index)
at YesSql.Session.BatchCommands()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the index type may change while the program is running.

Just like this:

thrown from OrchardCore.Contents.AuditTrail.Handlers.AuditTrailContentHandler by InvalidCastException System.InvalidCastException: [A]EasyOC.DynamicIndex.AmisSchemaDIndex cannot be cast to [B]EasyOC.DynamicIndex.AmisSchemaDIndex. Type A originates from 'DynamicTypesAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' in a byte array. Type B originates from 'DynamicTypesAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' in a byte array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be no way to reproduce this scenario in a unit test, or to reproduce this scenario would require a complicated process

@hyzx86
Copy link
Contributor Author

hyzx86 commented Dec 30, 2023

@MikeAlhayek I reupdated the code

        protected static PropertyInfo[] TypePropertiesCache(Type type)
        {
            if (TypeProperties.TryGetValue(type, out var pis))
            {
                return pis;
            }

+            // If the same name type is already cached, it should be removed from the cache.
+            var existsingType = TypeProperties.Keys.FirstOrDefault(x => x.FullName == type.FullName);
+            if (existsingType != null)
+            {
+                var existsingProps = TypeProperties[existsingType];
+                foreach (var prop in existsingProps)
+                {
+                    PropertyAccessors.Remove(prop, out _);
+                }
+
+                TypeProperties.Remove(existsingType, out _);
+            }

            var properties = type.GetProperties().Where(IsWriteable).ToArray();
            TypeProperties[type] = properties;
            return properties;
        }

@hyzx86 hyzx86 marked this pull request as ready for review January 2, 2024 15:15
@MikeAlhayek
Copy link
Collaborator

@hyzx86

OrchardCore Is a multi-tenant system, it will certainly initialize IStore many times, which is not the problem of OC.

OC registers a singleton IStore for each tenant. During registering the IStore we register all indexes as you can see here
https://github.com/OrchardCMS/OrchardCore/blob/ef1eaab35f571fe9500dfe9099a6154fef0a4c9e/src/OrchardCore/OrchardCore.Data.YesSql/OrchardCoreBuilderExtensions.cs#L108-L114

so it wouldn't be creating multiple IStore instances. Maybe the issue is in something else.

@MikeAlhayek MikeAlhayek dismissed their stale review January 2, 2024 18:09

The code have changes since it was approved

@hyzx86
Copy link
Contributor Author

hyzx86 commented Jan 3, 2024

OC registers a singleton IStore for each tenant. During registering the IStore we register all indexes as you can see here https://github.com/OrchardCMS/OrchardCore/blob/ef1eaab35f571fe9500dfe9099a6154fef0a4c9e/src/OrchardCore/OrchardCore.Data.YesSql/OrchardCoreBuilderExtensions.cs#L108-L114

so it wouldn't be creating multiple IStore instances. Maybe the issue is in something else.

Not really, pls see session.RegisterIndexes

@MikeAlhayek
Copy link
Collaborator

@hyzx86 have you tried implementing IIndexProvider interface for indexes that use generic type instead of ‘IIndexProvider`?

@hyzx86
Copy link
Contributor Author

hyzx86 commented Jan 3, 2024

@hyzx86 have you tried implementing IIndexProvider interface for indexes that use generic type instead of ‘IIndexProvider`?

Yes, but eventually I gave up
The TypeProperties cache actually holds The result type of the map function

IndexDescriptor<T, TIndex, TKey>.IndexType

It's only used when you delete an index, because the map index returns null when you delete an index, so you can't get its type

in Store.cs

Clearly limit the use of DescribeContext<> Maybe we can use a factory method here and implement it in the interface

        private static Func<IDescriptor> MakeDescriptorActivator(Type type)
        {
            var contextType = typeof(DescribeContext<>).MakeGenericType(type);
            return Expression.Lambda<Func<IDescriptor>>(Expression.New(contextType)).Compile();
        }

@MikeAlhayek
Copy link
Collaborator

If you use IIndexProvider then you would provide the type of index using ForType() I never tried it. But, this should work. From there you can caching the dynamic type not the type provided in IIndexProvider<T>

@MikeAlhayek
Copy link
Collaborator

If you need to create IndexTypeCacheProvider, the default implementation should solve the problem you are facing. Meaning your comment have to be addressed by default otherwise you're code can't be accepted since you are adding API that allows generic,

This interface needs to be reimplemented when dynamic type and multi-tenant scenarios are used,

You may want to add interface to the abstraction project maybe like this

public interface IIndexTypeCacheProvider
{
    PropertyInfoAccessor GetPropertyInfoAccessor(PropertyInfo property);

    PropertyInfo[] GetProperties(Type type);

    void Update(Type type);
}

The IndexTypeCacheProvider can go into the Core project.

But, I am not sure that the complexity added by IndexTypeCacheProvider is needed. Maybe @sebastienros can provide more feedback on the approach you taken.

@hyzx86
Copy link
Contributor Author

hyzx86 commented Jan 3, 2024

@MikeAlhayek updated the default index cache key

Now , you can modify the test PopulateIndexUsingGenericType

Comment on the following line

            // update index type cache
            store1.Configuration.IndexTypeCacheProvider?.UpdateCachedType(changedType);

And run the test ,Errors will be reproduced

There is nothing wrong with using the full name of the type as the Key for the cache.

As I mentioned before, dynamically indexed types may change the list of fields during the program run, so each time an indexed field is modified, a new type is generated.
We can call IndexTypeCacheProvider. UpdateCachedType(Type) updates the cached type after regenerating the type

However, in an OC or other project , it may be better to replace it with an in-memory cache or a distributed cache, since both of these are tenant isolated in an OC.

And we can define the full name of the class to start with the tenant name when using dynamic type generation

{
return pis;
}

// Clean up cache entries with the same name and different type
var exists = TypeProperties.Keys.FirstOrDefault(x => x.FullName == type.FullName);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ensure that only meaningful types are cached, cached type with the same name will cleared

@@ -67,20 +68,13 @@ protected static void GetProperties(DbCommand command, object item, string suffi

protected static PropertyInfo[] TypePropertiesCache(Type type)
{
if (TypeProperties.TryGetValue(type, out var pis))
if (TypeProperties.TryGetValue(type.FullName, out var pis))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be no way to reproduce this scenario in a unit test, or to reproduce this scenario would require a complicated process

getterIL.Emit(OpCodes.Ldfld, fieldBuilder);
getterIL.Emit(OpCodes.Ret);


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propType, null);
FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);

// 使用 IL API 生成类型属性访问器
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// Return the dynamic type
return dynamicType;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

using System.Reflection.Emit;
using YesSql.Indexes;

namespace YesSql.Tests.Indexes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Assert.NotEmpty(testProperties2);

}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

protected bool IsWriteable(PropertyInfo pi)
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MikeAlhayek Because this method is needed for caching in Yessql, I changed the original IIndexTypeCacheProvider to IndexTypeCacheProvider and included some virtual methods

/// This interface needs to be reimplemented when dynamic type and multi-tenant scenarios are used,
/// using tenant separated cache media insteadexTypeCacheProvider
/// </summary>
IndexTypeCacheProvider IndexTypeCacheProvider { get; set; }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MikeAlhayek We need to add it to the IConfiguration so that it can be overridden by other implementations
So I extracted IndexTypeCacheProvider into the Abstraction project

Copy link
Contributor Author

@hyzx86 hyzx86 Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking so long to reply. Because I didn't submit my comments 😢
I always wondered why there was a Pendding mark on my reply. I just figured it out.

# Conflicts:
#	src/Directory.Packages.props
@hyzx86
Copy link
Contributor Author

hyzx86 commented May 4, 2024

After a period of test run, it is found that this method is not reliable

Next I'm going to try to implement IIndexCommand through external code

@hyzx86 hyzx86 closed this May 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants