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"); } }