Home > front end >  How to cast TResult which can be an object or List<object> inside an Expression<Func<T,T
How to cast TResult which can be an object or List<object> inside an Expression<Func<T,T

Time:10-07

As the title says, "How to cast TResult which can be an object or List<object> inside an Expression<Func<T,TResult?>?"

    public Task ExplicitLoadAsync<TProperty>(T entity, Expression<Func<T, TProperty?>> propertyExpression) where TProperty : class
    {
        var typeOfProp = typeof(TProperty);
        if (typeOfProp.IsInterface && typeOfProp.GetGenericTypeDefinition() == typeof(ICollection<>))
        {
            var collectionExpression = propertyExpression as Expression<Func<T, IEnumerable<TProperty>>>;
            if (collectionExpression is not null)
            {
                return _dbContext.Entry(entity).Collection(collectionExpression).LoadAsync();
            }
        }

        return _dbContext.Entry(entity).Reference(propertyExpression).LoadAsync();
    }

collectionExpression will always be null, the issue is that Reference() only accept IEnumerable<TProperty> and overload is not possible because it will always use the method with TProperty instead of Expression<Func<T, IEnumerable<TProperty>>> propertyExpression

The reason is the cast is wrong because it will be IEnumerable<IEnumerable<...>> How to solve it?

One way is to have two methods with different names, but I would like to skip that as that would require more refactoring in the whole code base.

CodePudding user response:

This solution is based on Guru Stron's deleted answer

public async Task ExplicitLoadAsync<T, TProperty>(T entity, Expression<Func<T, IEnumerable<TProperty>>> propertiesExpression) where T : class where TProperty : class
  => await _dbContext.Entry(entity).Collection(propertiesExpression).LoadAsync();

public async Task ExplicitLoadAsync<T, TProperty>(T entity, Expression<Func<T, TProperty?>> propertyExpression) where T : class where TProperty : class
 => await _dbContext.Entry(entity).Reference(propertyExpression).LoadAsync();

Here the trick is to use different parameter names:

  • Expression<Func<T, IEnumerable<TProperty>>> propertiesExpression
  • Expression<Func<T, TProperty?>> propertyExpression

which allows you to be able to call both overloads

ExplicitLoadAsync<Entity, string>(enity, e => e.SingleString);
ExplicitLoadAsync<Entity, string>(enity, propertiesExpression: e => e.CollectionOfStrings);

CodePudding user response:

I just want to post another answer to just solve my case without calling the method with parameter name.

    public Task ExplicitLoadAsync<TProperty>(T entity, Expression<Func<T, TProperty?>> propertyExpression) where TProperty : class
    {
        var typeOfProp = typeof(TProperty);
        if (typeOfProp.IsInterface && typeOfProp.GetGenericTypeDefinition() == typeof(ICollection<>))
        {
            var pathName = PropertyPath<T>.GetAsStr(propertyExpression);
            return _dbContext.Entry(entity).Collection(pathName).LoadAsync();
        }

        return _dbContext.Entry(entity).Reference(propertyExpression).LoadAsync();
    }

Here is the utility class:

public static class PropertyPath<TSource>
{
    public static IReadOnlyList<MemberInfo> Get<TResult>(Expression<Func<TSource, TResult>> expression)
    {
        var visitor = new PropertyVisitor();
        visitor.Visit(expression.Body);
        visitor._path.Reverse();
        return visitor._path;
    }

    public static string GetAsStr<TResult>(Expression<Func<TSource, TResult>> expression)
    {
        return string.Join(".", Get(expression).Select(p => p.Name));
    }

    private sealed class PropertyVisitor : ExpressionVisitor
    {
        internal readonly List<MemberInfo> _path = new();

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Member is not PropertyInfo)
            {
                throw new ArgumentException("The path can only contain properties", nameof(node));
            }

            _path.Add(node.Member);
            return base.VisitMember(node);
        }
    }
}
  • Related