Select only specific fields with Linq (EF core)

霸气de小男生 提交于 2021-02-07 07:21:31

问题


I have a DbContext where I would like to run a query to return only specific columns, to avoid fetching all the data.
The problem is that I would like to specify the column names with a set of strings, and I would like to obtain an IQueryable of the original type, i.e. without constructing an anonymous type.

Here is an example:

// 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 };
    }
}

I would like to have something like this method:

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

Is there a way I can do this?


回答1:


Since you are projecting (selecting) the members of the type T to the same type T, the required Expression<Func<T, T>> can relatively easy be created with Expression class methods like this:

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 is the expression equivalent of the new T { Member1 = x.Member1, Member2 = x.Member2, ... } C# construct.

The sample usage would be:

return context.Set<Person>().SelectMembers(fieldsToSelect);



回答2:


This can be achieved by using Dynamic Linq.

and for .Net Core - System.Linq.Dynamic.Core

With Dynamic Linq you can pass in your SELECT and WHERE as a string.

Using your example, you could then do something like:

IQueryable<Person> query = context.Persons
                        .Select("new Person { FirstName = p.FirstName }");



回答3:


Try this code:

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



回答4:


I was able to do this with the package https://github.com/StefH/System.Linq.Dynamic.Core so easily.

Here is an example code.

use namespacing, 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);



回答5:


var students = dbContext.Students
    .Include(s => s.PresentDetails)
    .Include(s => s.PresentDetails.GuardianDetails)
    .Where(s => s.StudentStatus == "Admitted")
    .Select(p => new Person() 
                       { 
                           Id = p.Id, 
                           Name = p.Name
                       });

Why not minimize the selected columns in the regular way? this is way cleaner.



来源:https://stackoverflow.com/questions/54549506/select-only-specific-fields-with-linq-ef-core

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!