代码之家  ›  专栏  ›  技术社区  ›  Paolo Tedesco

仅选择具有Linq(EF核心)的特定字段

  •  4
  • Paolo Tedesco  · 技术社区  · 7 年前

    我有一个 DbContext 我希望运行一个查询,只返回特定列,以避免获取所有数据。
    IQueryable 原始类型,即不构造匿名类型。

    // Install-Package Microsoft.AspNetCore.All
    // Install-Package Microsoft.EntityFrameworkCore
    
    using Microsoft.EntityFrameworkCore;
    using System;
    using System.Linq;
    
    public class Person {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
    public class TestContext : DbContext {
        public virtual DbSet<Person> Persons { get; set; }
        public TestContext(DbContextOptions<TestContext> options) : base(options) {
        }
    }
    
    class Program {
        static void Main(string[] args) {
    
            var builder = new DbContextOptionsBuilder<TestContext>();
            builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
            var context = new TestContext(builder.Options);
    
            context.Persons.Add(new Person { FirstName = "John", LastName = "Doe" });
            context.SaveChanges();
    
            // How can I express this selecting columns with a set of strings? 
            IQueryable<Person> query = from p in context.Persons select new Person { FirstName = p.FirstName };
        }
    }
    

    我想采用类似于此方法的方法:

    static IQueryable<Person> GetPersons(TestContext context, params string[] fieldsToSelect) {
        // ...
    }
    

    我有办法做到这一点吗?

    3 回复  |  直到 7 年前
        1
  •  13
  •   Ivan Stoev    7 年前

    因为您正在投影(选择)类型的成员 T 同类型 ,所需的 Expression<Func<T, T>> 可以相对容易地用 Expression 类方法,如下所示:

    public static partial class QueryableExtensions
    {
        public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, params string[] memberNames)
        {
            var parameter = Expression.Parameter(typeof(T), "e");
            var bindings = memberNames
                .Select(name => Expression.PropertyOrField(parameter, name))
                .Select(member => Expression.Bind(member.Member, member));
            var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
            var selector = Expression.Lambda<Func<T, T>>(body, parameter);
            return source.Select(selector);
        }
    }
    

    Expression.MemberInit new T { Member1 = x.Member1, Member2 = x.Member2, ... } C#构造。

    return context.Set<Person>().SelectMembers(fieldsToSelect);
    
        2
  •  1
  •   Janus Pienaar    7 年前

    这可以通过使用 Dynamic Linq .

    对于.Net核心- System.Linq.Dynamic.Core

    使用动态Linq,您可以将SELECT和WHERE作为字符串传递。

    使用您的示例,您可以执行以下操作:

    IQueryable<Person> query = context.Persons
                            .Select("new Person { FirstName = p.FirstName }");
    
        3
  •  1
  •   ornic    5 年前

    answer 属于 Ivan 我制作了原始版本的缓存函数,以消除使用反射对我们造成的损失。它允许将这一通行费从 秒到 微型的 重复请求的秒数(例如,DbAccess API的典型情况)。

    public static class QueryableExtensions
    {
        public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, IEnumerable<string> memberNames)
        {
            var result = QueryableGenericExtensions<T>.SelectMembers(source, memberNames);
            return result;
        }
    }
    
    
    public static class QueryableGenericExtensions<T>
    {
        private static readonly ConcurrentDictionary<string, ParameterExpression> _parameters = new();
        private static readonly ConcurrentDictionary<string, MemberAssignment> _bindings = new();
        private static readonly ConcurrentDictionary<string, Expression<Func<T, T>>> _selectors = new();
    
        public static IQueryable<T> SelectMembers(IQueryable<T> source, IEnumerable<string> memberNames)
        {
            var parameterName = typeof(T).FullName;
    
            var requestName = $"{parameterName}:{string.Join(",", memberNames.OrderBy(x => x))}";
            if (!_selectors.TryGetValue(requestName, out var selector))
            {
                if (!_parameters.TryGetValue(parameterName, out var parameter))
                {
                    parameter = Expression.Parameter(typeof(T), typeof(T).Name.ToLowerInvariant());
    
                    _ = _parameters.TryAdd(parameterName, parameter);
                }
    
                var bindings = memberNames
                    .Select(name =>
                    {
                        var memberName = $"{parameterName}:{name}";
                        if (!_bindings.TryGetValue(memberName, out var binding))
                        {
                            var member = Expression.PropertyOrField(parameter, name);
                            binding = Expression.Bind(member.Member, member);
    
                            _ = _bindings.TryAdd(memberName, binding);
                        }
                        return binding;
                    });
    
                var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
                selector = Expression.Lambda<Func<T, T>>(body, parameter);
    
                _selectors.TryAdd(requestName, selector);
            }
    
            return source.Select(selector);
        }
    }
    

    使用相同参数连续运行后的结果示例(请注意,这是纳秒):

    SelectMembers time ... 3092214 ns
    SelectMembers time ... 145724 ns
    SelectMembers time ... 38613 ns
    SelectMembers time ... 1969 ns
    

        4
  •  0
  •   Bijay Koirala    7 年前

    请尝试以下代码:

    string fieldsToSelect = "new Person { FirstName = p.FirstName }"; //Pass this as parameter.
    
    public static IQueryable<Person> GetPersons(TestContext context, string fieldsToSelect) 
    {
        IQueryable<Person> query = context.Persons.Select(fieldsToSelect);
    }
    
        5
  •  0
  •   Ajeesh Joshy    6 年前

    我可以用这个包裹做这件事 https://github.com/StefH/System.Linq.Dynamic.Core 太容易了。

    使用名称空间, using System.Linq.Dynamic.Core;

    //var selectQuery = "new(Name, Id, PresentDetails.RollNo)";
    
    var selectQuery = "new(Name, Id, PresentDetails.GuardianDetails.Name as GuardianName)";
    
    var students = dbContext.Students
        .Include(s => s.PresentDetails)
        .Include(s => s.PresentDetails.GuardianDetails)
        .Where(s => s.StudentStatus == "Admitted")
        .Select(selectQuery);
    
        6
  •  0
  •   Tobias Lie-Atjam    4 年前
    var students = dbContext.Students
        .Include(s => s.PresentDetails)
        .Where(s => s.StudentStatus == "Admitted")
        .Select(p => new Person() 
                           { 
                               Id = p.Id, 
                               Name = p.Name
                           });