【问题标题】:How do I select specific columns from IQueryable<T> in C#如何从 C# 中的 IQueryable<T> 中选择特定列
【发布时间】:2015-11-26 22:37:43
【问题描述】:

我需要从 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

【问题讨论】:

    标签: c# entity-framework entity iqueryable


    【解决方案1】:

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

    Entity Framework 无法从已编译的函数生成 SQL 代码。

    当你写这个时:

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

    您正在向它传递一个Expression&lt;Func&lt;User, User&gt;&gt;,实体框架CAN 会将其转换为SQL。

    因此,您需要更改的是函数的结尾。而不是这个:

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

    你只想做

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

    并将函数的返回类型更改为Expression&lt;Func&lt;T,T&gt;&gt;

    不过

    无论出于何种原因,EntityFramework不允许您构造实体对象。 Linq-To-Sql 确实允许这样做,并且使用我上面的更改运行代码确实可以工作,并且只选择一列。

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

    所以,不要这样写:

    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]
    

    除了编写隐式转换运算符,您还可以使用自动映射器等工具。

    使用自动映射器,您最终会得到如下结果:

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

    【讨论】:

    • 很棒的解释,当然该死的正确。但是,解决方案太复杂了,我只是放弃了我的 lambda 表达式并做了一个普通的 Linq To Sql!
    猜你喜欢
    • 1970-01-01
    • 2020-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-26
    • 2016-11-22
    • 2018-06-30
    • 1970-01-01
    相关资源
    最近更新 更多