如何在C#中从IQueryable<T>中选择特定列

3

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

例如:

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< T >。

1个回答

2
你遇到的问题是你正在将一个Func<User, User>传递给Select。这意味着该方法在内存中执行,而不是在数据库中执行。
Entity Framework无法从已编译的函数生成SQL代码。
当你写下这段代码时:
var users = Users.Select(u => new User { a = 1 });

你需要向它传递一个 Expression<Func<User, User>>,这是与实体框架兼容的,可以转换为 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 允许这样做,使用上述更改运行代码确实可以工作,并且只选择一个列。

基本上,这意味着我们需要更改表达式,以返回一个不是 Entity Framework 使用的实体类。

因此,我们需要写成这样:

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

1
非常好的解释,而且肯定是正确的。然而,解决方案太复杂了,以至于我放弃了我的lambda表达式,改用普通的Linq To Sql! - Luis Gouveia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接