Насколько я знаю, в EF нет чистого способа сделать это. Вы можете использовать некоторые обходные пути разного уродства, ниже один. Это будет работать, только если вы не собираетесь обновлять \ присоединять \ удалять возвращенные объекты, что, как я полагаю, подходит для этого варианта использования.
Предположим, мы хотим включить только свойства "ID" и "Код". Нам нужно построить выражение этой формы:
fooBarsQuery.Select(x => new FooBar {ID = x.ID, Code = x.Code))
Мы можем сделать это вручную следующим образом:
public static IQueryable<T> IncludeOnly<T>(this IQueryable<T> query, params string[] properties) {
var arg = Expression.Parameter(typeof(T), "x");
var bindings = new List<MemberBinding>();
foreach (var propName in properties) {
var prop = typeof(T).GetProperty(propName);
bindings.Add(Expression.Bind(prop, Expression.Property(arg, prop)));
}
// our select, x => new T {Prop1 = x.Prop1, Prop2 = x.Prop2 ...}
var select = Expression.Lambda<Func<T, T>>(Expression.MemberInit(Expression.New(typeof(T)), bindings), arg);
return query.Select(select);
}
Но если мы действительно попробуем это:
// some test entity I use
var t = ctx.Errors.IncludeOnly("ErrorID", "ErrorCode", "Duration").Take(10).ToList();
Не удастся, за исключением
Сущность или сложный тип ... не могут быть построены
в запросе LINQ to Entities
Итак, new SomeType
недопустимо в Select
, если SomeType
является типом сопоставленной сущности.
Но что, если у нас есть тип, унаследованный от сущности, и используем его?
public class SomeTypeProxy : SomeType {}
Ну, тогда это будет работать. Поэтому нам нужно где-то получить такой тип прокси. Его легко генерировать во время выполнения с помощью встроенных инструментов, поскольку все, что нам нужно, это наследовать от какого-то типа, и все.
Имея это в виду, наш метод становится:
static class Extensions {
private static ModuleBuilder _moduleBuilder;
private static readonly Dictionary<Type, Type> _proxies = new Dictionary<Type, Type>();
static Type GetProxyType<T>() {
lock (typeof(Extensions)) {
if (_proxies.ContainsKey(typeof(T)))
return _proxies[typeof(T)];
if (_moduleBuilder == null) {
var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("ExcludeProxies"), AssemblyBuilderAccess.Run);
_moduleBuilder = asmBuilder.DefineDynamicModule(
asmBuilder.GetName().Name, false);
}
// Create a proxy type
TypeBuilder typeBuilder = _moduleBuilder.DefineType(typeof(T).Name + "Proxy",
TypeAttributes.Public |
TypeAttributes.Class,
typeof(T));
var type = typeBuilder.CreateType();
// cache it
_proxies.Add(typeof(T), type);
return type;
}
}
public static IQueryable<T> IncludeOnly<T>(this IQueryable<T> query, params string[] properties) {
var arg = Expression.Parameter(typeof(T), "x");
var bindings = new List<MemberBinding>();
foreach (var propName in properties) {
var prop = typeof(T).GetProperty(propName);
bindings.Add(Expression.Bind(prop, Expression.Property(arg, prop)));
}
// modified select, (T x) => new TProxy {Prop1 = x.Prop1, Prop2 = x.Prop2 ...}
var select = Expression.Lambda<Func<T, T>>(Expression.MemberInit(Expression.New(GetProxyType<T>()), bindings), arg);
return query.Select(select);
}
}
А теперь он работает нормально и генерирует SQL-запрос select только с включенными полями. Он действительно возвращает список типов прокси, но это не проблема, поскольку тип прокси наследуется от вашего типа запроса. Мысль, как я уже говорил, вы не можете прикрепить \ обновить \ удалить из контекста.
Конечно, вы также можете изменить этот метод для исключения, принятия выражений свойств вместо чистых строк и т. Д., Это просто код, подтверждающий идею.