Вернуть IQueryable, который объединяет две таблицы - PullRequest
0 голосов
/ 11 июля 2019

Этот метод обслуживания вернет IQueryable<Vehicle>:

public IQueryable<Vehicle> GetVehicles()
{
    return
        from v in _context.Vehicles
        where
            v.Schedule == true
            && v.Suspend == false
        select v;
}

Если я хочу включить в запрос другую таблицу:

public IQueryable<Vehicle> GetVehicles()
{

    return
        from v in _context.Vehicles
        join si in _context.ServiceIntervals on v.Id equals si.VehicleId into loj
        from rs in loj.DefaultIfEmpty()
        where
            v.Schedule == true
            && v.Suspend == false
        select new { vehicle = v, repair = rs };

}

Какой правильный IQueryable<T> тип возврата?IQueryable<{vehicle,repair}> неверно.

Есть ли лучший способ составить оператор select?

** Edit **

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

ServiceIntervals на самом деле IQueryable<T>, который ссылается на табличную функцию из другой базы данных SQL:

public IQueryable<ServiceInterval> ServiceIntervals(DateTime startingDate, DateTime endingDate) =>
    Query<ServiceInterval>().FromSql($@"
        SELECT *
        FROM OtherDatabase.Dbo.ServiceIntervals({startingDate}, {endingDate})"
    );

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

...
_context.ServiceIntervals(DateTime.Now.Date,DateTime.Now.Date)
...

Поэтому я не думаю, что ServiceIntervals может быть отображено как свойство навигации для объекта Vehicle.

Ответы [ 2 ]

3 голосов
/ 11 июля 2019

Выполнение select new {} в LINQ создает анонимный тип, который по определению является анонимным и не может использоваться в качестве возвращаемого типа. Если вы хотите вернуть эту переменную, вы должны создать для нее тип.

public class VehicleServiceDTO
{
   public Vehicle Vehicle { get; set; }
   public ServiceInterval Repair { get; set; }
}

public IQueryable<VehicleServiceDTO> GetVehicles()
{

    return
        from v in _context.Vehicles
        join si in _context.ServiceIntervals on v.Id equals si.VehicleId into loj
        from rs in loj.DefaultIfEmpty()
        where
            v.Schedule == true
            && v.Suspend == false
        select new VehicleServiceDTO() { Vehicle = v, Repair = rs };

}

Вы можете изменить типы и имена переменных в пользовательском классе DTO, чтобы они соответствовали типу _context.ServiceIntervals (я предполагал, что он называется ServiceInterval).

2 голосов
/ 11 июля 2019

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

Я также не думаю, что отдельная модель DTO имеет смысл в этом случае. Я предполагаю, что, поскольку вы упомянули свойство навигации, у вас есть отношение «1-много». Я предполагаю, что автомобиль должен быть запланирован с набором ремонтов. Если это так, то, возможно, GroupJoin будет работать. Поддерживается EF: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/supported-and-unsupported-linq-methods-linq-to-entities

Я добавил свойство для проведения ремонта Транспортного средства :

public IEnumerable<ServiceInterval> RepairsToSchedule { get; set; }

Тогда запрос выглядит примерно так:

public static IQueryable<Vehicle> GetVehicles()
{
    return _context.Vehicles
        .Where(v => v.Schedule && !v.Suspend)
        .GroupJoin(_context.ServiceIntervals,
            v => v.Id,
            si => si.VehicleId,
            (v, si) => SetServiceIntervals(v, si));
}

Я использовал приведенный ниже статический метод для добавления ServiceIntervals к Vehicle :

private static Vehicle SetServiceIntervals(Vehicle v, IEnumerable<ServiceInterval> si)
{
    v.RepairsToSchedule = si;
    return v;
}

Весь исходный код приведен ниже: Отредактировано: с поправкой на две отдельные базы данных (заметка на одном сервере)

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

namespace StackoverFlow
{
    class Program
    {
        private static FakeDatabaseContext _context = new FakeDatabaseContext();
        private static FakeDatabaseContext2 _context2 = new FakeDatabaseContext2();

        static void Main(string[] args)
        {
            CleanContext();
            LoadContext();

            foreach (var vehicle in GetVehicles())
            {
                Console.WriteLine(JsonConvert.SerializeObject(vehicle, Formatting.Indented));
                Console.WriteLine();
            }
            Console.ReadKey();
        }

        public static IQueryable<Vehicle> GetVehicles()
        {
            return _context.Vehicles
                .Where(v => v.Schedule && !v.Suspend)
                .GroupJoin(_context.ServiceIntervals(new DateTime(), new DateTime()),
                    v => v.Id,
                    si => si.VehicleId,
                    (v, si) => SetServiceIntervals(v, si));
        }

        private static Vehicle SetServiceIntervals(Vehicle v, IEnumerable<ServiceInterval> si)
        {
            v.RepairsToSchedule = si;
            return v;
        }

        #region EF Context
        public class FakeDatabaseContext : DbContext
        {
            public DbSet<Vehicle> Vehicles { get; set; }
            private DbSet<ServiceInterval> _serviceIntervals { get; set; }

            public IQueryable<ServiceInterval> ServiceIntervals(DateTime startingDate, DateTime endingDate)
            {
                return _serviceIntervals.FromSql($@"
                    SELECT *
                    FROM Stackoverflow2.dbo.ServiceIntervals"
                );
            }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(
                    @"Server=(localdb)\mssqllocaldb;Database=Stackoverflow;Integrated Security=True");
                optionsBuilder
                    .ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning));
            }
        }

        // Used to load a seperate database
        public class FakeDatabaseContext2 : DbContext
        {
            public DbSet<ServiceInterval> ServiceIntervals { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(
                    @"Server=(localdb)\mssqllocaldb;Database=Stackoverflow2;Integrated Security=True");
                optionsBuilder
                    .ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning));
            }
        }

        public class Vehicle
        {
            public string Id { get; set; }
            public bool Schedule { get; set; }
            public bool Suspend { get; set; }
            public IEnumerable<ServiceInterval> RepairsToSchedule { get; set; }
        }

        public class ServiceInterval
        {
            public string Id { get; set; }
            public string VehicleId { get; set; }
        }
        #endregion EF Context

        #region Seed methods
        private static Random _random = new Random();
        private static bool _randomBool => _random.Next() % 2 == 1;

        private static void LoadContext()
        {
            var maxVehicles = 10;
            for (int i = 1; i < maxVehicles; i++)
            {
                _context.Vehicles.Add(new Vehicle { Id = i.ToString(), Schedule = _randomBool, Suspend = _randomBool });

                for (int o = 1; o < _random.Next(10); o++)
                {
                    _context2.ServiceIntervals.Add(new ServiceInterval { Id = ((maxVehicles * i) + o).ToString(), VehicleId = i.ToString() });
                }
            };

            _context.SaveChanges();
            _context2.SaveChanges();
        }

        private static void CleanContext()
        {
            _context.Vehicles.RemoveRange(_context.Vehicles.ToArray());
            _context2.ServiceIntervals.RemoveRange(_context2.ServiceIntervals.ToArray());
            _context.SaveChanges();
            _context2.SaveChanges();
        }
        #endregion Seed methods
    }
}

IntelliTrace показало, что этот запрос был выполнен:

SELECT [v].[Id], [v].[Schedule], [v].[Suspend], [si].[Id], [si].[VehicleId]
FROM [Vehicles] AS [v]
LEFT JOIN (

                        SELECT *
                        FROM Stackoverflow2.dbo.ServiceIntervals
) AS [si] ON [v].[Id] = [si].[VehicleId]
WHERE ([v].[Schedule] = 1) AND ([v].[Suspend] = 0)
ORDER BY [v].[Id]

Я проверял это несколько раз, и все, кажется, работает нормально.

~ Приветствия

Примечания к проекту установки:

  1. Это консольное приложение .NET Core 2.1
  2. Install-Package Microsoft.EntityFrameworkCore -Version 2.2.6
  3. Install-Package Microsoft.EntityFrameworkCore.Design -Version 2.2.6
  4. Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.2.6
  5. Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design -Version 1.1.6
  6. Откройте окно cmd и перейдите в папку проекта
  7. Запустить в консоли: dotnet ef миграций добавить InitialCreate
  8. Запуск в консоли: обновление базы данных dotnet ef
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...