如何从C#中的IQueryable <t>中选择特定列

时间:2015-11-26 22:37:44

标签: c# entity-framework entity iqueryable

我需要从IQueryble中动态选择一些列。

示例:

IQueryable<Car> query = (from a in _dbContext.Table1
                     select new Car
                     {
                         Prop1 = a.Prop1,
                         Prop2 = a.Prop2,
                         Prop3 = a.Prop3
                     });

我需要做类似的事情:

var lambda = new Test().CreateNewStatement<Car>("Prop1,Prop2");

我正在使用该功能:

public Func<T, T> CreateNewStatement<T>(string fields)
    {
        var xParameter = Expression.Parameter(typeof(T), "o");
        var xNew = Expression.New(typeof(T));

        var bindings = fields.Split(',').Select(o => o.Trim())
            .Select(o =>
            {
                var mi = CustomGetType<T>(o);
                var xOriginal = Expression.Property(xParameter, mi);
                return Expression.Bind(mi, xOriginal);
            }
        );
        var xInit = Expression.MemberInit(xNew, bindings);
        var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
        return lambda.Compile();
    }

和电话:

var lambda = new Test().CreateNewStatement<Car>("Prop1");
var ax = query.Select(lambda);
var result = ax.ToList();

但在数据库中,查询错误:

SELECT 
[Limit1].[C1] AS [C1], 
[Limit1].[Prop1] AS [Prop1], 
[Limit1].[Prop2] AS [Prop2], 
[Limit1].[Prop3] AS [Prop3], 
FROM ( ...

数据库中的正确搜索应该是:

SELECT 
[Limit1].[C1] AS [C1], 
[Limit1].[Prop1] AS [Prop1]
FROM ( ...

我需要操纵IQueryable&lt; T>在数据库中搜索之前。

1 个答案:

答案 0 :(得分:2)

您遇到的问题是您已将Func<User, User>传递给Select。这实质上意味着该方法在内存中执行,而不是在数据库中执行。

实体框架无法从已编译的函数生成SQL代码。

当你这样写:

var users = Users.Select(u => new User { a = 1 });

您正在将Expression<Func<User, User>>实体框架 CAN 转换为SQL。

所以,你需要改变的是你的功能的结束。而不是:

var lambda = Expression.Lambda<Func<T, T>>(xInit, xParameter);
return lambda.Compile()

你想简单地做

return Expression.Lambda<Func<T, T>>(xInit, xParameter);

并将函数的返回类型更改为Expression<Func<T,T>>

<强> 无论其

无论出于何种原因,EntityFramework 都不会允许您构造作为实体的对象。 Linq-To-Sql 允许这样做,并且运行上述更改的代码确实有效,并且只选择一列。

基本上这意味着我们需要更改表达式以返回一个类 not 实体框架使用的实体。

所以,而不是写这个:

var users = Users.Select(u => new User { a = 1 });

我们需要写下这个:

var users = Users.Select(u => new NotAnEntityUser { a = 1 });

然后我们需要更改方法以返回NotAnEntityUser而不是User

public class Test
{
    public Expression<Func<TEntityType, TModelType>> CreateNewStatement<TEntityType, TModelType>(string fields)
    {
        var xParameter = Expression.Parameter(typeof (TEntityType), "o");
        var xNew = Expression.New(typeof (TModelType));

        var bindings = fields.Split(',').Select(o => o.Trim())
            .Select(paramName =>
            {
                var xOriginal = Expression.Property(xParameter, typeof(TEntityType).GetProperty(paramName));
                return Expression.Bind(typeof(TModelType).GetProperty(paramName), xOriginal);
            }
            );
        var xInit = Expression.MemberInit(xNew, bindings);

        var lambda = Expression.Lambda<Func<TEntityType, TModelType>>(xInit, xParameter);
        return lambda;
    }
}

定义我们的实体和模型:

public class User
{
    public virtual string Id { get; set; }
    public virtual string UserName { get; set; }
    public virtual string Salt { get; set; }

    public static implicit operator User(NotAnEntityUser o)
    {
        return new User { Id = o.Id, UserName = o.UserName, Salt = o.Salt };
    }
}
public class NotAnEntityUser
{
    public virtual string Id { get; set; }
    public virtual string UserName { get; set; }
    public virtual string Salt { get; set; }
}

然后让你写:

var lam = new Test().CreateNewStatement<User, NotAnEntityUser>("Id");
var c = new MyContext().Users.Select(lam).AsEnumerable().Select(u => (User)u).ToList();

事实上,由此产生的SQL是:

SELECT 
    1 AS [C1], 
    [Extent1].[Id] AS [Id]
    FROM [dbo].[User] AS [Extent1]

您也可以使用像automapper等工具,而不是编写隐式强制转换操作符。

使用automapper,您最终得到的结果如下:

var c = AutoMapper.Map<List<User>>(new MyContext().Users.Select(lam).ToList());