diff --git a/src/GlobalSuppressions.cs b/src/GlobalSuppressions.cs index 134b12f6..6f9f4e89 100644 --- a/src/GlobalSuppressions.cs +++ b/src/GlobalSuppressions.cs @@ -37,8 +37,8 @@ #region CA1006 Nested Generic Type [assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`1(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1,System.Threading.CancellationToken)")] [assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.ApiBaseExtensions.#QueryAsync`2(Microsoft.Restier.Core.ApiBase,System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Threading.CancellationToken)")] +[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMapDictionary")] [assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Query.QueryRequest.#Create`2(System.Linq.IQueryable`1,System.Linq.Expressions.Expression`1,!!1>>,System.Nullable`1)")] -[assembly: SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntitySetTypeMapCollection")] #endregion #region CA1020 Few types in namespace @@ -149,7 +149,8 @@ [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.ApiContext.#.ctor(Microsoft.Restier.Core.ApiConfiguration)")] [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery`1(System.Linq.Expressions.Expression)")] [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery(System.Linq.Expressions.Expression)")] -[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntitySetTypeMapCollection")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntitySetTypeMapDictionary")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMapDictionary")] #endregion #region CA1801 Unused Parameters diff --git a/src/Microsoft.Restier.Core/Model/ModelContext.cs b/src/Microsoft.Restier.Core/Model/ModelContext.cs index a9812e29..f7aa5f6b 100644 --- a/src/Microsoft.Restier.Core/Model/ModelContext.cs +++ b/src/Microsoft.Restier.Core/Model/ModelContext.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Reflection; namespace Microsoft.Restier.Core.Model { @@ -24,8 +24,16 @@ public ModelContext(ApiContext apiContext) } /// - /// Gets or sets Entity set and entity type map collection, it will be used by publisher for model build. + /// Gets or sets Entity set and entity type map dictionary, it will be used by publisher for model build. /// - public Collection> EntitySetTypeMapCollection { get; set; } + public IDictionary EntitySetTypeMapDictionary { get; set; } + + /// + /// Gets or sets entity type and its key properties map dictionary, and used by publisher for model build. + /// This is useful when key properties does not have key attribute + /// or follow Web Api OData key property naming convention. + /// Otherwise, this collection is not needed. + /// + public IDictionary> EntityTypeKeyPropertiesMapDictionary { get; set; } } } diff --git a/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs b/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs index 36a0bd8c..209142b6 100644 --- a/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs +++ b/src/Microsoft.Restier.Providers.EntityFramework/Model/ModelProducer.cs @@ -7,6 +7,9 @@ #if !EF7 using System.Data.Entity; #endif +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Infrastructure; +using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -42,43 +45,35 @@ public Task GetModelAsync(ModelContext context, CancellationToken can { Ensure.NotNull(context, "context"); - var entitySetTypeMapCollection = new Collection>(); - var apiContext = context.ApiContext; - var dbContext = apiContext.GetApiService(); + var entitySetTypeMapDictionary = new Dictionary(); + var entityTypeKeyPropertiesMapDictionary = new Dictionary>(); + var dbContext = context.ApiContext.GetApiService(); - List props = GetDbSetProperties(dbContext); - foreach (var prop in props) - { - var type = prop.PropertyType.GenericTypeArguments[0]; - var pair = new KeyValuePair(prop.Name, type); - entitySetTypeMapCollection.Add(pair); - } - - context.EntitySetTypeMapCollection = entitySetTypeMapCollection; - return Task.FromResult(null); - } - - internal static List GetDbSetProperties(DbContext dbContext) - { - var dbSetProperties = new List(); - var properties = dbContext.GetType().GetProperties(); + var efModel = (dbContext as IObjectContextAdapter).ObjectContext.MetadataWorkspace; + var efEntityContainer = efModel.GetItems(DataSpace.CSpace).Single(); + var itemCollection = (ObjectItemCollection)efModel.GetItemCollection(DataSpace.OSpace); - foreach (var property in properties) + foreach (var efEntitySet in efEntityContainer.EntitySets) { - var type = property.PropertyType; -#if EF7 - var genericType = type.FindGenericType(typeof(DbSet<>)); -#else - var genericType = type.FindGenericType(typeof(IDbSet<>)); -#endif + var efEntityType = efEntitySet.ElementType; + var objectSpaceType = efModel.GetObjectSpaceType(efEntityType); + Type clrType = itemCollection.GetClrType(objectSpaceType); - if (genericType != null) + // As entity set name and type map + entitySetTypeMapDictionary.Add(efEntitySet.Name, clrType); + + ICollection keyProperties = new List(); + foreach (var property in efEntityType.KeyProperties) { - dbSetProperties.Add(property); + keyProperties.Add(clrType.GetProperty(property.Name)); } + + entityTypeKeyPropertiesMapDictionary.Add(clrType, keyProperties); } - return dbSetProperties; + context.EntitySetTypeMapDictionary = entitySetTypeMapDictionary; + context.EntityTypeKeyPropertiesMapDictionary = entityTypeKeyPropertiesMapDictionary; + return Task.FromResult(null); } } } diff --git a/src/Microsoft.Restier.Publishers.OData/Model/RestierModelBuilder.cs b/src/Microsoft.Restier.Publishers.OData/Model/RestierModelBuilder.cs index 28b80544..c86c8d63 100644 --- a/src/Microsoft.Restier.Publishers.OData/Model/RestierModelBuilder.cs +++ b/src/Microsoft.Restier.Publishers.OData/Model/RestierModelBuilder.cs @@ -28,8 +28,8 @@ public async Task GetModelAsync(ModelContext context, CancellationTok } } - var collection = context.EntitySetTypeMapCollection; - if (collection == null || collection.Count == 0) + var entitySetTypeMapDictionary = context.EntitySetTypeMapDictionary; + if (entitySetTypeMapDictionary == null || entitySetTypeMapDictionary.Count == 0) { return null; } @@ -41,7 +41,7 @@ public async Task GetModelAsync(ModelContext context, CancellationTok MethodInfo method = typeof(ODataConventionModelBuilder) .GetMethod("EntitySet", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); - foreach (var pair in collection) + foreach (var pair in entitySetTypeMapDictionary) { // Build a method with the specific type argument var specifiedMethod = method.MakeGenericMethod(pair.Value); @@ -53,7 +53,27 @@ public async Task GetModelAsync(ModelContext context, CancellationTok specifiedMethod.Invoke(builder, parameters); } - context.EntitySetTypeMapCollection.Clear(); + entitySetTypeMapDictionary.Clear(); + + var entityTypeKeyPropertiesMapDictionary = context.EntityTypeKeyPropertiesMapDictionary; + if (entityTypeKeyPropertiesMapDictionary != null) + { + foreach (var pair in entityTypeKeyPropertiesMapDictionary) + { + var edmTypeConfiguration = builder.GetTypeConfigurationOrNull(pair.Key) as EntityTypeConfiguration; + if (edmTypeConfiguration == null) + { + continue; + } + + foreach (var property in pair.Value) + { + edmTypeConfiguration.HasKey(property); + } + } + + entityTypeKeyPropertiesMapDictionary.Clear(); + } return builder.GetEdmModel(); } diff --git a/test/Microsoft.Restier.Publishers.OData.Test/Microsoft.Restier.Publishers.OData.Test.csproj b/test/Microsoft.Restier.Publishers.OData.Test/Microsoft.Restier.Publishers.OData.Test.csproj index 4dad07bb..32adecd8 100644 --- a/test/Microsoft.Restier.Publishers.OData.Test/Microsoft.Restier.Publishers.OData.Test.csproj +++ b/test/Microsoft.Restier.Publishers.OData.Test/Microsoft.Restier.Publishers.OData.Test.csproj @@ -35,8 +35,13 @@ 4 - + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + True + + + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + True ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0-rc2-final\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll diff --git a/test/Microsoft.Restier.Publishers.OData.Test/app.config b/test/Microsoft.Restier.Publishers.OData.Test/app.config index 6101cbb7..24b854b1 100644 --- a/test/Microsoft.Restier.Publishers.OData.Test/app.config +++ b/test/Microsoft.Restier.Publishers.OData.Test/app.config @@ -1,5 +1,9 @@  + +
+ + @@ -16,4 +20,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.Restier.Publishers.OData.Test/packages.config b/test/Microsoft.Restier.Publishers.OData.Test/packages.config index 7dc15fa2..fbf6eeb4 100644 --- a/test/Microsoft.Restier.Publishers.OData.Test/packages.config +++ b/test/Microsoft.Restier.Publishers.OData.Test/packages.config @@ -1,5 +1,6 @@  + diff --git a/test/Microsoft.Restier.TestCommon/PublicApi.bsl b/test/Microsoft.Restier.TestCommon/PublicApi.bsl index a710d8ac..a111bd88 100644 --- a/test/Microsoft.Restier.TestCommon/PublicApi.bsl +++ b/test/Microsoft.Restier.TestCommon/PublicApi.bsl @@ -299,7 +299,8 @@ public interface Microsoft.Restier.Core.Model.IModelMapper { public class Microsoft.Restier.Core.Model.ModelContext : Microsoft.Restier.Core.InvocationContext { public ModelContext (Microsoft.Restier.Core.ApiContext apiContext) - System.Collections.ObjectModel.Collection`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Type]]]] EntitySetTypeMapCollection { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } + System.Collections.Generic.IDictionary`2[[System.String],[System.Type]] EntitySetTypeMapDictionary { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } + System.Collections.Generic.IDictionary`2[[System.Type],[System.Collections.Generic.ICollection`1[[System.Reflection.PropertyInfo]]]] EntityTypeKeyPropertiesMapDictionary { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } } public interface Microsoft.Restier.Core.Query.IQueryExecutor { diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestGetNorthwindMetadata.txt b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestGetNorthwindMetadata.txt index 70017644..2d96d116 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestGetNorthwindMetadata.txt +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/Baselines/TestGetNorthwindMetadata.txt @@ -12,34 +12,71 @@ - + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -59,6 +96,14 @@ + + + + + + + + @@ -88,71 +133,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - - - - - - - + + + + + + @@ -189,17 +180,26 @@ - + - + - - - - - - - + + + + + + + + + + + + + + + + @@ -216,19 +216,10 @@ - - - - - - - - - - - - - + + + + @@ -240,10 +231,22 @@ - - - - + + + + + + + + + + + + + + + + @@ -254,10 +257,7 @@ - - - - + diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/Models/NorthwindApi.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/Models/NorthwindApi.cs index 5b3c7cf6..db318787 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/Models/NorthwindApi.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind/Models/NorthwindApi.cs @@ -126,7 +126,7 @@ public async Task GetModelAsync(ModelContext context, CancellationTok // EF Model builder does not build model any more but just entity set name and entity type map if (model == null) { - var collection = context.EntitySetTypeMapCollection; + var collection = context.EntitySetTypeMapDictionary; if (collection == null || collection.Count == 0) { return null; @@ -149,7 +149,7 @@ public async Task GetModelAsync(ModelContext context, CancellationTok } // Clear the map collection to make RESTier model builder will not build the model again. - context.EntitySetTypeMapCollection.Clear(); + collection.Clear(); model = builder.GetEdmModel(); } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/Airline.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/Airline.cs index 4d717e58..fb5f01d3 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/Airline.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/Airline.cs @@ -8,7 +8,6 @@ namespace Microsoft.OData.Service.Sample.Trippin.Models { public class Airline { - [Key] public string AirlineCode { get; set; } public string Name { get; set; } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs index 68a26033..a2861d4d 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Models/TrippinModel.cs @@ -22,6 +22,8 @@ public TrippinModel() protected override void OnModelCreating(DbModelBuilder modelBuilder) { + modelBuilder.Entity().HasKey(a =>a.AirlineCode); + modelBuilder.Entity().HasMany(s => s.Flights).WithMany().Map(c => { c.MapLeftKey("TripId");