0

I have an efcore extension, that you find below. It allows me to select using a property path as string:

_dbContext.MyModel.Select("Name")    

instead of

_dbContext.MyModel.Select(x => x.Name)

This select returns IQueryable<string>. In addition I want to return the Id of the model. Without an extension it would look like this:

_dbContext.MyModel.Select(x => new { Id = x.Id, Name = x.Name });

I want to be able to call e.g.:

_dbContext.MyModel.Select("Name", withId: true)

and return { Id = x.Id, Name = x.Name }. The source type of Id can be either string or Guid. The destination type should be string. Any idea how to achieve that given the implementation below? The extension could also return { string?, string }, string? being the id.

Efcore extension:

public static IQueryable<string> SelectFromPropertyPath<T>(this IQueryable<T> query, string propertyPath)
{
    var parameter = Expression.Parameter(typeof(T), "e");
    var property = MakePropPath(parameter, propertyPath);

    if (property.Type != typeof(string))
    {
        if (property.Type != typeof(object))
        {
            property = Expression.Convert(property, typeof(object));
        }

        property = Expression.Call(_toStringMethod, property);
    }

    var lambda = Expression.Lambda<Func<T, string>>(property, parameter);

    return query.Select(lambda);
}

private static Expression MakePropPath(Expression objExpression, string path)
    => path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);


private static readonly MethodInfo _toStringMethod = typeof(Convert).GetMethods()
    .Single(m =>
        m.Name == nameof(Convert.ToString) && m.GetParameters().Length == 1 &&
        m.GetParameters()[0].ParameterType == typeof(object)
    );
2
  • Actually we can project into ExpandoObject. I think it will be well serialized into JSON. It is acceptable? Another variant to create class ValueWithId and return IQueryable<ValueWithId> Commented Jun 30, 2022 at 7:14
  • @SvyatoslavDanyliv Thx for the ideas, again! I would like to see the second variant using ValueWithId. Commented Jun 30, 2022 at 8:16

1 Answer 1

1

The following extension selects Value and Id based on property names and returns IQueryable<ValueWithId> which can be used later for filtering, grouping, materialization, etc.

var query1 = _dbContext.MyModel.Select("Name"); 
var query2 = _dbContext.MyModel.Select("Name", "OtherId");

And realization:

public static class QueryableExtensions
{
    public class ValueWithId
    {
        public string Id { get; set; }
        public string Value { get; set; }
    }

    public static IQueryable<ValueWithId> Select<T>(this IQueryable<T> query, string valuePath, string idPath = "Id")
    {
        var parameter = Expression.Parameter(typeof(T), "e");
        var idProperty = MakePropPath(parameter, idPath);
        var valueProperty = MakePropPath(parameter, valuePath);

        idProperty = EnsureString(idProperty);
        valueProperty = EnsureString(valueProperty);

        var body = Expression.MemberInit(Expression.New(_valueWithIdConstructor), 
            Expression.Bind(_idProp, idProperty), 
            Expression.Bind(_valueProp, valueProperty));

        var lambda = Expression.Lambda<Func<T, ValueWithId>>(body, parameter);

        return query.Select(lambda);
    }

    static Expression MakePropPath(Expression objExpression, string path)
    {
        return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
    }

    private static Expression EnsureString(Expression expression)
    {
        if (expression.Type == typeof(string))
            return expression;

        if (expression.Type != typeof(object))
            expression = Expression.Convert(expression, typeof(object));

        expression = Expression.Call(_toStringMethod, expression);

        return expression;
    }

    private static MethodInfo _toStringMethod = typeof(Convert).GetMethods()
        .Single(m =>
            m.Name == nameof(Convert.ToString) && m.GetParameters().Length == 1 &&
            m.GetParameters()[0].ParameterType == typeof(object)
        );

    private static readonly ConstructorInfo _valueWithIdConstructor = typeof(ValueWithId).GetConstructors().Single();
    private static readonly PropertyInfo _idProp = typeof(ValueWithId).GetProperty(nameof(ValueWithId.Id)) ?? throw new InvalidOperationException();
    private static readonly PropertyInfo _valueProp = typeof(ValueWithId).GetProperty(nameof(ValueWithId.Value)) ?? throw new InvalidOperationException();
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.