From 6667e2010bae8b4a8df0104067d80bb9db9d4195 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Wed, 11 Mar 2020 21:07:07 -0700 Subject: [PATCH] Modify to fix issue for #2076, Containment expand problem --- .../AspNetCore3xEndpointSample.Web.csproj | 5 +- .../Controllers/CustomersController.cs | 85 ++++++++++++++++++- .../Models/CustomerOrder.cs | 5 +- .../Models/CustomerOrderContext.cs | 23 +++-- .../Models/EdmModelBuilder.cs | 58 ++++++++++++- .../AspNetCore3xEndpointSample.Web/Startup.cs | 9 +- .../appsettings.json | 3 + .../Query/ODataQueryOptions.cs | 3 +- .../Microsoft.AspNetCore.OData.csproj | 6 +- 9 files changed, 173 insertions(+), 24 deletions(-) diff --git a/samples/AspNetCore3xEndpointSample.Web/AspNetCore3xEndpointSample.Web.csproj b/samples/AspNetCore3xEndpointSample.Web/AspNetCore3xEndpointSample.Web.csproj index 49f3a29ef6..37258518c0 100644 --- a/samples/AspNetCore3xEndpointSample.Web/AspNetCore3xEndpointSample.Web.csproj +++ b/samples/AspNetCore3xEndpointSample.Web/AspNetCore3xEndpointSample.Web.csproj @@ -5,9 +5,10 @@ - + + diff --git a/samples/AspNetCore3xEndpointSample.Web/Controllers/CustomersController.cs b/samples/AspNetCore3xEndpointSample.Web/Controllers/CustomersController.cs index d447550659..9008d10d9c 100644 --- a/samples/AspNetCore3xEndpointSample.Web/Controllers/CustomersController.cs +++ b/samples/AspNetCore3xEndpointSample.Web/Controllers/CustomersController.cs @@ -6,10 +6,10 @@ using AspNetCore3xEndpointSample.Web.Models; using Microsoft.AspNet.OData; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; + namespace AspNetCore3xEndpointSample.Web.Controllers -{ +{/* public class CustomersController : ODataController { private readonly CustomerOrderContext _context; @@ -87,4 +87,85 @@ public IActionResult Get(int key) return Ok(_context.Customers.FirstOrDefault(c => c.Id == key)); } } + */ + public class CustomersController : ODataController + { + private readonly IList _customers; + + public CustomersController(/*CustomerOrderContext context*/) + { + _customers = new List + { + new Customer + { + ID = 123, + CustomerReferrals = new List + { + new CustomerReferral { ID = 8, CustomerID = 123, ReferredCustomerID = 8}, + new CustomerReferral { ID = 9, CustomerID = 123, ReferredCustomerID = 9}, + }, + Phones = Enumerable.Range(0, 2).Select(e => + new CustomerPhone { ID = 11, CustomerID = 123, Formatted = new CustomerPhoneNumberFormatted { CustomerPhoneNumberID = 17, FormattedNumber = "171" } } + ).ToList() + }, + new Customer + { + ID = 456, + CustomerReferrals = new List + { + new CustomerReferral { ID = 18, CustomerID = 456, ReferredCustomerID = 8}, + new CustomerReferral { ID = 19, CustomerID = 456, ReferredCustomerID = 9}, + }, + Phones = Enumerable.Range(0, 2).Select(e => + new CustomerPhone { ID = 21 + e, CustomerID = 456 + e, Formatted = new CustomerPhoneNumberFormatted { CustomerPhoneNumberID = 17 + e, FormattedNumber = "abc" } } + ).ToList() + }, + //new Customer + //{ + // Name = "Peter", + // HomeAddress = new Address { City = "Hollewye", Street = "Main St NE"}, + // FavoriteAddresses = new List
+ // { + // new Address { City = "R4mond", Street = "546 NE"}, + // new Address { City = "R4d", Street = "546 AVE"}, + // }, + // Order = new Order { Title = "Jichan" }, + // Orders = Enumerable.Range(0, 2).Select(e => new Order { Title = "ijk" + e }).ToList() + //}, + }; + + foreach (var customer in _customers) + { + foreach (var refed in customer.CustomerReferrals) + { + refed.ReferredCustomer = customer; + refed.Customer = customer; + } + } + + } + + [EnableQuery] + public IActionResult Get() + { + // Be noted: without the NoTracking setting, the query for $select=HomeAddress with throw exception: + // A tracking query projects owned entity without corresponding owner in result. Owned entities cannot be tracked without their owner... + // _context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + return Ok(_customers); + } + + [EnableQuery] + public IActionResult Get(int key) + { + return Ok(_customers.FirstOrDefault(c => c.ID == key)); + } + + [EnableQuery(MaxExpansionDepth = 5)] + public IActionResult GetCustomerReferrals(int key) + { + var c = _customers.FirstOrDefault(c => c.ID == key); + return Ok(c.CustomerReferrals); + } + } } diff --git a/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrder.cs b/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrder.cs index 5ac809cf4f..508b7a74d4 100644 --- a/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrder.cs +++ b/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrder.cs @@ -3,10 +3,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; namespace AspNetCore3xEndpointSample.Web.Models -{ +{/* public class Customer { public int Id { get; set; } @@ -35,5 +34,5 @@ public class Address public string City { get; set; } public string Street { get; set; } - } + }*/ } diff --git a/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrderContext.cs b/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrderContext.cs index 6c6e4fff2e..8ec4a310b8 100644 --- a/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrderContext.cs +++ b/samples/AspNetCore3xEndpointSample.Web/Models/CustomerOrderContext.cs @@ -1,24 +1,35 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore; +//using Microsoft.EntityFrameworkCore; + +using System.Data.Entity; namespace AspNetCore3xEndpointSample.Web.Models { public class CustomerOrderContext : DbContext { - public CustomerOrderContext(DbContextOptions options) - : base(options) + //public CustomerOrderContext(DbContextOptions options) + // : base(options) + //{ + //} + + public CustomerOrderContext(string connectString) + : base(connectString) { } public DbSet Customers { get; set; } - public DbSet Orders { get; set; } + // public DbSet Orders { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) + protected override void OnModelCreating(DbModelBuilder modelBuilder) { - modelBuilder.Entity().OwnsOne(c => c.HomeAddress).WithOwner(); + modelBuilder.Entity().HasOptional(a => a.Formatted).WithRequired(); + modelBuilder.Entity() + .HasMany(c => c.CustomerReferrals) + .WithRequired(c => c.Customer) + .HasForeignKey(c => c.CustomerID); } } } diff --git a/samples/AspNetCore3xEndpointSample.Web/Models/EdmModelBuilder.cs b/samples/AspNetCore3xEndpointSample.Web/Models/EdmModelBuilder.cs index 53f68d0e13..5cb6a5d351 100644 --- a/samples/AspNetCore3xEndpointSample.Web/Models/EdmModelBuilder.cs +++ b/samples/AspNetCore3xEndpointSample.Web/Models/EdmModelBuilder.cs @@ -3,6 +3,9 @@ using Microsoft.AspNet.OData.Builder; using Microsoft.OData.Edm; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace AspNetCore3xEndpointSample.Web.Models { @@ -15,8 +18,9 @@ public static IEdmModel GetEdmModel() if (_edmModel == null) { var builder = new ODataConventionModelBuilder(); - builder.EntitySet("Customers"); - builder.EntitySet("Orders"); + var customers = builder.EntitySet("Customers"); + customers.Binding.HasManyPath(c => c.CustomerReferrals, true).HasRequiredBinding(r => r.ReferredCustomer, "Customers"); + // builder.EntitySet("Orders"); _edmModel = builder.GetEdmModel(); } @@ -24,4 +28,54 @@ public static IEdmModel GetEdmModel() } } + + public class Customer + { + [Key] + public int ID { get; set; } + + [Contained] + public virtual ICollection CustomerReferrals { get; set; } + + [Contained] + public virtual ICollection Phones { get; set; } + } + + public class CustomerReferral + { + [Key] + public int ID { get; set; } + + public int CustomerID { get; set; } + + public int ReferredCustomerID { get; set; } + + [Required] + [ForeignKey(nameof(CustomerID))] + public virtual Customer Customer { get; set; } + + [Required] + [ForeignKey(nameof(ReferredCustomerID))] + public virtual Customer ReferredCustomer { get; set; } + } + + public class CustomerPhone + { + [Key] + public int ID { get; set; } + + [Editable(false)] + public int CustomerID { get; set; } + + [Contained] + public virtual CustomerPhoneNumberFormatted Formatted { get; set; } + } + + public class CustomerPhoneNumberFormatted + { + [Key] + public int CustomerPhoneNumberID { get; set; } + + public string FormattedNumber { get; set; } + } } \ No newline at end of file diff --git a/samples/AspNetCore3xEndpointSample.Web/Startup.cs b/samples/AspNetCore3xEndpointSample.Web/Startup.cs index 667d735e05..cd34fce8cd 100644 --- a/samples/AspNetCore3xEndpointSample.Web/Startup.cs +++ b/samples/AspNetCore3xEndpointSample.Web/Startup.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using AspNetCore3xEndpointSample.Web.Models; using Microsoft.AspNet.OData.Batch; using Microsoft.AspNet.OData.Extensions; @@ -11,12 +9,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OData; using Microsoft.OData.Edm; +using System; +using System.Collections.Generic; namespace AspNetCore3xEndpointSample.Web { @@ -32,7 +31,8 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddDbContext(opt => opt.UseLazyLoadingProxies().UseInMemoryDatabase("CustomerOrderList")); + //services.AddDbContext(opt => opt.UseLazyLoadingProxies().UseInMemoryDatabase("CustomerOrderList")); + //services.AddScoped(_ => new CustomerOrderContext(Configuration.GetConnectionString("DefaultConnection"))); services.AddOData(); services.AddRouting(); } @@ -54,6 +54,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { + endpoints.Expand(); endpoints.MapODataRoute( "nullPrefix", null, b => diff --git a/samples/AspNetCore3xEndpointSample.Web/appsettings.json b/samples/AspNetCore3xEndpointSample.Web/appsettings.json index d9d9a9bff6..a992a10255 100644 --- a/samples/AspNetCore3xEndpointSample.Web/appsettings.json +++ b/samples/AspNetCore3xEndpointSample.Web/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=EF6MVCCore;Trusted_Connection=True;MultipleActiveResultSets=true" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs b/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs index 13da5b7cfb..67a16cebb3 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/ODataQueryOptions.cs @@ -73,8 +73,7 @@ private void Initialize(ODataQueryContext context) _queryOptionParser = new ODataQueryOptionParser( context.Model, - context.ElementType, - context.NavigationSource, + context.Path.Path, normalizedQueryParameters); // Note: the context.RequestContainer must be set by the ODataQueryOptions constructor. diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj index aacbeab3e3..ca54e3cfc7 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj @@ -41,9 +41,9 @@ - - - + + +