动态选择器Linq To Entities

时间:2014-09-30 18:04:16

标签: c# linq entity-framework linq-expressions

我有一个动态选择器表达式,它产生匿名类型。它在linq到对象中运行良好,但在linq到实体中,它抛出:

尝试1

  

NotSupportedException异常

     

LINQ to Entities中仅支持无参数构造函数和初始值设定项。

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();

尝试2 ,如果我创建了一个自定义类型,它正在工作,但我不想这样做,因为我想重用这个帮助方法而不为每个实体创建额外的自定义类型,我想要能够根据消费者传递类型。

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();

尝试3 ,我也尝试使用Tuple<User, string, string>,但也不支持。

  

NotSupportedException异常

     

LINQ to Entities无法识别该方法   “System.Tuple`3 [用户,System.String,System.String]   创建[User,String,String](User,System.String,System.String)'   方法,并且此方法无法转换为商店表达式。

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();

请帮忙。

更新

我试图在任何消费者(用户,客户,员工等)中重复使用此辅助方法,而无需为每个消费者提供特定的实现。

类结构看起来像。

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
}

我有很多联系人,每个消费者可能会有所不同。

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();

并将x.Contacts.Where(c => c.Type == "Address").Select(c => c.Value).FirstOrDefault()重构为表达式,但我不能直接在方法中使用它,如:

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

它会抛出错误,因为linq to expression不支持Invoke方法,因此我需要重构整个Select表达式,但我需要将其设为泛型(UserUserName,但CustomerCompanyName,以及任何其他信息),并且可能在此问题解决后传递联系类型。目前的预期结果将是微不足道的:

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();

1 个答案:

答案 0 :(得分:4)

当我将你的表达式与编译器为u => new { User = u, Address = "X", Fax = "Y" }之类的东西进行比较时,不同之处在于后者填充了Members

我不太明白Members存在的原因是什么,但我会尝试设置它,我的猜测是它会解决你的问题。代码可能类似于:

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)));