Подтвердить что ты не робот

Извлечь объект из сущностей без поля ONE

Я использую структуру сущности для подключения к базе данных. У меня есть одна небольшая проблема:

У меня есть одна таблица с одним столбцом varbinary (MAX) (с файловым потоком).

Я использую SQL-запрос для управления частью данных, но EF для остальных (метаданные файла).

У меня есть один код, который должен получить все файлы id, filename, guid, date date,... файла. Это вообще не нужно в поле "Данные".

Есть ли способ получить список, но без заполнения этого столбца?

Что-то вроде

context.Files.Where(f=>f.xyz).Exclude(f=>f.Data).ToList();

??

Я знаю, что могу создавать анонимные объекты, но мне нужно передать результат методу, поэтому никаких анонимных методов. И я не хочу помещать это в список анонимного типа, а затем создаю список моего неанонимного типа (Файл).

Цель состоит в том, чтобы избежать этого:

using(RsSolutionsEntities context = new RsSolutionsEntities())
{
    var file = context.Files
        .Where(f => f.Id == idFile)
        .Select(f => new {
            f.Id, f.MimeType, f.Size, f.FileName, f.DataType,
            f.DateModification, f.FileId
        }).FirstOrDefault();

    return new File() {
        DataType = file.DataType, DateModification = file.DateModification,
        FileId = file.FileId, FileName = file.FileName, Id = file.Id,
        MimeType = file.MimeType, Size = file.Size
    };
}

(Я использую здесь анонимный тип, потому что иначе вы получите исключение NotSupportedException: объект или сложный тип "ProjectName.File" не может быть сконструирован в запросе LINQ to Entities.)

(например, этот код выдает предыдущее исключение:

File file2 = context.Files.Where(f => f.Id == idFile)
  .Select(f => new File() {Id = f.Id, DataType = f.DataType}).FirstOrDefault();

и "Файл" - это тип, который я получаю с помощью context.Files.ToList(). Это хороший класс:

using File = MyProjectNamespace.Common.Data.DataModel.File;

Файл - это известный класс моего документа EF datacontext:

public ObjectSet<File> Files
{
    get { return _files  ?? (_files = CreateObjectSet<File>("Files")); }
}
private ObjectSet<File> _files;
4b9b3361

Ответ 1

Есть ли способ получить список, но без заполнения этого столбца?

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

Я использую анонимный тип, потому что иначе вы получите NotSupportedException: объект или сложный тип 'ProjectName.File' не может быть сконструирован в запросе LINQ to Entities.

Как исключение, вы не можете проецировать отображаемый объект. Я упомянул выше: проекция делает разные данные и EF не нравится "частичные сущности".

Ошибка 16 Ошибка 3023: проблема с отображением фрагментов, начиная с строки 2717: Файлы столбцов. Данные в таблице Файлы должны отображаться: у него нет значение по умолчанию и не равно NULL.

Недостаточно удалить свойство из конструктора. Вы должны открыть EDMX как XML и удалить столбец из SSDL, что сделает вашу модель очень хрупкой (каждое обновление из базы данных вернет ваш столбец). Если вы не хотите отображать столбец, вы должны использовать представление базы данных без столбца и отображать представление вместо таблицы, но вы не сможете вставлять данные.

В качестве обходного пути для всех ваших проблем используйте разбиение таблиц и разделите проблемный двоичный столбец на другой объект с соотношением 1:1 к вашему основному объекту File.

Ответ 2

Я бы сделал что-то вроде этого:

var result = from thing in dbContext.Things
             select new Thing {
                 PropertyA = thing.PropertyA,
                 Another = thing.Another
                 // and so on, skipping the VarBinary(MAX) property
             };

Где Thing - ваша организация, которой EF знает, как материализоваться. Результирующий оператор SQL не должен содержать большой столбец в его результирующем наборе, поскольку он не нужен в запросе.

РЕДАКТИРОВАТЬ. Из ваших изменений вы получите сообщение об ошибке NotSupportedException: объект или сложный тип 'ProjectName.File' не может быть сконструирован в запросе LINQ to Entities. потому что вы не отобразили этот класс как объект. Вы не можете включать объекты в запросы LINQ to Entities, которые EF не знает и ожидает, что он будет генерировать соответствующие SQL-инструкции.

Вы можете сопоставить другой тип, который исключает столбец VarBinary(MAX) в его определении или использовать приведенный выше код.

Ответ 3

вы можете сделать это:

var files = dbContext.Database.SqlQuery<File>("select FileId, DataType, MimeType from Files");

или это:

var files = objectContext.ExecuteStoreQuery<File>("select FileId, DataType, MimeType from Files");

в зависимости от вашей версии EF

Ответ 4

У меня было это требование, потому что у меня есть объект Document, у которого есть поле Content с содержимым файла, то есть размером около 100 МБ, и у меня есть функция поиска, которую я хотел бы вернуть остальную часть столбцы.

Я решил использовать проекцию:

IQueryable<Document> results = dbContext.Documents.Include(o => o.UploadedBy).Select(o => new {
    Content = (string)null,
    ContentType = o.ContentType,
    DocumentTypeId = o.DocumentTypeId,
    FileName = o.FileName,
    Id = o.Id,
    // etc. even with related entities here like:
    UploadedBy = o.UploadedBy
});

Затем мой контроллер WebApi передает этот объект results к общей функции Pagination, которая применяет .Skip, .Take и .ToList.

Это означает, что когда запрос выполняется, он не обращается к столбцу Content, поэтому данные 100 МБ не затрагиваются, и запрос выполняется так быстро, как вы хотите/ожидаете.

Затем я вернул его в мой класс DTO, который в этом случае почти точно такой же, как класс сущности, поэтому это может не быть шагом, который вам нужно реализовать, но он следует моему типичному шаблону кодирования WebApi, так:

var dtos = paginated.Select(o => new DocumentDTO
{
    Content = o.Content,
    ContentType = o.ContentType,
    DocumentTypeId = o.DocumentTypeId,
    FileName = o.FileName,
    Id = o.Id,
    UploadedBy = o.UploadedBy == null ? null : ModelFactory.Create(o.UploadedBy)
});

Затем я возвращаю список DTO:

return Ok(dtos);

Таким образом, он использует проекцию, которая может не соответствовать требованиям оригинального плаката, но если вы используете классы DTO, вы все равно конвертируете. Вы можете так же легко сделать следующее, чтобы вернуть их в качестве ваших реальных объектов:

var dtos = paginated.Select(o => new Document
{
    Content = o.Content,
    ContentType = o.ContentType,
    DocumentTypeId = o.DocumentTypeId,
    //...

Всего несколько дополнительных шагов, но это хорошо работает для меня.

Ответ 5

Я использую здесь анонимный тип, потому что в противном случае вы получите исключение NotSupportedException: сущность или сложный тип ProjectName.File не могут быть созданы в запросе LINQ to Entities.

var file = context.Files
        .Where(f => f.Id == idFile)
        .FirstOrDefault() // You need to exeucte the query if you want to reuse the type
        .Select(f => new {
            f.Id, f.MimeType, f.Size, f.FileName, f.DataType,
            f.DateModification, f.FileId
        }).FirstOrDefault();

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

Ответ 6

Я хотел бы поделиться своими попытками решить эту проблему, если кто-то другой находится в той же ситуации.

Я начал с того, что предложил Джереми Даньюу, который для меня является менее болезненным вариантом.

// You need to include all fields in the query, just make null the ones you don't want.
var results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName");

В моем случае мне нужен объект результата IQueryable<>, поэтому я добавил AsQueryable() в конце. Это, конечно, позволяет мне добавлять вызовы к .Where, .Take и другим командам, которые все мы знаем, и они отлично работали. Но есть оговорка:

Нормальный код (в основном context.myEntity.AsQueryable()) возвратил System.Data.Entity.DbSet<Data.DataModel.myEntity>, в то время как этот подход вернул System.Linq.EnumerableQuery<Data.DataModel.myEntity>.

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

Поэтому я попытался имитировать объект Entity Framework, используя точный запрос EF, созданный даже с этими [Extent1] псевдонимами, но это не сработало. При анализе результирующего объекта его запрос заканчивается как

FROM [dbo].[TableName] AS [Extent1].Where(c => ...

вместо ожидаемого

FROM [dbo].[TableName] AS [Extent1] WHERE ([Extent1]...

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

string query = "SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName";
if (parameterId.HasValue)
    query += " WHERE Field1 = " + parameterId.Value.ToString();
var results = context.Database.SqlQuery<myEntity>(query);

Если вашему методу иногда требуется это поле, вы можете добавить параметр bool, а затем сделать что-то вроде этого:

IQueryable<myEntity> results;
if (excludeBigData)
    results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName").AsQueryable();
else
    results = context.myEntity.AsQueryable();

Если кому-то удастся заставить расширения Linq работать правильно, как если бы это был оригинальный объект EF, прокомментируйте, чтобы я мог обновить ответ.

Ответ 7

Я пробовал это:

Из диаграммы edmx (EF 6) я нажал на столбец, который я хотел скрыть от EF, и по их свойствам вы можете настроить их getter и setter на приватный. Таким образом, для меня это работает.

Я возвращаю некоторые данные, которые содержат ссылку на пользователя, поэтому я хотел скрыть поле "Пароль", хотя он был зашифрован и засолен, я просто не хотел его на моем json, и я не хотел делать:

Select(col => new {}) 

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

Недостатком этого метода является то, что если вы когда-либо регенерируете свою модель, вам нужно будет снова изменить их getter и setter.

Ответ 8

Для EF Core 2 я реализовал такое решение:

var files = context.Files.AsNoTracking()
                         .IgnoreProperty(f => f.Report)
                         .ToList();

Основная идея состоит в том, чтобы включить, например, этот запрос:

SELECT [f].[Id], [f].[Report], [f].[CreationDate]
FROM [File] AS [f]

в это:

SELECT [f].[Id], '' as [Report], [f].[CreationDate]
FROM [File] AS [f]

Вы можете увидеть полный исходный код здесь: https://github.com/aspnet/EntityFrameworkCore/issues/1387#issuecomment-495630292