redis-om-dotnet icon indicating copy to clipboard operation
redis-om-dotnet copied to clipboard

Not working as expected, when using IQueryProvider.CreateQuery

Open zulander1 opened this issue 1 year ago • 3 comments

I am using Automapper to map internal object to a DTO, however it is throwing

System.MissingMethodException: 'Constructor on type 'Redis.OM.Searching.RedisCollection1[[System.Linq.IQueryable1[[CustomerDTO, Redis_Graphql, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]]' not found.'

on


public IQueryable CreateQuery(Expression expression)
{
	Type elementType = expression.Type;
	try
	{
		return
		   (IQueryable)Activator.CreateInstance(
			   typeof(RedisCollection<>).MakeGenericType(elementType),
			   this,
			   expression);
	}
	catch (TargetInvocationException e)
	{
		if (e.InnerException == null)
		{
			throw;
		}

		throw e.InnerException;
	}
}

runable code:


using AutoMapper;
using Redis.OM;
using Redis.OM.Modeling;
using AutoMapper.QueryableExtensions;

var provider = new RedisConnectionProvider("redis://localhost:6379");
provider.Connection.CreateIndex(typeof(CustomerModel));
var customers = provider.RedisCollection<CustomerModel>();

// Insert customer
customers.Insert(new CustomerModel()
{
    Id = 1,
    FirstName = "James",
    LastName = "Bond",
}, WhenKey.NotExists);

customers.Insert(new CustomerModel()
{
    Id = 2,
    FirstName = "Dr",
    LastName = "No",
}, WhenKey.NotExists);

var materialized= customers.ToList();

var mapperConfig = new MapperConfiguration(f => f.AddProfile(new CustomerDTOProfile()));
var mapper = new Mapper(mapperConfig);
var mapped = mapper.Map<IEnumerable<CustomerDTO>>(materialized);

var notWorking = customers.ProjectTo<CustomerDTO>(mapperConfig);

public class CustomerDTOProfile : Profile
{
    public CustomerDTOProfile()
    {
        CreateMap<CustomerModel, CustomerDTO>();
    }
}

[Document(StorageType = StorageType.Json, Prefixes = new[] { Prefix }, IndexName = Prefix + ":idx")]
public class 
{
    public const string Prefix = "customer";

    [Indexed][RedisIdField] public int Id { get; set; }
    [Indexed] public string FirstName { get; set; }
    [Indexed] public string LastName { get; set; }
}

public class CustomerDTO
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

zulander1 avatar Jan 13 '24 13:01 zulander1

Adding, this would solve the constructor issue but it's going into the exception of "The root attribute of a Redis Collection must be decorated with a DocumentAttribute"... perhaps adding a more generic constructor would help :thinking: ?

object[] args = new object[] { Connection, _saveState, _chunkSize };
return (IQueryable)Activator.CreateInstance(typeof(RedisCollection<>).MakeGenericType(elementType), args);

zulander1 avatar Jan 13 '24 17:01 zulander1

I'm encountering the same issue with AutoMapper:

System.MissingMethodException: 'Constructor on type >'Redis.OM.Searching.RedisCollection1[[System.Linq.IQueryable1[[Caching.Redis.Infra.RedisOmitAttributesCollectionTe>sts.RedisOmitAttributesCollectionTests+ItemEntity, Caching.Redis.Infra, Version=1.0.0.0, Culture=neutral.

The issue is related to the implementation of the RedisQueryProvider.CreateQuery(Expression) method:

/// <inheritdoc/>
        public IQueryable CreateQuery(Expression expression)
        {
            Type elementType = expression.Type;
            try
            {
                return
                   (IQueryable)Activator.CreateInstance(
                       typeof(RedisCollection<>).MakeGenericType(elementType),
                       this,
                       expression);
            }
            catch (TargetInvocationException e)
            {
                if (e.InnerException == null)
                {
                    throw;
                }

                throw e.InnerException;
            }
        }

There are no valid constructors for RedisCollection<T>(RedisQueryProvider, Expression). You can reproduce the issue with the following code:

var redisCollection = new RedisCollection<ItemEntity>(_redisConnectionProvider.Connection, false, 100);
var query = redisCollection.Provider.CreateQuery(expression);

StackTrace:

at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) at System.Activator.CreateInstance(Type type, Object[] args) at Redis.OM.Searching.RedisQueryProvider.CreateQuery(Expression expression) at Caching.Redis.Infra.RedisOmitAttributesCollectionTests. <FindWhenExpressionProvidedShouldReturnEntity2>d__11.MoveNext() in C:\Users\User\OneDrive - e\caching.redis\test\Caching.Redis.Infra\RedisOmitAttributesCollectionTests\RedisOmitAttributesCollectionTests.cs:line 138

The fix seems straightforward - we can take the implementation from the CreateQuery<TElement>(Expression) method and adapt it. Here is the code:

        /// <inheritdoc/>
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
            where TElement : notnull
        {
            var booleanExpression = expression as Expression<Func<TElement, bool>>;
            return new RedisCollection<TElement>(this, expression, StateManager, booleanExpression, true);
        }

What do you think?

shapik1234 avatar Jul 09 '24 20:07 shapik1234

Did this problem ever get resolved? I'm running into the exact same problem today while trying to use the AutoMapper IQueryableExtensions with RedisConnection.

If not, is there a workaround?

mfaulcon avatar Jan 29 '25 20:01 mfaulcon