Как выбрать максимальное значение столбца и получить несколько результатов?

Я изо всех сил пытаюсь понять, как построить свой SQL-запрос с использованием Entity Framework 6 и SQL Server в моем проекте ASP.NET Web API v2.

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

Я могу получить компьютер, включая всю имеющуюся на нем информацию, с помощью следующего запроса:

Computer computer = ComputersDbContext.Computers
                   ... more includes....
                    .Include("Computer_Win_Installed_Software")
                   ... more includes....
                    .Where(a => a.ComputerId == computerId)
                       .First(t => t.TenantId == tenantId);

Как видите, у меня есть еще одна таблица под названием Computer_Win_Installed_Software, в которой хранится все программное обеспечение, установленное в системе. Это выглядит так:

IdentityKey | ComputerID (FK) | Softwarename     | EntryTimestamp
------------+-----------------+------------------+--------------
          1 |               1 | Some Software    | 1547241345 
          2 |               1 | Another Software | 1547241345 

EntryTimestamp - это временная метка Unix, которая уникальна для каждого запуска инвентаризации и одинакова для всего программного обеспечения, обнаруженного при этом запуске. Теперь, если система снова будет инвентаризирована, таблица будет выглядеть так:

IdentityKey | ComputerID (FK) | Softwarename       | EntryTimestamp
------------+-----------------+--------------------+---------------
          1 |               1 | Some Software      |     1547241345 
          2 |               1 | Another Software   |     1547241345 
          3 |               1 | Some Software      |     1886454564 
          4 |               1 | Another Software   |     1886454564 
          5 |               1 | Even More Software |     1886454564 

Я хочу сохранить исторические данные, поэтому мне нужно сохранить старые записи.

Моя проблема в том, что мой запрос EF сверху вернет ВСЕ эти записи в результирующем объекте.

Как мне изменить свой запрос, чтобы он возвращал только:

IdentityKey | ComputerID (FK) | Softwarename       | EntryTimestamp
------------+-----------------+--------------------+---------------
          3 |               1 | Some Software      |     1886454564 
          4 |               1 | Another Software   |     1886454564 
          5 |               1 | Even More Software |     1886454564 

Я думал об использовании двух запросов:

  • Первый запрос: я проверяю максимальное значение EntryTimestamp
  • Второй запрос: примерно так:

    Computer computer = ComputersDbContext.Computers
                            .Include("Computer_Win_Installed_Software")      
                            .Where(a => a.ComputerId == computerId)
                            .Where(b => b.Computer_Win_Installed_Software.EntryTimestamp == EntryTimestamp)
                            .First(t => t.TenantId == tenantId);
    

Но Intellisense тут же кричит на меня: D

Я тоже думал только о выборе MAX() из колонки EntryTimestamp; но я даже не могу использовать b.Computer_Win_Installed_Software.EntryTimestamp в коде запроса.

Когда я пишу: .Where(b => b.Computer_Win_Installed_Software, он не перечисляет какие-либо столбцы как доступные варианты.

Я думаю, это потому, что класс Computer_Win_Installed_Software в EF относится к типу ICollection<Computer_Win_Installed_Software>. Это та же проблема с другими таблицами, которые все имеют отношение "1 ко многим" с таблицей Computers. Другая таблица имеет отношение 1 к 1 к таблице Computers, и там я могу выбрать все столбцы.

Я очень запутался.

И да, я сделал Google, но я не смог найти ничего, что помогло бы мне. Какой правильный путь сюда?

Обновлено: добавленные модели

DBContext:

public class DbEntities : DbContext
{
    public virtual DbSet<Agent> Agents { get; set; }     
    public virtual DbSet<Computer_Win_Installed_Software> ComputerWinInstalledSoftware { get; set; }      
    public DbSet<Computer> Computers { get; set; }
}

Модель EF для компьютера:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace ProjectName
{
    using System;
    using System.Collections.Generic;

    public partial class Computer
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Computer()
        {
            this.Computer_Win_Installed_Software = new HashSet<Computer_Win_Installed_Software>();            
        }

        public int ComputerId { get; set; }
        public int TenantId { get; set; }
        public virtual Agent Agent { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Computer_Win_Installed_Software> Computer_Win_Installed_Software { get; set; }

    }
}

Модель EF для компьютера_вин_установленное_программное обеспечение:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from a template.
//
//     Manual changes to this file may cause unexpected behavior in your application.
//     Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace KysoWebApi
{
    using System;
    using System.Collections.Generic;

    public partial class Computer_Win_Installed_Software
    {
        public int ComputerId { get; set; }
        public string SoftwareName { get; set; }
        public Nullable<double> SoftwareVersion { get; set; }
        public Nullable<bool> IsServerSoftware { get; set; }
        public Nullable<int> SoftwareVendorId { get; set; }
        public string SoftwareIs32or64bits { get; set; }
        public int ComputerWinInstalledSoftwareEntryId { get; set; }
        public string EntryTimestamp { get; set; }

        public virtual Computer Computer { get; set; }
    }
}

Вы терпите неудачу, потому что вы не соответствуете типу столбцов. Какой тип EntryTimestamp? Это тот же тип, что и в базе данных?

jdweng 22.10.2018 21:30

В базе данных это: varchar (10), а в моем коде это строка. Но я даже не могу выбрать b.Computer_Win_Installed_Software.EntryTimestamp. Похоже, то же самое со всеми таблицами, смоделированными как ICollection <something>. Они имеют отношение 1 ко многим к таблице компьютеров.

soomon 22.10.2018 21:36

Можете ли вы предоставить свои модельные объекты?

Mauricio Atanache 22.10.2018 21:42

Кстати, вы можете легко сделать это на стороне сервера: SELECT identitykey, computerid, softwarename, entrytimestamp FROM computer_win_installed_software WHERE entrytimestamp = (SELECT MAX(entrytimestamp) FROM computer_win_installed_software)

Miamy 22.10.2018 21:52

@Mauricio Atanache: добавлены модели EF. Надеюсь, вы имели в виду именно эти. Если вы имели в виду что-то другое, поясните, пожалуйста, и я с радостью предоставлю их

soomon 22.10.2018 21:52

@Miamy: Спасибо! Я, конечно, мог бы это сделать, но для этого мне потребуется выполнить 2 запроса. Я уверен, что столкнусь с этой проблемой в нескольких местах: / Знаете ли вы решение, которое я могу интегрировать в свой текущий запрос?

soomon 22.10.2018 21:58

Я нигде не вижу поля TenantId.

JuanR 22.10.2018 22:09

@JuanR: Блин, ты молодец! Мне очень жаль, что я удалил ссылки на другие таблицы из моделей и кода, чтобы улучшить читаемость, а также удалил их. Я добавил это снова. У каждого компьютера есть ссылка на арендатора, см. Модель компьютера.

soomon 22.10.2018 22:17

Похоже, у вас есть три базы данных (это dbSet, а не dbTable) 1) Агенты 2) ComputerWinInstalledSoftware 3) Компьютеры Каждая база данных будет иметь DataTables. Каждая таблица будет объектом списка. Вы пытаетесь выполнить перечисление с помощью компьютеров базы данных, которые не являются списком (). Вам нужно найти имя таблицы в компьютерной базе данных, в которой есть информация.

jdweng 22.10.2018 23:06

Какого результата вы хотите в итоге? Если вы ищете строки ComputerWinInstalledSoftware, почему бы не запросить их?

NetMage 23.10.2018 01:52

@jdweng: Нет, у меня только одна база данных. В соответствии с моей Visual Studio IntelliSense, похоже, существуют сопли DbTable. Не могли бы вы показать мне ссылку на документацию? Я не мог найти ни одного.

soomon 23.10.2018 07:07

@NetMage: я могу получить все строки из таблицы ComputerWinInstalledSoftware, но я хочу отфильтровать их внутри запроса как часть компьютерного запроса, чтобы я не запрашивал базу данных несколько раз

soomon 23.10.2018 07:08

Можете ли вы использовать SQL Server Management Studio для проверки баз данных, таблиц и столбцов. Структура в сети может не соответствовать базе данных. Ваш файл конфигурации может нуждаться в обновлении.

jdweng 23.10.2018 11:03

«Intellisense немедленно кричит на меня». Лучше сказать, о чем он кричит. Думаю нужен Where(b => b.Computer_Win_Installed_Software.Any(wis => wis.EntryTimestamp == EntryTimestamp))

Gert Arnold 23.10.2018 13:16

@GertArnold: Спасибо, что связались со мной! С сожалением сообщаю, что ваше решение не работает. Результат: Если нет строки со значением EntryId -> нет результата. Если есть одна строка с правильным значением EntryId -> возвращаются все строки с совпадающим ComputerId, независимо от того, что EntryId является правильным для всех этих строк. Он не проверяет каждую строку на наличие правильного EntryId.

soomon 23.10.2018 16:38

Заменить Any на All.

Gert Arnold 23.10.2018 16:40

К сожалению, когда я это делаю, я получаю исключение Sequence contains no element Exception

soomon 24.10.2018 08:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
17
455
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Пожалуйста, попробуйте это:

            var computer = db.ComputerWinInstalledSoftware
                .Include("Computer")
                .First(c => c.Computer.ComputerId == computerId 
                && c.Computer.TenantId == tenantId 
                && c.EntryTimestamp == EntryTimestamp ).Computer;

Это то, что я пробовал. Ошибка: Ошибка CS1061 «ICollection <Computer_Win_Installed_Software>» не содержит определения для «EntryTimestamp» и не может быть найден доступный метод расширения «EntryTimestamp», принимающий первый аргумент типа «ICollection <Computer_Win_Installed_Software> (если вам не хватает использования директива или ссылка на сборку?)

soomon 22.10.2018 22:04

Просто чтобы подтвердить, это отличается от кода, который вы разместили в строке «Включить». Вы пробовали это изменение?

Mauricio Atanache 23.10.2018 14:43

Вы заметили, что у вас разные пространства имен в ваших сущностях? Это нарочно?

Mauricio Atanache 24.10.2018 14:33

Попробуй это:

Computer computer = context.Computers
   .Include(c => c.Computer_Win_Installed_Software.Join(
       c.Computer_Win_Installed_Software.Where(a => a.ComputerId == computerId && a.Computer.TenantId == tenantId)
       .GroupBy(s => s.Softwarename)
       .Select(g => new { SoftwareName = g.Key, MaxDate = g.Max(r => r.EntryTimestamp) })
       ,o => o.EntryTimestamp
       ,i => i.MaxDate
       , (o,i) => o))
   .First(a => a.ComputerId == computerId && a.TenantId == tenantId);

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

Что это делает, так это присоединение к записям по максимальной дате.

ОБНОВЛЕНИЕ: Я сделал небольшую поправку. Это должно быть сгруппировано по названию программного обеспечения. Измените группировку по своему вкусу, чтобы она правильно идентифицировала каждую часть программного обеспечения.

С сожалением вынужден сообщить, что это не работает. Все, что находится внутри .Include (), помечается как ошибка: Ошибка CS1660 Не удается преобразовать лямбда-выражение в тип «строка», потому что это не тип делегата.

soomon 22.10.2018 22:31

@soomon: Я еще раз взглянул на вашу модель, и похоже, что EntryTimeStamp - это string (хотя это похоже на число). Попробуйте изменить оператор Max на r.EntryTimestamp.ToString().

JuanR 23.10.2018 03:21

Спасибо еще раз. Да, это строка. К сожалению, ошибка все еще существует. Я думаю, что ошибка не в вашем коде, а в том факте, что выражение lamda не разрешено в Include().

soomon 23.10.2018 07:13

Хорошо, мне нужно пространство имен using System.Data.Entity, чтобы удалить ошибку. Также вы правы насчет того, что EntryTimestamp - это строка. Я изменил его на целое число. Код по-прежнему выдает ошибку System.ArgumentException: The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties. Parameter name: path at System.Data.Entity.QueryableExtensions.Include[T,TProperty](‌​IQueryable`1 source, Expression`1 path)

soomon 23.10.2018 09:22

Пробовал: .Include(c => c.Computer_Win_Installed_Software.Where(d => d.EntryTimestamp == 1540275242)) провести простой тест. Он компилируется, но дает ту же ошибку. Когда я нажимаю . после d, он фактически показывает мне свойства Computer_Win_Installed_Software. Итак, он знает, что я хочу их использовать, но ошибка остается той же

soomon 23.10.2018 09:29
Ответ принят как подходящий

Итак, для данного ComputerICollection<Computer_Win_Installed_Software> Computer_Win_Installed_Software всегда будет включать все связанные строки со всеми EntryTimestampts. Вы не можете (легко) фильтр включенных сущностей.

Итак, вместо этого просто верните отфильтрованные объекты:

var computer = ComputersDbContext.Computers
                .Where(a => a.ComputerId == computerId)
                .First(t => t.TenantId == tenantId);
var cwis = computer
            .Computer_Win_Installed_Software
                .Where(b => b.EntryTimestamp == EntryTimestamp);

Конечно, я не уверен, что это то, что вы действительно хотите сделать, у вас может быть Проблема XY.

Можете ли вы попробовать не включать связанные объекты, а просто запрашивать их?

var computer = ComputersDbContext.Computers
                .Where(a => a.ComputerId == computerId)
                .First(t => t.TenantId == tenantId);
                .Where(b => b.Computer_Win_Installed_Software.EntryTimestamp == EntryTimestamp);

Блин, это значит, что если таблица Computer_Win_Installed_Software содержит 1000 строк, мне всегда придется запрашивать их все и потом фильтровать?

soomon 23.10.2018 07:15

Или не используйте Include и загрузите их позже. Кажется, что ваша база данных и ваши сущности расходятся - я не использую EF, но я предполагаю, что вам следует перенести старые записи в таблицу истории, чтобы ваша сущность ожидала, что все текущие строки будут объединены. При некоторых изменениях модели EF можно создать свойство EF, которое предназначено только для текущих строк и автоматически изменяет соединение для последних. Очевидно, метод EF обычно представляет собой два запроса с использованием методов Query и Load для ручной загрузки отфильтрованных объектов.

NetMage 23.10.2018 19:36

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

NetMage 23.10.2018 19:43

Хорошо. После долгого исследования я нашел несколько сообщений, в которых говорилось, что вы не можете фильтровать внутри включенных статистических данных и что это сделка типа «все или ничего». Фильтрация в конце все время дает странные результаты.

Источники: Entity Framework - выборочное условие для включенного свойства навигации

Включает ли загрузку всех связанных сущностей или указанных?

Мне удалось найти 2 решения: DynamicSelectExtensions: https://github.com/thiscode/DynamicSelectExtensions

Кажется, есть EntityFramework + (EF +). Я видел сообщения разработчиков на Stackoverflow, но пока не могу их найти. Его библиотека, кажется, может делать то, что я хочу, но нормальный EF - нет и, похоже, никогда не будет, поскольку запрос функции для этого датируется несколькими годами.

Спасибо всем за попытку помочь мне и за то, что потратили свое время. Сейчас я сделаю несколько запросов и, возможно, вернусь позже, надеюсь, с большим опытом: D

Другие вопросы по теме