Как выбрать максимальное значение столбца и получить несколько результатов? - PullRequest
0 голосов
/ 22 октября 2018

Я изо всех сил пытаюсь понять, как построить мой 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 

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

  • Первый запрос: я проверяю максимальное значение 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 Model для Computer_Win_Installed_Software:

//------------------------------------------------------------------------------
// <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; }
    }
}

Ответы [ 4 ]

0 голосов
/ 24 октября 2018

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

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

Загружает ли «Включить» все связанные объекты или указанные?

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

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

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

0 голосов
/ 22 октября 2018

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

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 в вашем определении сущности, поэтому просто убедитесь, что там указано правильное поле.

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

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

0 голосов
/ 23 октября 2018

Итак, для данного Computer, ICollection<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);
0 голосов
/ 22 октября 2018

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

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