减少 EF Core 选择的列
Reduce columns selected by EF Core
我正在使用 EF Core 查询数据库。
减少使用 EF Core 检索的列数量的方法之一是使用 select 语句。例如,
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"
但是,我的问题是我需要一种可以动态执行此操作的方法,因为不同的列将通过用户选择 selected。所以我写了一个小的 class 可以动态创建这些 select 语句
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 and 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)");
});
}
}
}
问题是我打电话给 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
方法签名更新为上面的方法,使用了正确的 select 语句并且它按预期工作。
我正在使用 EF Core 查询数据库。
减少使用 EF Core 检索的列数量的方法之一是使用 select 语句。例如,
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"
但是,我的问题是我需要一种可以动态执行此操作的方法,因为不同的列将通过用户选择 selected。所以我写了一个小的 class 可以动态创建这些 select 语句
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 and 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)");
});
}
}
}
问题是我打电话给 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
方法签名更新为上面的方法,使用了正确的 select 语句并且它按预期工作。