diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e2d1c235866..07a02af2431 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -8,6 +8,7 @@ updates:
- dependency-name: Castle.Core
- dependency-name: Humanizer.Core
- dependency-name: IdentityServer4.EntityFramework
+ - dependency-name: Microsoft.AspNetCore.OData
- dependency-name: Microsoft.Azure.Cosmos
- dependency-name: Microsoft.CSharp
- dependency-name: Microsoft.Data.SqlClient
diff --git a/All.sln b/All.sln
index 22981381fbc..49654115002 100644
--- a/All.sln
+++ b/All.sln
@@ -110,6 +110,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Sqlite.winsq
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.Sqlite.sqlite3.Tests", "test\Microsoft.Data.Sqlite.Tests\Microsoft.Data.Sqlite.sqlite3.Tests.csproj", "{E0FF35C8-8038-4394-9C2A-AF34BE3CC61F}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.OData.FunctionalTests", "test\EFCore.OData.FunctionalTests\EFCore.OData.FunctionalTests.csproj", "{7C0E5443-FE44-4436-8A7D-CE64D1F889BD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -288,6 +290,10 @@ Global
{E0FF35C8-8038-4394-9C2A-AF34BE3CC61F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0FF35C8-8038-4394-9C2A-AF34BE3CC61F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0FF35C8-8038-4394-9C2A-AF34BE3CC61F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C0E5443-FE44-4436-8A7D-CE64D1F889BD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -336,6 +342,7 @@ Global
{7B598E0C-B8E2-4F1F-B53C-ED84178E65BE} = {258D5057-81B9-40EC-A872-D21E27452749}
{B163761D-FB4A-4C80-BAB9-01905E1351EF} = {258D5057-81B9-40EC-A872-D21E27452749}
{E0FF35C8-8038-4394-9C2A-AF34BE3CC61F} = {258D5057-81B9-40EC-A872-D21E27452749}
+ {7C0E5443-FE44-4436-8A7D-CE64D1F889BD} = {258D5057-81B9-40EC-A872-D21E27452749}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {285A5EB4-BCF4-40EB-B9E1-DF6DBCB5E705}
diff --git a/EFCore.Relational.slnf b/EFCore.Relational.slnf
index 90819ad6c50..a299aa8fcd4 100644
--- a/EFCore.Relational.slnf
+++ b/EFCore.Relational.slnf
@@ -19,6 +19,7 @@
"test\\EFCore.Design.Tests\\EFCore.Design.Tests.csproj",
"test\\EFCore.InMemory.FunctionalTests\\EFCore.InMemory.FunctionalTests.csproj",
"test\\EFCore.InMemory.Tests\\EFCore.InMemory.Tests.csproj",
+ "test\\EFCore.OData.FunctionalTests\\EFCore.OData.FunctionalTests.csproj",
"test\\EFCore.Proxies.Tests\\EFCore.Proxies.Tests.csproj",
"test\\EFCore.Relational.Specification.Tests\\EFCore.Relational.Specification.Tests.csproj",
"test\\EFCore.Relational.Tests\\EFCore.Relational.Tests.csproj",
diff --git a/EFCore.Runtime.slnf b/EFCore.Runtime.slnf
index 0f22354b082..cd2621c7750 100644
--- a/EFCore.Runtime.slnf
+++ b/EFCore.Runtime.slnf
@@ -23,6 +23,7 @@
"test\\EFCore.Design.Tests\\EFCore.Design.Tests.csproj",
"test\\EFCore.InMemory.FunctionalTests\\EFCore.InMemory.FunctionalTests.csproj",
"test\\EFCore.InMemory.Tests\\EFCore.InMemory.Tests.csproj",
+ "test\\EFCore.OData.FunctionalTests\\EFCore.OData.FunctionalTests.csproj",
"test\\EFCore.Proxies.Tests\\EFCore.Proxies.Tests.csproj",
"test\\EFCore.Relational.Specification.Tests\\EFCore.Relational.Specification.Tests.csproj",
"test\\EFCore.Relational.Tests\\EFCore.Relational.Tests.csproj",
@@ -31,7 +32,7 @@
"test\\EFCore.SqlServer.Tests\\EFCore.SqlServer.Tests.csproj",
"test\\EFCore.Sqlite.FunctionalTests\\EFCore.Sqlite.FunctionalTests.csproj",
"test\\EFCore.Sqlite.Tests\\EFCore.Sqlite.Tests.csproj",
- "test\\EFCore.Tests\\EFCore.Tests.csproj"
+ "test\\EFCore.Tests\\EFCore.Tests.csproj",
]
}
}
\ No newline at end of file
diff --git a/EFCore.slnf b/EFCore.slnf
index febfd94576a..9e6f2bb07a8 100644
--- a/EFCore.slnf
+++ b/EFCore.slnf
@@ -30,6 +30,7 @@
"test\\EFCore.Design.Tests\\EFCore.Design.Tests.csproj",
"test\\EFCore.InMemory.FunctionalTests\\EFCore.InMemory.FunctionalTests.csproj",
"test\\EFCore.InMemory.Tests\\EFCore.InMemory.Tests.csproj",
+ "test\\EFCore.OData.FunctionalTests\\EFCore.OData.FunctionalTests.csproj",
"test\\EFCore.Proxies.Tests\\EFCore.Proxies.Tests.csproj",
"test\\EFCore.Relational.Specification.Tests\\EFCore.Relational.Specification.Tests.csproj",
"test\\EFCore.Relational.Tests\\EFCore.Relational.Tests.csproj",
diff --git a/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj b/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj
new file mode 100644
index 00000000000..9e13aaa4e18
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/EFCore.OData.FunctionalTests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ $(StandardTestTfms)
+ Microsoft.EntityFrameworkCore.OData.FunctionalTests
+ Microsoft.EntityFrameworkCore
+ True
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/EFCore.OData.FunctionalTests/Extensions/HttpContentExtensions.cs b/test/EFCore.OData.FunctionalTests/Extensions/HttpContentExtensions.cs
new file mode 100644
index 00000000000..0b33fe6fa3f
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Extensions/HttpContentExtensions.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net.Http;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace Microsoft.EntityFrameworkCore.Extensions
+{
+ public static class HttpContentExtensions
+ {
+ public static async Task ReadAsObject(this HttpContent content)
+ {
+ var json = await content.ReadAsStringAsync();
+
+ return JsonConvert.DeserializeObject(json);
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/PortArranger.cs b/test/EFCore.OData.FunctionalTests/PortArranger.cs
new file mode 100644
index 00000000000..8ef60c65ccd
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/PortArranger.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Threading;
+
+namespace Microsoft.EntityFrameworkCore
+{
+ public class PortArranger
+ {
+ private static int _nextPort = 11000;
+
+ public static int Reserve()
+ {
+ var attempts = 0;
+ while (attempts++ < 10)
+ {
+ var port = Interlocked.Increment(ref _nextPort);
+ if (port >= 65535)
+ {
+ throw new OverflowException("Cannot get an available port, port value overflowed");
+ }
+
+ if (IsFree(port))
+ {
+ return port;
+ }
+ }
+
+ throw new TimeoutException(string.Format("Cannot get an available port in {0} attempts.", attempts));
+ }
+
+ private static bool IsFree(int port)
+ {
+ var properties = IPGlobalProperties.GetIPGlobalProperties();
+ var connections = properties.GetActiveTcpConnections();
+
+ return !connections.Any(c => c.LocalEndPoint.Port == port || c.RemoteEndPoint.Port == port);
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Properties/TestAssemblyConditions.cs b/test/EFCore.OData.FunctionalTests/Properties/TestAssemblyConditions.cs
new file mode 100644
index 00000000000..4385e186611
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Properties/TestAssemblyConditions.cs
@@ -0,0 +1,7 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.TestUtilities;
+
+// Skip the entire assembly if not on Windows and no external SQL Server is configured
+[assembly: SqlServerConfiguredCondition]
diff --git a/test/EFCore.OData.FunctionalTests/Properties/launchSettings.json b/test/EFCore.OData.FunctionalTests/Properties/launchSettings.json
new file mode 100644
index 00000000000..0d53dda6631
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:64176",
+ "sslPort": 44376
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "EFCore.OData.Tests": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsControllers.cs b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsControllers.cs
new file mode 100644
index 00000000000..61c65444d0a
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsControllers.cs
@@ -0,0 +1,148 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNet.OData;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class LevelOneController : TestODataController, IDisposable
+ {
+ private readonly ComplexNavigationsODataContext _context;
+
+ public LevelOneController(ComplexNavigationsODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.LevelOne;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.LevelOne.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class LevelTwoController : TestODataController, IDisposable
+ {
+ private readonly ComplexNavigationsODataContext _context;
+
+ public LevelTwoController(ComplexNavigationsODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.LevelTwo;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.LevelTwo.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class LevelThreeController : TestODataController, IDisposable
+ {
+ private readonly ComplexNavigationsODataContext _context;
+
+ public LevelThreeController(ComplexNavigationsODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.LevelThree;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.LevelThree.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class LevelFourController : TestODataController, IDisposable
+ {
+ private readonly ComplexNavigationsODataContext _context;
+
+ public LevelFourController(ComplexNavigationsODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.LevelFour;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.LevelFour.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataContext.cs b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataContext.cs
new file mode 100644
index 00000000000..f804660d69f
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataContext.cs
@@ -0,0 +1,86 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class ComplexNavigationsODataContext : PoolableDbContext
+ {
+ public ComplexNavigationsODataContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public DbSet LevelOne { get; set; }
+ public DbSet LevelTwo { get; set; }
+ public DbSet LevelThree { get; set; }
+ public DbSet LevelFour { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever();
+ modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever();
+ modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever();
+ modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever();
+
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_Self1).WithOne();
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Required_PK1).WithOne(e => e.OneToOne_Required_PK_Inverse2)
+ .HasPrincipalKey(e => e.Id).HasForeignKey(e => e.Id).IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_PK1).WithOne(e => e.OneToOne_Optional_PK_Inverse2)
+ .HasPrincipalKey(e => e.Id).IsRequired(false);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Required_FK1).WithOne(e => e.OneToOne_Required_FK_Inverse2)
+ .HasForeignKey(e => e.Level1_Required_Id).IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_FK1).WithOne(e => e.OneToOne_Optional_FK_Inverse2)
+ .HasForeignKey(e => e.Level1_Optional_Id).IsRequired(false);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required1).WithOne(e => e.OneToMany_Required_Inverse2).IsRequired()
+ .OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional1).WithOne(e => e.OneToMany_Optional_Inverse2).IsRequired(false);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required_Self1).WithOne(e => e.OneToMany_Required_Self_Inverse1)
+ .IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional_Self1).WithOne(e => e.OneToMany_Optional_Self_Inverse1)
+ .IsRequired(false);
+
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_Self2).WithOne();
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Required_PK2).WithOne(e => e.OneToOne_Required_PK_Inverse3)
+ .HasPrincipalKey(e => e.Id).HasForeignKey(e => e.Id).IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_PK2).WithOne(e => e.OneToOne_Optional_PK_Inverse3)
+ .HasPrincipalKey(e => e.Id).IsRequired(false);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Required_FK2).WithOne(e => e.OneToOne_Required_FK_Inverse3)
+ .HasForeignKey(e => e.Level2_Required_Id).IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_FK2).WithOne(e => e.OneToOne_Optional_FK_Inverse3)
+ .HasForeignKey(e => e.Level2_Optional_Id).IsRequired(false);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required2).WithOne(e => e.OneToMany_Required_Inverse3).IsRequired()
+ .OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional2).WithOne(e => e.OneToMany_Optional_Inverse3).IsRequired(false);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required_Self2).WithOne(e => e.OneToMany_Required_Self_Inverse2)
+ .IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional_Self2).WithOne(e => e.OneToMany_Optional_Self_Inverse2)
+ .IsRequired(false);
+
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_Self3).WithOne();
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Required_PK3).WithOne(e => e.OneToOne_Required_PK_Inverse4)
+ .HasPrincipalKey(e => e.Id).HasForeignKey(e => e.Id).IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_PK3).WithOne(e => e.OneToOne_Optional_PK_Inverse4)
+ .HasPrincipalKey(e => e.Id).IsRequired(false);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Required_FK3).WithOne(e => e.OneToOne_Required_FK_Inverse4)
+ .HasForeignKey(e => e.Level3_Required_Id).IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_FK3).WithOne(e => e.OneToOne_Optional_FK_Inverse4)
+ .HasForeignKey(e => e.Level3_Optional_Id).IsRequired(false);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required3).WithOne(e => e.OneToMany_Required_Inverse4).IsRequired()
+ .OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional3).WithOne(e => e.OneToMany_Optional_Inverse4).IsRequired(false);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required_Self3).WithOne(e => e.OneToMany_Required_Self_Inverse3)
+ .IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional_Self3).WithOne(e => e.OneToMany_Optional_Self_Inverse3)
+ .IsRequired(false);
+
+ modelBuilder.Entity().HasOne(e => e.OneToOne_Optional_Self4).WithOne();
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Required_Self4).WithOne(e => e.OneToMany_Required_Self_Inverse4)
+ .IsRequired().OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasMany(e => e.OneToMany_Optional_Self4).WithOne(e => e.OneToMany_Optional_Self_Inverse4)
+ .IsRequired(false);
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataQueryTestFixture.cs b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataQueryTestFixture.cs
new file mode 100644
index 00000000000..88e09a79df9
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataQueryTestFixture.cs
@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net.Http;
+using Microsoft.AspNet.OData.Builder;
+using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel;
+using Microsoft.Extensions.Hosting;
+using Microsoft.OData.Edm;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class ComplexNavigationsODataQueryTestFixture : ComplexNavigationsQuerySqlServerFixture, IODataQueryTestFixture
+ {
+ private IHost _selfHostServer = null;
+
+ protected override string StoreName { get; } = "ODataComplexNavigations";
+
+ public ComplexNavigationsODataQueryTestFixture()
+ {
+ var controllers = new Type[]
+ {
+ typeof(LevelOneController),
+ typeof(LevelTwoController),
+ typeof(LevelThreeController),
+ typeof(LevelFourController),
+ };
+
+ (BaseAddress, ClientFactory, _selfHostServer)
+ = ODataQueryTestFixtureInitializer.Initialize(StoreName, controllers, GetEdmModel());
+ }
+
+ private static IEdmModel GetEdmModel()
+ {
+ var modelBuilder = new ODataConventionModelBuilder();
+ modelBuilder.EntitySet("LevelOne");
+ modelBuilder.EntitySet("LevelTwo");
+ modelBuilder.EntitySet("LevelThree");
+ modelBuilder.EntitySet("LevelFour");
+
+ return modelBuilder.GetEdmModel();
+ }
+
+ public string BaseAddress { get; private set; }
+
+ public IHttpClientFactory ClientFactory { get; private set; }
+
+ public override void Dispose()
+ {
+ if (_selfHostServer != null)
+ {
+ _selfHostServer.StopAsync();
+ _selfHostServer.WaitForShutdownAsync();
+ _selfHostServer = null;
+ }
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataQueryTests.cs b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataQueryTests.cs
new file mode 100644
index 00000000000..7f88d26a6b9
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/ComplexNavigationsODataQueryTests.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Extensions;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class ComplexNavigationsODataQueryTests : ODataQueryTestBase, IClassFixture
+ {
+ public ComplexNavigationsODataQueryTests(ComplexNavigationsODataQueryTestFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ [ConditionalFact]
+ public async Task Query_level_ones()
+ {
+ var requestUri = string.Format("{0}/odata/LevelOne", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#LevelOne", result["@odata.context"].ToString());
+ var levelOnes = result["value"] as JArray;
+
+ Assert.Equal(13, levelOnes.Count);
+ }
+
+ [ConditionalFact]
+ public async Task Query_level_threes()
+ {
+ var requestUri = string.Format("{0}/odata/LevelThree", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#LevelThree", result["@odata.context"].ToString());
+ var levelThrees = result["value"] as JArray;
+
+ Assert.Equal(10, levelThrees.Count);
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/EndpointRouteConfiguration.cs b/test/EFCore.OData.FunctionalTests/Query/EndpointRouteConfiguration.cs
new file mode 100644
index 00000000000..d5b82f08e42
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/EndpointRouteConfiguration.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public sealed class EndpointRouteConfiguration : IEndpointRouteBuilder
+ {
+ private readonly IEndpointRouteBuilder _routeBuilder;
+ private ApplicationPartManager _scopedPartManager;
+
+ public EndpointRouteConfiguration(IEndpointRouteBuilder routeBuilder)
+ {
+ _routeBuilder = routeBuilder ?? throw new ArgumentNullException(nameof(routeBuilder));
+ }
+
+ ///
+ /// Add a list of controllers to be discovered by the application.
+ ///
+ ///
+ public void AddControllers(params Type[] controllers)
+ {
+ // Strip out all the IApplicationPartTypeProvider parts.
+ _scopedPartManager = _routeBuilder.ServiceProvider.GetRequiredService();
+ var parts = _scopedPartManager.ApplicationParts;
+ var nonAssemblyParts = parts.Where(p => p.GetType() != typeof(IApplicationPartTypeProvider)).ToList();
+ _scopedPartManager.ApplicationParts.Clear();
+ _scopedPartManager.ApplicationParts.Concat(nonAssemblyParts);
+
+ // Add a new AssemblyPart with the controllers.
+ var part = new AssemblyPart(new TestAssembly(controllers));
+ _scopedPartManager.ApplicationParts.Add(part);
+ }
+
+ public ICollection DataSources => _routeBuilder.DataSources;
+
+ public IServiceProvider ServiceProvider => _routeBuilder.ServiceProvider;
+
+ public IApplicationBuilder CreateApplicationBuilder() => _routeBuilder.CreateApplicationBuilder();
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/GearsOfWarControllers.cs b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarControllers.cs
new file mode 100644
index 00000000000..3a46768deea
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarControllers.cs
@@ -0,0 +1,373 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNet.OData;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class GearsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public GearsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Gears;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable GetFromOfficer()
+ {
+ return _context.Gears.OfType();
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] string keyNickname, [FromODataUri] int keySquadId)
+ {
+ var result = _context.Gears.FirstOrDefault(e => e.Nickname == keyNickname && e.SquadId == keySquadId);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class SquadsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public SquadsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Squads;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.Squads.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class TagsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public TagsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Tags;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] Guid key)
+ {
+ var result = _context.Tags.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class WeaponsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public WeaponsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Weapons;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.Weapons.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class CitiesController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public CitiesController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Cities;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] string key)
+ {
+ var result = _context.Cities.FirstOrDefault(e => e.Name == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class MissionsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public MissionsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Missions;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.Missions.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class SquadMissionsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public SquadMissionsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.SquadMissions;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int keySquadId, [FromODataUri] int keyMissionId)
+ {
+ var result = _context.SquadMissions.FirstOrDefault(e => e.SquadId == keySquadId && e.MissionId == keyMissionId);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class FactionsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public FactionsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Factions;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable GetFromLocustHorde()
+ {
+ return _context.Factions.OfType();
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.Factions.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class LocustLeadersController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public LocustLeadersController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.LocustLeaders;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable GetFromLocustCommander()
+ {
+ return _context.LocustLeaders.OfType();
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] string key)
+ {
+ var result = _context.LocustLeaders.FirstOrDefault(e => e.Name == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class LocustHighCommandsController : TestODataController, IDisposable
+ {
+ private readonly GearsOfWarODataContext _context;
+
+ public LocustHighCommandsController(GearsOfWarODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.LocustHighCommands;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.LocustHighCommands.FirstOrDefault(e => e.Id == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs
new file mode 100644
index 00000000000..a187fd36638
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataContext.cs
@@ -0,0 +1,97 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class GearsOfWarODataContext : PoolableDbContext
+ {
+ public GearsOfWarODataContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public DbSet Gears { get; set; }
+ public DbSet Squads { get; set; }
+ public DbSet Tags { get; set; }
+ public DbSet Weapons { get; set; }
+ public DbSet Cities { get; set; }
+ public DbSet Missions { get; set; }
+ public DbSet SquadMissions { get; set; }
+ public DbSet Factions { get; set; }
+ public DbSet LocustLeaders { get; set; }
+ public DbSet LocustHighCommands { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(
+ b =>
+ {
+ b.HasKey(c => c.Name);
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.HasKey(
+ g => new { g.Nickname, g.SquadId });
+
+ b.HasOne(g => g.CityOfBirth).WithMany(c => c.BornGears).HasForeignKey(g => g.CityOfBirthName).IsRequired();
+ b.HasOne(g => g.Tag).WithOne(t => t.Gear).HasForeignKey(
+ t => new { t.GearNickName, t.GearSquadId });
+ b.HasOne(g => g.AssignedCity).WithMany(c => c.StationedGears).IsRequired(false);
+ });
+
+ modelBuilder.Entity().HasMany(o => o.Reports).WithOne().HasForeignKey(
+ o => new { o.LeaderNickname, o.LeaderSquadId });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.HasKey(s => s.Id);
+ b.Property(s => s.Id).ValueGeneratedNever();
+ b.Property(s => s.Banner5).HasMaxLength(5);
+ b.HasMany(s => s.Members).WithOne(g => g.Squad).HasForeignKey(g => g.SquadId);
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(w => w.Id).ValueGeneratedNever();
+ b.HasOne(w => w.SynergyWith).WithOne().HasForeignKey(w => w.SynergyWithId);
+ b.HasOne(w => w.Owner).WithMany(g => g.Weapons).HasForeignKey(w => w.OwnerFullName).HasPrincipalKey(g => g.FullName);
+ });
+
+ modelBuilder.Entity().Property(m => m.Id).ValueGeneratedNever();
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.HasKey(
+ sm => new { sm.SquadId, sm.MissionId });
+ b.HasOne(sm => sm.Mission).WithMany(m => m.ParticipatingSquads).HasForeignKey(sm => sm.MissionId);
+ b.HasOne(sm => sm.Squad).WithMany(s => s.Missions).HasForeignKey(sm => sm.SquadId);
+ });
+
+ modelBuilder.Entity().HasKey(f => f.Id);
+ modelBuilder.Entity().Property(f => f.Id).ValueGeneratedNever();
+
+ modelBuilder.Entity().HasBaseType();
+ modelBuilder.Entity().HasMany(h => h.Leaders).WithOne();
+
+ modelBuilder.Entity().HasOne(h => h.Commander).WithOne(c => c.CommandingFaction);
+
+ modelBuilder.Entity().HasKey(l => l.Name);
+ modelBuilder.Entity().HasBaseType();
+ modelBuilder.Entity().HasOne(c => c.DefeatedBy).WithOne().HasForeignKey(
+ c => new { c.DefeatedByNickname, c.DefeatedBySquadId });
+
+ modelBuilder.Entity().HasKey(l => l.Id);
+ modelBuilder.Entity().Property(l => l.Id).ValueGeneratedNever();
+
+ modelBuilder.Entity().Property(g => g.Location).HasColumnType("varchar(100)");
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataQueryTestFixture.cs b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataQueryTestFixture.cs
new file mode 100644
index 00000000000..c9d8e5a5845
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataQueryTestFixture.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net.Http;
+using Microsoft.AspNet.OData.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
+using Microsoft.Extensions.Hosting;
+using Microsoft.OData.Edm;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class GearsOfWarODataQueryTestFixture : GearsOfWarQuerySqlServerFixture, IODataQueryTestFixture
+ {
+ private IHost _selfHostServer = null;
+
+ protected override string StoreName { get; } = "ODataGearsOfWarQueryTest";
+
+ public GearsOfWarODataQueryTestFixture()
+ {
+ var controllers = new Type[]
+ {
+ typeof(GearsController),
+ typeof(SquadsController),
+ typeof(TagsController),
+ typeof(WeaponsController),
+ typeof(CitiesController),
+ typeof(MissionsController),
+ typeof(SquadMissionsController),
+ typeof(FactionsController),
+ typeof(LocustLeadersController),
+ typeof(LocustHighCommandsController),
+ };
+
+ (BaseAddress, ClientFactory, _selfHostServer)
+ = ODataQueryTestFixtureInitializer.Initialize(StoreName, controllers, GetEdmModel());
+ }
+
+ private static IEdmModel GetEdmModel()
+ {
+ var modelBuilder = new ODataConventionModelBuilder();
+ modelBuilder.EntitySet("Gears");
+ modelBuilder.EntityType().HasKey(e => new { e.Nickname, e.SquadId });
+ modelBuilder.EntitySet("Squads");
+ modelBuilder.EntitySet("Tags");
+ modelBuilder.EntitySet("Weapons");
+ modelBuilder.EntitySet("Cities");
+ modelBuilder.EntityType().HasKey(c => c.Name);
+ modelBuilder.EntitySet("Missions");
+ modelBuilder.EntitySet("SquadMissions");
+ modelBuilder.EntityType().HasKey(e => new { e.SquadId, e.MissionId });
+ modelBuilder.EntitySet("Factions");
+ modelBuilder.EntitySet("LocustLeaders");
+ modelBuilder.EntityType().HasKey(c => c.Name);
+ modelBuilder.EntitySet("LocustHighCommands");
+
+ return modelBuilder.GetEdmModel();
+ }
+
+ public string BaseAddress { get; private set; }
+
+ public IHttpClientFactory ClientFactory { get; private set; }
+
+ public override void Dispose()
+ {
+ if (_selfHostServer != null)
+ {
+ _selfHostServer.StopAsync();
+ _selfHostServer.WaitForShutdownAsync();
+ _selfHostServer = null;
+ }
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataQueryTests.cs b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataQueryTests.cs
new file mode 100644
index 00000000000..0ee608a9ab3
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/GearsOfWarODataQueryTests.cs
@@ -0,0 +1,83 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Extensions;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class GearsOfWarODataQueryTests : ODataQueryTestBase, IClassFixture
+ {
+ public GearsOfWarODataQueryTests(GearsOfWarODataQueryTestFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ [ConditionalFact]
+ public async Task Basic_query_gears()
+ {
+ var requestUri = string.Format("{0}/odata/Gears", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Gears", result["@odata.context"].ToString());
+ var gears = result["value"] as JArray;
+
+ Assert.Equal(5, gears.Count);
+ }
+
+ [ConditionalFact]
+ public async Task Basic_query_inheritance()
+ {
+ var requestUri = string.Format("{0}/odata/Gears/Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel.Officer", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Gears/Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel.Officer", result["@odata.context"].ToString());
+ var gears = result["value"] as JArray;
+
+ Assert.Equal(2, gears.Count);
+ }
+
+ [ConditionalFact]
+ public async Task Basic_query_single_element_from_set_composite_key()
+ {
+ var requestUri = string.Format("{0}/odata/Gears(Nickname='Marcus', SquadId=1)", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Gears/Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel.Officer/$entity", result["@odata.context"].ToString());
+ Assert.Equal("Marcus", result["Nickname"].ToString());
+ }
+
+ [ConditionalFact]
+ public async Task Complex_query_with_any_on_collection_navigation()
+ {
+ var requestUri = string.Format(@"{0}/odata/Gears?$filter=Weapons/any(w: w/Id gt 4)", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Gears", result["@odata.context"].ToString());
+ var officers = result["value"] as JArray;
+
+ Assert.Equal(3, officers.Count);
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/IODataQueryTestFixture.cs b/test/EFCore.OData.FunctionalTests/Query/IODataQueryTestFixture.cs
new file mode 100644
index 00000000000..88ef77dab23
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/IODataQueryTestFixture.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net.Http;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public interface IODataQueryTestFixture
+ {
+ public string BaseAddress { get; }
+
+ public IHttpClientFactory ClientFactory { get; }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/NorthwindControllers.cs b/test/EFCore.OData.FunctionalTests/Query/NorthwindControllers.cs
new file mode 100644
index 00000000000..c9f41d3ac25
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/NorthwindControllers.cs
@@ -0,0 +1,182 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNet.OData;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class CustomersController : TestODataController, IDisposable
+ {
+ private readonly NorthwindODataContext _context;
+
+ public CustomersController(NorthwindODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Customers;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] string key)
+ {
+ var result = _context.Customers.FirstOrDefault(g => g.CustomerID == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class OrdersController : TestODataController, IDisposable
+ {
+ private readonly NorthwindODataContext _context;
+
+ public OrdersController(NorthwindODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Orders;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int key)
+ {
+ var result = _context.Orders.FirstOrDefault(e => e.OrderID == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class OrderDetailsController : TestODataController, IDisposable
+ {
+ private readonly NorthwindODataContext _context;
+
+ public OrderDetailsController(NorthwindODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.OrderDetails;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] int keyOrderId, [FromODataUri] int keyProductId)
+ {
+ var result = _context.OrderDetails.FirstOrDefault(e => e.OrderID == keyOrderId && e.ProductID == keyProductId);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class EmployeesController : TestODataController, IDisposable
+ {
+ private readonly NorthwindODataContext _context;
+
+ public EmployeesController(NorthwindODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Employees;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] uint key)
+ {
+ var result = _context.Employees.FirstOrDefault(e => e.EmployeeID == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ public class ProductsController : TestODataController, IDisposable
+ {
+ private readonly NorthwindODataContext _context;
+
+ public ProductsController(NorthwindODataContext context)
+ {
+ _context = context;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public IEnumerable Get()
+ {
+ return _context.Products;
+ }
+
+ [HttpGet]
+ [EnableQuery]
+ public ITestActionResult Get([FromODataUri] uint key)
+ {
+ var result = _context.Products.FirstOrDefault(e => e.ProductID == key);
+ if (result == null)
+ {
+ return NotFound();
+ }
+
+ return Ok(result);
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/NorthwindODataContext.cs b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataContext.cs
new file mode 100644
index 00000000000..cf84aa5812d
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataContext.cs
@@ -0,0 +1,104 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class NorthwindODataContext : PoolableDbContext
+ {
+ public NorthwindODataContext(DbContextOptions options)
+ : base(options)
+ {
+ }
+
+ public virtual DbSet Customers { get; set; }
+ public virtual DbSet Employees { get; set; }
+ public virtual DbSet Orders { get; set; }
+ public virtual DbSet OrderDetails { get; set; }
+ public virtual DbSet Products { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(
+ e =>
+ {
+ e.Ignore(em => em.Address);
+ e.Ignore(em => em.BirthDate);
+ e.Ignore(em => em.Extension);
+ e.Ignore(em => em.HireDate);
+ e.Ignore(em => em.HomePhone);
+ e.Ignore(em => em.LastName);
+ e.Ignore(em => em.Notes);
+ e.Ignore(em => em.Photo);
+ e.Ignore(em => em.PhotoPath);
+ e.Ignore(em => em.PostalCode);
+ e.Ignore(em => em.Region);
+ e.Ignore(em => em.TitleOfCourtesy);
+
+ e.HasOne(e1 => e1.Manager).WithMany().HasForeignKey(e1 => e1.ReportsTo);
+ });
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.Ignore(p => p.CategoryID);
+ e.Ignore(p => p.QuantityPerUnit);
+ e.Ignore(p => p.ReorderLevel);
+ e.Ignore(p => p.UnitsOnOrder);
+ });
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.Ignore(o => o.Freight);
+ e.Ignore(o => o.RequiredDate);
+ e.Ignore(o => o.ShipAddress);
+ e.Ignore(o => o.ShipCity);
+ e.Ignore(o => o.ShipCountry);
+ e.Ignore(o => o.ShipName);
+ e.Ignore(o => o.ShipPostalCode);
+ e.Ignore(o => o.ShipRegion);
+ e.Ignore(o => o.ShipVia);
+ e.Ignore(o => o.ShippedDate);
+ });
+
+ modelBuilder.Entity(
+ e =>
+ {
+ e.HasKey(
+ od => new { od.OrderID, od.ProductID });
+ });
+
+ modelBuilder.Entity()
+ .Property(c => c.CustomerID)
+ .HasColumnType("nchar(5)");
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(c => c.EmployeeID).HasColumnType("int");
+ b.Property(c => c.ReportsTo).HasColumnType("int");
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(o => o.EmployeeID).HasColumnType("int");
+ b.Property(o => o.OrderDate).HasColumnType("datetime");
+ });
+
+ modelBuilder.Entity()
+ .Property(od => od.UnitPrice)
+ .HasColumnType("money");
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(p => p.UnitPrice).HasColumnType("money");
+ b.Property(p => p.UnitsInStock).HasColumnType("smallint");
+ });
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTestFixture.cs b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTestFixture.cs
new file mode 100644
index 00000000000..486e00074c4
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTestFixture.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net.Http;
+using Microsoft.AspNet.OData.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore.TestModels.Northwind;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.Extensions.Hosting;
+using Microsoft.OData.Edm;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class NorthwindODataQueryTestFixture : NorthwindQuerySqlServerFixture, IODataQueryTestFixture
+ {
+ private IHost _selfHostServer = null;
+
+ protected override string StoreName { get; } = "ODataNorthwind";
+
+ public NorthwindODataQueryTestFixture()
+ {
+ var controllers = new Type[]
+ {
+ typeof(CustomersController),
+ typeof(OrdersController),
+ typeof(OrderDetailsController),
+ typeof(EmployeesController),
+ typeof(ProductsController),
+ };
+
+ (BaseAddress, ClientFactory, _selfHostServer)
+ = ODataQueryTestFixtureInitializer.Initialize(StoreName, controllers, GetEdmModel());
+ }
+
+ private static IEdmModel GetEdmModel()
+ {
+ var modelBuilder = new ODataConventionModelBuilder();
+ modelBuilder.EntitySet("Customers");
+ modelBuilder.EntitySet("Orders");
+ modelBuilder.EntityType().HasKey(e => new { e.OrderID, e.ProductID });
+ modelBuilder.EntitySet("Order Details");
+
+ return modelBuilder.GetEdmModel();
+ }
+
+ public string BaseAddress { get; private set; }
+
+ public IHttpClientFactory ClientFactory { get; private set; }
+
+ public override void Dispose()
+ {
+ if (_selfHostServer != null)
+ {
+ _selfHostServer.StopAsync();
+ _selfHostServer.WaitForShutdownAsync();
+ _selfHostServer = null;
+ }
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs
new file mode 100644
index 00000000000..b84727e39b5
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs
@@ -0,0 +1,114 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore.Extensions;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class NorthwindODataQueryTests : ODataQueryTestBase, IClassFixture
+ {
+ public NorthwindODataQueryTests(NorthwindODataQueryTestFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ [ConditionalFact]
+ public async Task Basic_query_customers()
+ {
+ var requestUri = string.Format("{0}/odata/Customers", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Customers", result["@odata.context"].ToString());
+ var customers = result["value"] as JArray;
+
+ Assert.Equal(91, customers.Count);
+ }
+
+ [ConditionalFact]
+ public async Task Basic_query_select_single_customer()
+ {
+ var requestUri = string.Format(@"{0}/odata/Customers('ALFKI')", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Customers/$entity", result["@odata.context"].ToString());
+ Assert.Equal("ALFKI", result["CustomerID"].ToString());
+ }
+
+ [ConditionalFact]
+ public async Task Query_for_alfki_expand_orders()
+ {
+ var requestUri = string.Format(@"{0}/odata/Customers?$filter=CustomerID eq 'ALFKI'&$expand=Orders", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Customers", result["@odata.context"].ToString());
+ var customers = result["value"] as JArray;
+
+ Assert.Single(customers);
+ Assert.Equal("ALFKI", customers[0]["CustomerID"]);
+ var orders = customers[0]["Orders"] as JArray;
+ Assert.Equal(6, orders.Count);
+ }
+
+ [ConditionalFact]
+ public async Task Basic_query_orders()
+ {
+ var requestUri = string.Format("{0}/odata/Orders", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Orders", result["@odata.context"].ToString());
+ var orders = result["value"] as JArray;
+
+ Assert.Equal(830, orders.Count);
+ }
+
+ [ConditionalFact]
+ public async Task Query_orders_select_single_property()
+ {
+ var requestUri = string.Format("{0}/odata/Orders?$select=OrderDate", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#Orders(OrderDate)", result["@odata.context"].ToString());
+ var orderDates = result["value"] as JArray;
+
+ Assert.Equal(830, orderDates.Count);
+ }
+
+ [ConditionalFact(Skip = "TODO: fix routing")]
+ public async Task Basic_query_order_details()
+ {
+ var requestUri = string.Format("{0}/odata/Order Details", BaseAddress);
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ var response = await Client.SendAsync(request);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var result = await response.Content.ReadAsObject();
+
+ Assert.Contains("$metadata#OrderDetails", result["@odata.context"].ToString());
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/ODataQueryTestBase.cs b/test/EFCore.OData.FunctionalTests/Query/ODataQueryTestBase.cs
new file mode 100644
index 00000000000..09c6f37339d
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/ODataQueryTestBase.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net.Http;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public abstract class ODataQueryTestBase
+ {
+ public ODataQueryTestBase(IODataQueryTestFixture fixture)
+ {
+ BaseAddress = fixture.BaseAddress;
+ Client = fixture.ClientFactory.CreateClient();
+ }
+
+ public string BaseAddress { get; }
+
+ public HttpClient Client { get; }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/Query/ODataQueryTestFixtureInitializer.cs b/test/EFCore.OData.FunctionalTests/Query/ODataQueryTestFixtureInitializer.cs
new file mode 100644
index 00000000000..587f3eb25a8
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/Query/ODataQueryTestFixtureInitializer.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Reflection;
+using Microsoft.AspNet.OData.Batch;
+using Microsoft.AspNet.OData.Extensions;
+using Microsoft.AspNet.OData.Routing;
+using Microsoft.AspNet.OData.Routing.Conventions;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore.TestUtilities;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.OData.Edm;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ public class ODataQueryTestFixtureInitializer
+ {
+ public static (string BaseAddress, IHttpClientFactory ClientFactory, IHost SelfHostServer) Initialize(
+ string storeName,
+ Type[] controllers,
+ IEdmModel edmModel)
+ where TContext : DbContext
+ {
+ //SecurityHelper.AddIpListen();
+ var port = PortArranger.Reserve();
+ var baseAddress = string.Format("http://localhost:{0}", port.ToString());
+
+ var clientFactory = default(IHttpClientFactory);
+ var selfHostServer = Host.CreateDefaultBuilder()
+ .ConfigureWebHostDefaults(webBuilder => webBuilder
+ .UseKestrel(options => options.Listen(IPAddress.Loopback, port))
+ .ConfigureServices(services =>
+ {
+ services.AddHttpClient();
+ services.AddOData();
+ services.AddRouting();
+
+ UpdateConfigureServices(services, storeName);
+ })
+ .Configure(app =>
+ {
+ clientFactory = app.ApplicationServices.GetRequiredService();
+
+ app.UseODataBatching();
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
+ {
+ var config = new EndpointRouteConfiguration(endpoints);
+ UpdateConfigure(config, controllers, edmModel);
+ });
+ })
+ .ConfigureLogging((hostingContext, logging) =>
+ {
+ logging.AddDebug();
+ logging.SetMinimumLevel(LogLevel.Warning);
+ }
+ )).Build();
+
+ selfHostServer.Start();
+
+ return (baseAddress, clientFactory, selfHostServer);
+ }
+
+ public static void UpdateConfigureServices(IServiceCollection services, string storeName)
+ where TContext : DbContext
+ {
+ services.AddDbContext(b =>
+ b.UseSqlServer(
+ SqlServerTestStore.CreateConnectionString(storeName)));
+ }
+
+ protected static void UpdateConfigure(EndpointRouteConfiguration configuration, Type[] controllers, IEdmModel edmModel)
+ {
+ configuration.AddControllers(controllers);
+ configuration.MaxTop(2).Expand().Select().OrderBy().Filter();
+
+ configuration.MapODataRoute("odata", "odata",
+ edmModel,
+ new DefaultODataPathHandler(),
+ ODataRoutingConventions.CreateDefault(),
+ new DefaultODataBatchHandler());
+ }
+
+ }
+}
+
diff --git a/test/EFCore.OData.FunctionalTests/TestAssembly.cs b/test/EFCore.OData.FunctionalTests/TestAssembly.cs
new file mode 100644
index 00000000000..e9fcd27e803
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/TestAssembly.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+
+namespace Microsoft.EntityFrameworkCore
+{
+ internal sealed class TestAssembly : Assembly
+ {
+ readonly Type[] _types;
+
+ public TestAssembly(params Type[] types)
+ {
+ _types = types;
+ }
+
+ public override Type[] GetTypes()
+ {
+ return _types;
+ }
+ }
+}
diff --git a/test/EFCore.OData.FunctionalTests/TestControllers.cs b/test/EFCore.OData.FunctionalTests/TestControllers.cs
new file mode 100644
index 00000000000..cb5754e27ee
--- /dev/null
+++ b/test/EFCore.OData.FunctionalTests/TestControllers.cs
@@ -0,0 +1,247 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNet.OData;
+using Microsoft.AspNet.OData.Extensions;
+using Microsoft.AspNet.OData.Results;
+using Microsoft.AspNet.OData.Routing;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Routing;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore
+{
+ public class TestODataController : ODataController
+ {
+ [NonAction]
+ public new TestNotFoundResult NotFound()
+ => new TestNotFoundResult(base.NotFound());
+
+ [NonAction]
+ public new TestNotFoundObjectResult NotFound(object value)
+ => new TestNotFoundObjectResult(base.NotFound(value));
+
+ [NonAction]
+ public new TestBadRequestResult BadRequest()
+ => new TestBadRequestResult(base.BadRequest());
+
+ [NonAction]
+ public new TestBadRequestObjectResult BadRequest(ModelStateDictionary modelState)
+ => new TestBadRequestObjectResult(base.BadRequest(modelState));
+
+ [NonAction]
+ public TestBadRequestObjectResult BadRequest(string message)
+ => new TestBadRequestObjectResult(base.BadRequest(message));
+
+ public new TestBadRequestObjectResult BadRequest(object obj)
+ => new TestBadRequestObjectResult(base.BadRequest(obj));
+
+ [NonAction]
+ public new TestOkResult Ok()
+ => new TestOkResult(base.Ok());
+
+ [NonAction]
+ public new TestOkObjectResult Ok(object value)
+ => new TestOkObjectResult(value);
+
+ [NonAction]
+ public TestStatusCodeResult StatusCode(HttpStatusCode statusCode)
+ => new TestStatusCodeResult(base.StatusCode((int)statusCode));
+
+ [NonAction]
+ public TestStatusCodeObjectResult StatusCode(HttpStatusCode statusCode, object value)
+ => new TestStatusCodeObjectResult(base.StatusCode((int)statusCode, value));
+
+ [NonAction]
+ public new TestCreatedODataResult Created(T entity)
+ => new TestCreatedODataResult(entity);
+
+ [NonAction]
+ public new TestCreatedResult Created(string uri, object entity)
+ => new TestCreatedResult(base.Created(uri, entity));
+
+ [NonAction]
+ public new TestUpdatedODataResult Updated(T entity)
+ => new TestUpdatedODataResult(entity);
+
+ protected string GetServiceRootUri()
+ {
+ var routeName = Request.ODataFeature().RouteName;
+ var requestLeftPartBuilder = new StringBuilder(Request.Scheme);
+ requestLeftPartBuilder.Append("://");
+ requestLeftPartBuilder.Append(Request.Host.HasValue ? Request.Host.Value : Request.Host.ToString());
+ if (!string.IsNullOrEmpty(routeName))
+ {
+ requestLeftPartBuilder.Append("/");
+ requestLeftPartBuilder.Append(routeName);
+ }
+
+ return requestLeftPartBuilder.ToString();
+ }
+
+ protected string GetRoutePrefix()
+ {
+ var oDataRoute = Request.HttpContext.GetRouteData().Routers
+ .Where(r => r.GetType() == typeof(ODataRoute))
+ .SingleOrDefault() as ODataRoute;
+
+ Assert.NotNull(oDataRoute);
+
+ return oDataRoute.RoutePrefix;
+ }
+
+ protected bool Validate(object model)
+ {
+ return TryValidateModel(model);
+ }
+ }
+
+ public interface ITestActionResult : IActionResult { }
+
+ public class TestActionResult : ITestActionResult
+ {
+ private readonly IActionResult _innerResult;
+
+ public TestActionResult(IActionResult innerResult)
+ {
+ _innerResult = innerResult;
+ }
+
+ public Task ExecuteResultAsync(ActionContext context)
+ {
+ return _innerResult.ExecuteResultAsync(context);
+ }
+ }
+
+ public class TestObjectResult : ObjectResult, ITestActionResult
+ {
+ public TestObjectResult(object innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestStatusCodeResult : StatusCodeResult, ITestActionResult
+ {
+ private readonly StatusCodeResult _innerResult;
+
+ public TestStatusCodeResult(StatusCodeResult innerResult)
+ : base(innerResult.StatusCode)
+ {
+ _innerResult = innerResult;
+ }
+ }
+
+ public class TestNotFoundResult : TestStatusCodeResult
+ {
+ public TestNotFoundResult(NotFoundResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestNotFoundObjectResult : TestObjectResult
+ {
+ public TestNotFoundObjectResult(NotFoundObjectResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestBadRequestResult : TestStatusCodeResult
+ {
+ public TestBadRequestResult(BadRequestResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestBadRequestObjectResult : TestActionResult
+ {
+ public TestBadRequestObjectResult(BadRequestObjectResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestOkResult : TestStatusCodeResult
+ {
+ public TestOkResult(OkResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestOkObjectResult : TestObjectResult
+ {
+ public TestOkObjectResult(object innerResult)
+ : base(innerResult)
+ {
+ StatusCode = 200;
+ }
+ }
+
+ public class TestOkObjectResult : TestObjectResult
+ {
+ public TestOkObjectResult(object innerResult)
+ : base(innerResult)
+ {
+ StatusCode = 200;
+ }
+
+ public TestOkObjectResult(T content, TestODataController controller)
+ : base(content)
+ {
+ // Controller is unused.
+ StatusCode = 200;
+ }
+ }
+
+ public class TestStatusCodeObjectResult : TestObjectResult
+ {
+ public TestStatusCodeObjectResult(ObjectResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestCreatedResult : TestActionResult
+ {
+ public TestCreatedResult(CreatedResult innerResult)
+ : base(innerResult)
+ {
+ }
+ }
+
+ public class TestUpdatedODataResult : UpdatedODataResult, ITestActionResult
+ {
+ public TestUpdatedODataResult(T entity)
+ : base(entity)
+ {
+ }
+
+ public TestUpdatedODataResult(string uri, T entity)
+ : base(entity)
+ {
+ }
+ }
+
+ public class TestCreatedODataResult : CreatedODataResult, ITestActionResult
+ {
+ public TestCreatedODataResult(T entity)
+ : base(entity)
+ {
+ }
+
+ public TestCreatedODataResult(string uri, T entity)
+ : base(entity)
+ {
+ }
+ }
+}
diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs
index 2ea7de6a9cd..52608f496ec 100644
--- a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs
+++ b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs
@@ -269,6 +269,18 @@ public async Task AssertAll(
var expected = RewriteExpectedQuery(expectedQuery(ExpectedData)).All(rewrittenExpectedPredicate);
Assert.Equal(expected, actual);
+
+ //var dependenciesPropertyInfo = typeof(Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade).GetProperty("Dependencies", BindingFlags.NonPublic | BindingFlags.Instance);
+ //var relationalDependencies = dependenciesPropertyInfo.GetMethod.Invoke(context.Database, new object[] { });
+
+ //var relationalDependenciesType = relationalDependencies.GetType();
+ //var connectionProperty = relationalDependenciesType.GetProperty("RelationalConnection");
+ //var connection = connectionProperty.GetMethod.Invoke(relationalDependencies, new object[] { });
+
+ //var connectionStringProperty = connection.GetType().GetProperty("ConnectionString");
+ //var connectionString = connectionStringProperty.GetMethod.Invoke(connection, new object[] { });
+
+ //throw new InvalidOperationException("Test: " + connectionString);
}
public async Task AssertFirst(
diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerNorthwindTestStoreFactory.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerNorthwindTestStoreFactory.cs
index 4ddbdce4e8e..5047eff6474 100644
--- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerNorthwindTestStoreFactory.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerNorthwindTestStoreFactory.cs
@@ -14,6 +14,6 @@ protected SqlServerNorthwindTestStoreFactory()
}
public override TestStore GetOrCreate(string storeName)
- => SqlServerTestStore.GetOrCreate(Name, "Northwind.sql");
+ => SqlServerTestStore.GetOrCreate(storeName, "Northwind.sql");
}
}