Dynamic Linq selector for objects

I have a dynamic selector expression that creates an anonymous type. It works fine on linq for objects, but on linq for entities it throws:

Attempt 1

NotSupportedException

In LINQ objects, only parameterless constructors and initializers are supported.

Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
    var newExpression = Expression.New(
        typeof(T).GetConstructor(typeof(T).GenericTypeArguments),
        userParam,
        Expression.Constant("X"),
        Expression.Constant("Y")
    );
    return Expression.Lambda<Func<User, T>>(newExpression, userParam);
}

var userParam = Expression.Parameter(typeof(User), "u");
var obj = new { User = new User(), Address = string.Empty, Fax = string.Empty };
var arr = context.Set<T>()
    .Select(DynamicSelect(obj, userParam))
    .ToArray();

      

Attempt 2 . If I create a custom type, it works, but I don't want to because I want to reuse this helper method without creating an additional custom type for each object, I want to be able to pass a consumer based type.

public class Container
{
    public User User { get; set; }
    public string Address { get; set; }
    public string Fax { get; set; }
}
Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
    var initExpression = Expression.MemberInit(
        Expression.New(typeof(T)),
        Expression.Bind(typeof(T).GetProperty("User"), userParam),
        Expression.Bind(typeof(T).GetProperty("Address"), Expression.Constant("X")),
        Expression.Bind(typeof(T).GetProperty("Fax"), Expression.Constant("Y"))
    );
    return Expression.Lambda<Func<User, T>>(initExpression, userParam);
}

var userParam = Expression.Parameter(typeof(User), "u");
var arr = context.Set<T>()
    .Select(DynamicSelect<Container>(null, userParam))
    .ToArray();

      

Attempt 3 , I also tried to use Tuple<User, string, string>

but it is not supported either.

NotSupportedException

LINQ to Entities does not recognize the method "System.Tuple`3 [User, System.String, System.String] Create [User, String, String] (User, System.String, System.String) 'method, and this method cannot be translated into a store expression.

Expression<Func<User, T>> DynamicSelect<T>(T obj, ParameterExpression userParam)
{
    var createExpression = Expression.Call(
        typeof(Tuple), 
        "Create", 
        typeof(T).GenericTypeArguments,
        userParam,
        Expression.Constant("X"), 
        Expression.Constant("Y"));
    return Expression.Lambda<Func<User, T>>(createExpression, userParam);
}

var userParam = Expression.Parameter(typeof(User), "u");
var arr = context.Set<User>()
    .Select(DynamicSelect<Tuple<User, string, string>>(null, userParam))
    .ToArray();

      

Please, help.

Update

I've tried to reuse this helper method on any consumer (User, Customer, Associate, etc.) without having a specific implementation for each user.

The class structure looks like this.

public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public virtual ICollection<Contact> Contacts { get; set; }
}
public class Customer
{
    public int Id { get; set; }
    public string CompanyName { get; set; }
    public virtual ICollection<Contact> Contacts { get; set; }
}
public class Contact
{
    public int Id { get; set; }
    public string Type { get; set; }
    public string Content { get; set; }
}
public class UserDto
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public ContactDto Contact { get; set; }
}
public class CustomerDto
{
    public int Id { get; set; }
    public string CompanyName { get; set; }
    public ContactDto Contact { get; set; }
}
public class ContactDto
{
    public string Email { get; set; }
    public string Address { get; set; }
    public string Fax { get; set; }
    // other contact informations
}

      

And I have many contacts that may be different for each user.

var users = context.Set<User>()
    .Select(x => new UserDto
    {
        Id = x.Id,
        UserName = x.UserName,
        Contact = new ContactDto
        {
            Email = x.Contacts.Where(c => c.Type == "Email").Select(c => c.Value).FirstOrDefault()
        }
    })
    .ToArray();

var customers = context.Set<Customer>()
    .Select(x => new CustomerDto
    {
        Id = x.Id,
        CompanyName = x.CompanyName,
        Contact = new ContactDto
        {
            Address = x.Contacts.Where(c => c.Type == "Address").Select(c => c.Value).FirstOrDefault(),
            Fax = x.Contacts.Where(c => c.Type == "Fax").Select(c => c.Value).FirstOrDefault(),
        }
    })
    .ToArray();

      

And edited the expression x.Contacts.Where(c => c.Type == "Address").Select(c => c.Value).FirstOrDefault()

in the expression, but I cannot use it directly inside the method, for example:

var users = context.Set<User>()
    .Select(x => new UserDto
    {
        Id = x.Id,
        UserName = x.UserName,
        Contact = new ContactDto
        {
            Email = GetContactExpression("Email").Compile()(x)
        }
    })
    .ToArray();

      

It throws an error because the method is Invoke

not supported in linq for expression, so I need to refactor the whole expression Select

, but I need to make it generic ( User

has UserName

, but Customer

has CompanyName

any other information) and probably passes the contact type (s) too after that ... The expected output at the moment would be something like:

var obj = new { User = new User(), Email = "" };
var users = context.Set<User>()
    .Select(x => DynamicSelect(obj))
    .Select(x => new UserDto
    {
        Id = x.User.Id,
        UserName = x.User.UserName,
        Contact = new ContactDto
        {
            Email = x.Email
        }
    })
    .ToArray();

      

+3


source to share


1 answer


When I compare your expression to the one generated by the compiler for something like the u => new { User = u, Address = "X", Fax = "Y" }

difference is what the last one has filled in Members

.

I don't quite understand what is the reason for Members

existing at all, but I will try to install it, I think it will fix your problem. The code might look something like this:



var constructor = typeof(T).GetConstructor(typeof(T).GenericTypeArguments);

var newExpression = Expression.New(
    constructor,
    new Expression[] { userParam, Expression.Constant("X"), Expression.Constant("Y") },
    constructor.GetParameters().Select(p => typeof(T).GetProperty(p.Name)));

      

+4


source







All Articles