Уменьшить столбцы, выбранные EF Core - PullRequest
0 голосов
/ 30 января 2020

Я использую EF Core для запроса к базе данных.

Один из способов уменьшить количество столбцов, которые извлекаются с помощью EF Core, - использовать оператор выбора. Например,

using (SchoolContext context = new SchoolContext(connection, loggerFactory))
{
    foreach (Pupil pupil in context.Pupils.Select(pupil => new Pupil{ Id = pupil.Id, Name = pupil.Name }))
    {
        Console.WriteLine(pupil.Id);
        Console.WriteLine(pupil.Age);
        Console.WriteLine(pupil.Name);
    }
}

уменьшит

SELECT "p"."id", "p"."age", "p"."name"
FROM "pupil" AS "p"

до

SELECT "p"."id" AS "Id", "p"."name" AS "Name"
FROM "pupil" AS "p"

Однако моя проблема заключается в том, что мне нужен способ, который мог бы сделать это динамически, как разные столбцы будет выбран через выбор пользователя. Поэтому я написал небольшой класс, который мог бы динамически создавать эти операторы выбора

public class Selector<T>
{
    private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();

    public Selector<T> AddProperty<TPropertyType>(Expression<Func<T, TPropertyType>> property)
    {
        if (!(property.Body is MemberExpression member))
        {
            throw new ArgumentException($"Expression '{property}' refers to a method, not a property.");
        }

        if (!(member.Member is PropertyInfo propertyInfo))
        {
            throw new ArgumentException($"Expression '{property}' refers to a field, not a property.");
        }

        _properties.Add(propertyInfo);
        return this;
    }

    public Func<T, T> Select()
    {
        ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
        NewExpression objToInitialise = Expression.New(typeof(T));
        IEnumerable<MemberAssignment> propertiesToInitialise = _properties.Select(property =>
            {
                MemberExpression originalValue = Expression.Property(parameter, property);
                return Expression.Bind(property, originalValue);
            }
        );
        MemberInitExpression initialisedMember = Expression.MemberInit(objToInitialise, propertiesToInitialise);
        return Expression.Lambda<Func<T, T>>(initialisedMember, parameter).Compile();
    }
}

и использоваться как

using (SchoolContext context = new SchoolContext(connection, loggerFactory))
{
    Func<Pupil, Pupil> pupilIdsAndNames = new Selector<Pupil>()
        .AddProperty(x => x.Id)
        .AddProperty(x => x.Name)
        .Select();

    foreach (Pupil pupil in context.Pupils.Select(pupilIdsAndNames))
    {
        Console.WriteLine(pupil.Id);
        Console.WriteLine(pupil.Age);
        Console.WriteLine(pupil.Name);
    }
}

Проблема

Проблема в том, что код, который я написал для динамического генерирования выражения, не уменьшает объем данных, возвращаемых из запроса. Запрос, выполняемый EF Core:

SELECT "p"."id", "p"."age", "p"."name"
FROM "pupil" AS "p"

Это можно увидеть в MCVE ниже.

Почему это происходит и как это можно исправить?


MCVE

Программа требует Microsoft.EntityFrameworkCore.Sqlite и Microsoft.Extensions.Logging.Console .

Install-Package Microsoft.EntityFrameworkCore.Sqlite -Version 3.1.1
Install-Package Microsoft.Extensions.Logging.Console -Version 3.1.1

Код

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace ExplicitLoadTest
{
    internal static class Program
    {
        private static void Main()
        {
            SqliteConnection connection = new SqliteConnection("DataSource=:memory:");
            connection.Open();

            ILoggerFactory loggerFactory = LoggerFactory.Create(builder => {
                    builder.AddConsole();
                }
            );

            Pupil phil = MakePupilWithNameAndAge("Phil", 7);
            Pupil joe = MakePupilWithNameAndAge("Joe", 8);
            Pupil mac = MakePupilWithNameAndAge("Mac", 5);
            Pupil rose = MakePupilWithNameAndAge("Rose", 10);
            Pupil harry = MakePupilWithNameAndAge("Harry", 9);
            Pupil meg = MakePupilWithNameAndAge("Meg", 8);

            using (SchoolContext context = new SchoolContext(connection, loggerFactory))
            {
                context.Database.EnsureCreated();

                context.Pupils.Add(phil);
                context.Pupils.Add(joe);
                context.Pupils.Add(mac);
                context.Pupils.Add(rose);
                context.Pupils.Add(harry);
                context.Pupils.Add(meg);

                context.SaveChanges();
            }

            using (SchoolContext context = new SchoolContext(connection, loggerFactory))
            {
                foreach (Pupil pupil in context.Pupils.Select(pupil => new Pupil{ Id = pupil.Id, Name = pupil.Name }))
                {
                    Console.WriteLine(pupil.Id);
                    Console.WriteLine(pupil.Age);
                    Console.WriteLine(pupil.Name);
                }
            }

            using (SchoolContext context = new SchoolContext(connection, loggerFactory))
            {
                Func<Pupil, Pupil> pupilIdsAndNames = new Selector<Pupil>()
                    .AddProperty(x => x.Id)
                    .AddProperty(x => x.Name)
                    .Select();

                foreach (Pupil pupil in context.Pupils.Select(pupilIdsAndNames))
                {
                    Console.WriteLine(pupil.Id);
                    Console.WriteLine(pupil.Age);
                    Console.WriteLine(pupil.Name);
                }
            }

            connection.Close();
        }

        private static Pupil MakePupilWithNameAndAge(string name, int age) => new Pupil
        {
            Id = Guid.NewGuid(),
            Name = name,
            Age = age
        };
    }

    public class Selector<T>
    {
        private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();

        public Selector<T> AddProperty<TPropertyType>(Expression<Func<T, TPropertyType>> property)
        {
            if (!(property.Body is MemberExpression member))
            {
                throw new ArgumentException($"Expression '{property}' refers to a method, not a property.");
            }

            if (!(member.Member is PropertyInfo propertyInfo))
            {
                throw new ArgumentException($"Expression '{property}' refers to a field, not a property.");
            }

            _properties.Add(propertyInfo);
            return this;
        }

        public Func<T, T> Select()
        {
            ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
            NewExpression objToInitialise = Expression.New(typeof(T));
            IEnumerable<MemberAssignment> propertiesToInitialise = _properties.Select(property =>
                {
                    MemberExpression originalValue = Expression.Property(parameter, property);
                    return Expression.Bind(property, originalValue);
                }
            );
            MemberInitExpression initialisedMember = Expression.MemberInit(objToInitialise, propertiesToInitialise);
            return Expression.Lambda<Func<T, T>>(initialisedMember, parameter).Compile();
        }
    }

    public class Pupil
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }
    }

    public class SchoolContext : DbContext
    {
        private readonly ILoggerFactory _loggerFactory;
        private readonly SqliteConnection _connection;

        public SchoolContext(SqliteConnection connection, ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory;
            _connection = connection;
        }

        public DbSet<Pupil> Pupils { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);

            if (optionsBuilder == null)
            {
                throw new ArgumentNullException(nameof(optionsBuilder), "Options builder is required and cannot be null");
            }

            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlite(_connection)
                    .EnableSensitiveDataLogging()
                    .UseLoggerFactory(_loggerFactory);
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            if (modelBuilder == null)
            {
                throw new ArgumentNullException(nameof(modelBuilder), "Model builder is required and cannot be null");
            }

            modelBuilder.Entity<Pupil>(entity =>
            {
                entity.ToTable("pupil");

                entity.HasIndex(e => e.Id)
                    .HasName("pupil_id_uindex")
                    .IsUnique();

                entity.Property(e => e.Id)
                    .IsRequired()
                    .HasColumnName("id")
                    .HasColumnType("char(36)");

                entity.Property(e => e.Name)
                    .HasColumnName("name")
                    .HasColumnType("varchar(45)");

                entity.Property(e => e.Age)
                    .HasColumnName("age")
                    .HasColumnType("int(3)");
            });
        }
    }
}

1 Ответ

0 голосов
/ 05 февраля 2020

Кредиты Иван Стоев


Проблема заключалась в том, что я звонил Enumerable.Select вместо Queryable.Select.

public Expression<Func<T, T>> Select()
{
    ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
    NewExpression objToInitialise = Expression.New(typeof(T));
    IEnumerable<MemberAssignment> propertiesToInitialise = _properties.Select(property =>
        {
            MemberExpression originalValue = Expression.Property(parameter, property);
            return Expression.Bind(property, originalValue);
        }
    );
    MemberInitExpression initializedMember = Expression.MemberInit(objToInitialise, propertiesToInitialise);
    return Expression.Lambda<Func<T, T>>(initializedMember, parameter);
}

При обновлении Selector.Select Подпись метода к приведенному выше, правильный оператор выбора используется, и он работает как ожидалось.

...