diff --git a/build/dependencies.props b/build/dependencies.props index eab740c..d970e47 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -12,7 +12,7 @@ netstandard2.0 netcoreapp2.2 7.5.4 - 2.3.0 + 2.3.1 3.2.0 16.1.1 $(OdataToEntityVersion) diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DbDynamicMetadataProvider.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DbDynamicMetadataProvider.cs index 1294398..4212cd0 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DbDynamicMetadataProvider.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DbDynamicMetadataProvider.cs @@ -48,8 +48,8 @@ public override DynamicDependentPropertyInfo GetDependentProperties(String table SchemaContext schemaContext = _dbContextPool.Rent(); try { - (String, String) tableFullName = _schemaCache.GetTables(schemaContext)[tableName]; - if (_schemaCache.GetNavigations(schemaContext).TryGetValue(tableFullName, out List navigations)) + (String tableSchema, String tableName, bool isQueryType) tableFullName = _schemaCache.GetTables(schemaContext)[tableName]; + if (_schemaCache.GetNavigations(schemaContext).TryGetValue((tableFullName.tableSchema, tableFullName.tableName), out List navigations)) foreach (SchemaCache.Navigation navigation in navigations) if (navigation.NavigationName == navigationPropertyName) { @@ -77,13 +77,13 @@ public override String GetEntityName(String tableName) if (!String.IsNullOrEmpty(navigationMapping.ManyToManyTarget)) yield return (navigationMapping.NavigationName, navigationMapping.ManyToManyTarget); } - public override IEnumerable GetNavigationProperties(String tableName) + public override IEnumerable GetNavigationProperties(String tableEdmName) { SchemaContext schemaContext = _dbContextPool.Rent(); try { - (String, String) tableFullName = _schemaCache.GetTables(schemaContext)[tableName]; - if (_schemaCache.GetNavigations(schemaContext).TryGetValue(tableFullName, out List navigations)) + (String tableSchema, String tableName, bool isQueryType) tableFullName = _schemaCache.GetTables(schemaContext)[tableEdmName]; + if (_schemaCache.GetNavigations(schemaContext).TryGetValue((tableFullName.tableSchema, tableFullName.tableName), out List navigations)) foreach (SchemaCache.Navigation navigation in navigations) yield return navigation.NavigationName; } @@ -92,11 +92,20 @@ public override IEnumerable GetNavigationProperties(String tableName) _dbContextPool.Return(schemaContext); } } - public override IEnumerable GetPrimaryKey(String tableName) + public override IEnumerable GetPrimaryKey(String tableEdmName) { - foreach (DbColumn column in _schemaCache.GetColumns(tableName)) - if (column.IsKey.GetValueOrDefault()) - yield return column.ColumnName; + SchemaContext schemaContext = _dbContextPool.Rent(); + try + { + (String tableSchema, String tableName) tableFullName = _schemaCache.GetTableFullName(tableEdmName); + String constraintName = _schemaCache.GetPrimaryKeyConstraintNames(schemaContext)[tableFullName]; + List keyColumns = _schemaCache.GetKeyColumns(schemaContext)[(tableFullName.tableSchema, constraintName)]; + return keyColumns.OrderBy(c => c.OrdinalPosition).Select(c => c.ColumnName); + } + finally + { + _dbContextPool.Return(schemaContext); + } } public override IEnumerable GetStructuralProperties(String tableName) { @@ -121,13 +130,13 @@ public override String GetTableName(String entityName) { return entityName; } - public override IEnumerable GetTableNames() + public override IEnumerable<(String tableEdmName, bool isQueryType)> GetTableNames() { SchemaContext schemaContext = _dbContextPool.Rent(); try { - ICollection tableNames = _schemaCache.GetTables(schemaContext).Keys; - return tableNames; + foreach (var pair in _schemaCache.GetTables(schemaContext)) + yield return (pair.Key, pair.Value.isQueryType); } finally { diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicDataAdapter.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicDataAdapter.cs index 0cbc49b..f3d8342 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicDataAdapter.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicDataAdapter.cs @@ -20,7 +20,7 @@ private static Db.OeEntitySetAdapterCollection CreateEntitySetAdapters(DynamicTy var entitySetAdapters = new Db.OeEntitySetAdapter[typeDefinitionManager.TypeDefinitions.Count]; int i = 0; foreach (DynamicTypeDefinition typeDefinition in typeDefinitionManager.TypeDefinitions) - entitySetAdapters[i++] = CreateEntitySetAdapter(typeDefinition.DynamicTypeType, typeDefinition.TableName, false); + entitySetAdapters[i++] = CreateEntitySetAdapter(typeDefinition.DynamicTypeType, typeDefinition.TableName, typeDefinition.IsQueryType); return new Db.OeEntitySetAdapterCollection(entitySetAdapters); } diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicEdmModelMetadataProvider.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicEdmModelMetadataProvider.cs index fbcae64..05ba1c9 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicEdmModelMetadataProvider.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicEdmModelMetadataProvider.cs @@ -46,7 +46,7 @@ public override IReadOnlyList GetManyToManyProperties(Type clrType if (properties == null) properties = new List(); - Type itemType = _typeDefinitionManager.GetDynamicTypeDefinition(targetTableName).DynamicTypeType; + Type itemType = _typeDefinitionManager.GetDynamicTypeDefinition(targetTableName, false).DynamicTypeType; Type propertyType = typeof(ICollection<>).MakeGenericType(itemType); properties.Add(new OeShadowPropertyInfo(clrType, propertyType, propertyName)); } diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicMetadataProvider.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicMetadataProvider.cs index 98d34c4..d8363a1 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicMetadataProvider.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicMetadataProvider.cs @@ -13,7 +13,7 @@ public abstract class DynamicMetadataProvider public abstract IEnumerable GetPrimaryKey(String tableEdmName); public abstract IEnumerable GetStructuralProperties(String tableEdmName); public abstract String GetTableName(String entityName); - public abstract IEnumerable GetTableNames(); + public abstract IEnumerable<(String tableEdmName, bool isQueryType)> GetTableNames(); public abstract DbContextOptions DbContextOptions { get; } } diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicModelBuilder.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicModelBuilder.cs index a0d7730..b7ef808 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicModelBuilder.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicModelBuilder.cs @@ -21,17 +21,17 @@ public DynamicModelBuilder(DynamicTypeDefinitionManager typeDefinitionManager) public void Build(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder) { - foreach (String tableName in MetadataProvider.GetTableNames()) + foreach ((String tableEdmName, bool isQueryType) in MetadataProvider.GetTableNames()) { - CreateEntityType(modelBuilder, tableName); - CreateNavigationProperties(modelBuilder, tableName); + CreateEntityType(modelBuilder, tableEdmName, isQueryType); + CreateNavigationProperties(modelBuilder, tableEdmName); } } - private EntityType CreateEntityType(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder, String tableName) + private EntityType CreateEntityType(Microsoft.EntityFrameworkCore.ModelBuilder modelBuilder, String tableName, bool isQueryType) { if (!_entityTypes.TryGetValue(tableName, out EntityType entityType)) { - var dynamicTypeDefinition = TypeDefinitionManager.GetDynamicTypeDefinition(tableName); + var dynamicTypeDefinition = TypeDefinitionManager.GetDynamicTypeDefinition(tableName, isQueryType); EntityTypeBuilder entityTypeBuilder = modelBuilder.Entity(dynamicTypeDefinition.DynamicTypeType).ToTable(tableName); entityType = (EntityType)entityTypeBuilder.Metadata; @@ -47,7 +47,10 @@ private EntityType CreateEntityType(Microsoft.EntityFrameworkCore.ModelBuilder m propertyBuilder.ValueGeneratedNever(); } - entityTypeBuilder.HasKey(MetadataProvider.GetPrimaryKey(tableName).ToArray()); + if (isQueryType) + entityTypeBuilder.Metadata.IsQueryType = true; + else + entityTypeBuilder.HasKey(MetadataProvider.GetPrimaryKey(tableName).ToArray()); _entityTypes.Add(tableName, entityType); } @@ -60,8 +63,8 @@ private void CreateNavigationProperties(Microsoft.EntityFrameworkCore.ModelBuild { DynamicDependentPropertyInfo dependentInfo = MetadataProvider.GetDependentProperties(tableName, propertyName); - EntityType dependentEntityType = CreateEntityType(modelBuilder, MetadataProvider.GetTableName(dependentInfo.DependentEntityName)); - EntityType principalEntityType = CreateEntityType(modelBuilder, MetadataProvider.GetTableName(dependentInfo.PrincipalEntityName)); + EntityType dependentEntityType = CreateEntityType(modelBuilder, MetadataProvider.GetTableName(dependentInfo.DependentEntityName), false); + EntityType principalEntityType = CreateEntityType(modelBuilder, MetadataProvider.GetTableName(dependentInfo.PrincipalEntityName), false); var dependentProperties = new List(); foreach (String dependentPropertyName in dependentInfo.DependentPropertyNames) @@ -81,7 +84,7 @@ private void CreateNavigationProperties(Microsoft.EntityFrameworkCore.ModelBuild fkey = dependentEntityType.AddForeignKey(dependentProperties, pkey, principalEntityType); } - DynamicTypeDefinition dynamicTypeDefinition = TypeDefinitionManager.GetDynamicTypeDefinition(tableName); + DynamicTypeDefinition dynamicTypeDefinition = TypeDefinitionManager.GetDynamicTypeDefinition(tableName, false); if (dependentInfo.IsCollection) { Navigation navigation = fkey.HasPrincipalToDependent(propertyName); diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinition.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinition.cs index 83ad466..2cbd72d 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinition.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinition.cs @@ -25,11 +25,12 @@ public ShadowPropertyDefinition(MethodInfo methodGet, FieldInfo fieldInfo) private readonly Dictionary _shadowPropertyFieldInfoByGetName; private int _singleFieldIndex; - public DynamicTypeDefinition(Type dynamicTypeType, String entityName, String tableName) + public DynamicTypeDefinition(Type dynamicTypeType, String entityName, String tableName, bool isQueryType) { DynamicTypeType = dynamicTypeType; EntityName = entityName; TableName = tableName; + IsQueryType = isQueryType; _navigationPropertyNames = new Dictionary(); _shadowPropertyDefinitions = new Dictionary(); @@ -98,6 +99,7 @@ public String GetSingleFiledName(String navigationPropertyName) public Type DynamicTypeType { get; } public String EntityName { get; } + public bool IsQueryType { get; } public IReadOnlyCollection PropertyNames => _navigationPropertyNames.Keys; public String TableName { get; } } diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinitionManager.cs b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinitionManager.cs index 7fa03da..9b14311 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinitionManager.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/DynamicTypeDefinitionManager.cs @@ -67,14 +67,14 @@ public static IQueryable GetQueryable(DynamicDbContext dynamicDbCon ConstructorInfo ctor = dbSetType.GetConstructor(new Type[] { typeof(IAsyncQueryProvider) }); return (IQueryable)ctor.Invoke(new Object[] { dynamicDbContext.GetDependencies().QueryProvider }); } - public DynamicTypeDefinition GetDynamicTypeDefinition(String tableName) + public DynamicTypeDefinition GetDynamicTypeDefinition(String tableName, bool isQueryType) { if (_tableNameTypes.TryGetValue(tableName, out Type dynamicTypeType)) return GetDynamicTypeDefinition(dynamicTypeType); dynamicTypeType = GetDynamicTypeType(); String entityName = MetadataProvider.GetEntityName(tableName); - var dynamicTypeDefinition = new DynamicTypeDefinition(dynamicTypeType, entityName, tableName); + var dynamicTypeDefinition = new DynamicTypeDefinition(dynamicTypeType, entityName, tableName, isQueryType); _tableNameTypes.Add(tableName, dynamicTypeType); _dynamicTypeDefinitions.Add(dynamicTypeType, dynamicTypeDefinition); return dynamicTypeDefinition; diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/EdmDynamicMetadataProvider.cs b/source/OdataToEntity.EfCore.DynamicDataContext/EdmDynamicMetadataProvider.cs index 19726ea..75da61e 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/EdmDynamicMetadataProvider.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/EdmDynamicMetadataProvider.cs @@ -110,10 +110,10 @@ public override String GetTableName(String entityName) throw new InvalidOperationException("Table for entity name " + entityName + " not found"); } - public override IEnumerable GetTableNames() + public override IEnumerable<(String tableEdmName, bool isQueryType)> GetTableNames() { foreach (IEdmEntitySet entitySet in _edmModel.EntityContainer.EntitySets()) - yield return entitySet.Name; + yield return (entitySet.Name, false); } public override DbContextOptions DbContextOptions => throw new NotImplementedException(); diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaCache.cs b/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaCache.cs index dee91bc..876cac0 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaCache.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaCache.cs @@ -27,19 +27,20 @@ public Navigation(String constraintSchema, String dependentConstraintName, Strin public String PrincipalConstraintName { get; } } - private readonly Dictionary<(String, String), ReadOnlyCollection> _columns; - private Dictionary<(String, String), List> _keyColumns; - private Dictionary<(String, String), ICollection> _navigationMappings; - private Dictionary<(String, String), List> _tableNavigations; - private Dictionary _tableEdmNameFullNames; - private Dictionary<(String, String), String> _tableFullNameEdmNames; + private readonly Dictionary<(String tableSchema, String tableName), ReadOnlyCollection> _columns; + private Dictionary<(String constraintSchema, String constraintName), List> _keyColumns; + private Dictionary<(String tableSchema, String tableName), ICollection> _navigationMappings; + private Dictionary<(String tableSchema, String tableName), String> _primaryKeys; + private Dictionary<(String tableSchema, String tableName), List> _tableNavigations; + private Dictionary _tableEdmNameFullNames; + private Dictionary<(String tableSchema, String tableName), String> _tableFullNameEdmNames; public SchemaCache() { _columns = new Dictionary<(String, String), ReadOnlyCollection>(); } - private void AddNavigation(Dictionary<(String, String), List> tableNavigations, ReferentialConstraint fkey, KeyColumnUsage keyColumn, String navigationName, bool isCollection) + private void AddNavigation(ReferentialConstraint fkey, KeyColumnUsage keyColumn, String navigationName, bool isCollection) { if (_navigationMappings.TryGetValue((keyColumn.TableSchema, keyColumn.TableName), out ICollection navigationMappings)) foreach (NavigationMapping navigationMapping in navigationMappings) @@ -51,34 +52,34 @@ private void AddNavigation(Dictionary<(String, String), List> tableN if (!String.IsNullOrEmpty(navigationName)) { - (String, String) tableFullName = (keyColumn.TableSchema, keyColumn.TableName); - if (!tableNavigations.TryGetValue(tableFullName, out List principalNavigations)) + (String tableName, String tableSchema) tableFullName = (keyColumn.TableSchema, keyColumn.TableName); + if (!_tableNavigations.TryGetValue(tableFullName, out List principalNavigations)) { principalNavigations = new List(); - tableNavigations.Add(tableFullName, principalNavigations); + _tableNavigations.Add(tableFullName, principalNavigations); } principalNavigations.Add(new Navigation(fkey.ConstraintSchema, fkey.ConstraintName, fkey.UniqueConstraintName, navigationName, isCollection)); } } - public ReadOnlyCollection GetColumns(String tableName) + public ReadOnlyCollection GetColumns(String tableEdmName) { - if (!_tableEdmNameFullNames.TryGetValue(tableName, out (String, String) tableFullName)) + if (!_tableEdmNameFullNames.TryGetValue(tableEdmName, out (String tableSchema, String tableName, bool isQueryType) tableFullName)) return null; - return GetColumns(tableFullName.Item1, tableFullName.Item2); + return GetColumns(tableFullName.tableSchema, tableFullName.tableName); } public ReadOnlyCollection GetColumns(String tableSchema, String tableName) { return _columns[(tableSchema, tableName)]; } - public Dictionary<(String, String), List> GetKeyColumns(SchemaContext schemaContext) + public Dictionary<(String constraintSchema, String constraintName), List> GetKeyColumns(SchemaContext schemaContext) { if (_keyColumns == null) { - var keyColumns = new Dictionary<(String, String), List>(); + var keyColumns = new Dictionary<(String constraintSchema, String constraintName), List>(); foreach (KeyColumnUsage keyColumn in schemaContext.KeyColumnUsage) { - var key = ValueTuple.Create(keyColumn.ConstraintSchema, keyColumn.ConstraintName); + var key = (keyColumn.ConstraintSchema, keyColumn.ConstraintName); if (!keyColumns.TryGetValue(key, out List columns)) { columns = new List(); @@ -93,8 +94,8 @@ public ReadOnlyCollection GetColumns(String tableSchema, String tableN } public ICollection GetNavigationMappings(String tableEdmName) { - if (_tableEdmNameFullNames.TryGetValue(tableEdmName, out (String, String) tableFullName)) - if (_navigationMappings.TryGetValue((tableFullName.Item1, tableFullName.Item2), out ICollection _navigationMapping)) + if (_tableEdmNameFullNames.TryGetValue(tableEdmName, out (String tableSchema, String tableName, bool isQueryType) tableFullName)) + if (_navigationMappings.TryGetValue((tableFullName.tableSchema, tableFullName.tableName), out ICollection _navigationMapping)) return _navigationMapping; return Array.Empty(); @@ -103,10 +104,10 @@ public ICollection GetNavigationMappings(String tableEdmName) { if (_tableNavigations == null) { + _tableNavigations = new Dictionary<(String tableSchema, String tableName), List>(); var keyColumns = GetKeyColumns(schemaContext); var navigationCounter = new Dictionary<(String, String, String), int>(); - var tableNavigations = new Dictionary<(String, String), List>(); foreach (ReferentialConstraint fkey in schemaContext.ReferentialConstraints) { KeyColumnUsage dependentKeyColumn = keyColumns[(fkey.ConstraintSchema, fkey.ConstraintName)][0]; @@ -145,11 +146,9 @@ public ICollection GetNavigationMappings(String tableEdmName) principalNavigationName += scounter; } - AddNavigation(tableNavigations, fkey, dependentKeyColumn, dependentNavigationName, false); - AddNavigation(tableNavigations, fkey, principalKeyColumn, principalNavigationName, true); + AddNavigation(fkey, dependentKeyColumn, dependentNavigationName, false); + AddNavigation(fkey, principalKeyColumn, principalNavigationName, true); } - - _tableNavigations = tableNavigations; } return _tableNavigations; @@ -179,12 +178,23 @@ int GetCount(ReadOnlyCollection columns, String navigationName, int co return counter; } } + public Dictionary<(String tableSchema, String tableName), String> GetPrimaryKeyConstraintNames(SchemaContext schemaContext) + { + if (_primaryKeys == null) + _primaryKeys = schemaContext.TableConstraints.Where(t => t.ConstraintType == "PRIMARY KEY").ToDictionary(t => (t.TableSchema, t.TableName), t => t.ConstraintName); + return _primaryKeys; + } public String GetTableEdmName(String tableSchema, String tableName) { _tableFullNameEdmNames.TryGetValue((tableSchema, tableName), out String tableEdmName); return tableEdmName; } - public Dictionary GetTables(SchemaContext schemaContext) + public (String tableSchema, String tableName) GetTableFullName(String tableEdmName) + { + (String tableSchema, String tableName, bool isQueryType) table = _tableEdmNameFullNames[tableEdmName]; + return (table.tableSchema, table.tableName); + } + public Dictionary GetTables(SchemaContext schemaContext) { if (_tableEdmNameFullNames == null) { @@ -194,11 +204,11 @@ public String GetTableEdmName(String tableSchema, String tableName) var navigationMappings = new Dictionary<(String, String), ICollection>(); - var tableEdmNameFullNames = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - var tableFullNameEdmNames = new Dictionary<(String, String), String>(); + var tableEdmNameFullNames = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var tableFullNameEdmNames = new Dictionary<(String tableSchema, String tableName), String>(); var fixTableNames = new List(); - List tables = schemaContext.Tables.Where(t => t.TableType == "BASE TABLE").ToList(); + List
tables = schemaContext.Tables.ToList(); foreach (Table table in tables) { String tableName = table.TableName; @@ -208,7 +218,6 @@ public String GetTableEdmName(String tableSchema, String tableName) tableName = table.TableSchema + table.TableName; } - (String, String) tableFullName = (table.TableSchema, table.TableName); if (tableMappings != null) { if (tableMappings.TryGetValue(table.TableName, out TableMapping tableMapping) || @@ -225,14 +234,14 @@ public String GetTableEdmName(String tableSchema, String tableName) } if (tableMapping.Navigations != null && tableMapping.Navigations.Count > 0) - navigationMappings.Add(tableFullName, tableMapping.Navigations); + navigationMappings.Add((table.TableSchema, table.TableName), tableMapping.Navigations); } else continue; } - tableEdmNameFullNames.Add(tableName, tableFullName); - tableFullNameEdmNames.Add(tableFullName, tableName); + tableEdmNameFullNames.Add(tableName, (table.TableSchema, table.TableName, table.TableType == "VIEW")); + tableFullNameEdmNames.Add((table.TableSchema, table.TableName), tableName); ReadOnlyCollection columns = schemaContext.GetColumns(table.TableSchema, table.TableName); _columns.Add((table.TableSchema, table.TableName), columns); @@ -241,7 +250,7 @@ public String GetTableEdmName(String tableSchema, String tableName) foreach (String tableName in fixTableNames) { int index = tables.FindIndex(t => t.TableName == tableName); - tableEdmNameFullNames[tables[index].TableSchema + tables[index].TableName] = (tables[index].TableSchema, tables[index].TableName); + tableEdmNameFullNames[tables[index].TableSchema + tables[index].TableName] = (tables[index].TableSchema, tables[index].TableName, tables[index].TableType == "VIEW"); } _navigationMappings = navigationMappings; diff --git a/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaTables.cs b/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaTables.cs index 004a021..74ac475 100644 --- a/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaTables.cs +++ b/source/OdataToEntity.EfCore.DynamicDataContext/InformationSchema/SchemaTables.cs @@ -65,6 +65,21 @@ public sealed class Table public String TableType { get; set; } } + [Table("TABLE_CONSTRAINTS", Schema = "INFORMATION_SCHEMA")] + public sealed class TableConstraint + { + [Column("CONSTRAINT_SCHEMA")] + public String ConstraintSchema { get; set; } + [Column("CONSTRAINT_NAME")] + public String ConstraintName { get; set; } + [Column("TABLE_SCHEMA")] + public String TableSchema { get; set; } + [Column("TABLE_NAME")] + public String TableName { get; set; } + [Column("CONSTRAINT_TYPE")] + public String ConstraintType { get; set; } + } + public sealed class SchemaContext : DbContext { public SchemaContext(DbContextOptions options) : base(options) @@ -82,7 +97,7 @@ public ReadOnlyCollection GetColumns(String tableSchema, String tableN try { command.CommandText = "select * from " + tableSchema + "." + tableName; - using (DbDataReader dataReader = command.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) + using (DbDataReader dataReader = command.ExecuteReader(CommandBehavior.SchemaOnly)) return dataReader.GetColumnSchema(); } finally @@ -95,6 +110,7 @@ public ReadOnlyCollection GetColumns(String tableSchema, String tableN public DbQuery Columns { get; set; } public DbQuery KeyColumnUsage { get; set; } public DbQuery ReferentialConstraints { get; set; } + public DbQuery TableConstraints { get; set; } public DbQuery
Tables { get; set; } } } diff --git a/source/OdataToEntity/OeParser.cs b/source/OdataToEntity/OeParser.cs index fc32337..02aa93d 100644 --- a/source/OdataToEntity/OeParser.cs +++ b/source/OdataToEntity/OeParser.cs @@ -99,7 +99,7 @@ public Object GetService(Type serviceType) public OeParser(Uri baseUri, IEdmModel edmModel) : this(baseUri, edmModel, null, null) { } - public OeParser(Uri baseUri, IEdmModel edmModel, Query.OeModelBoundProvider modelBoundProvider, IServiceProvider serviceProvider) + public OeParser(Uri baseUri, IEdmModel edmModel, Query.OeModelBoundProvider modelBoundProvider, IServiceProvider serviceProvider = null) { BaseUri = baseUri; EdmModel = edmModel; diff --git a/source/OdataToEntity/Parsers/Translators/OeSelectItem.cs b/source/OdataToEntity/Parsers/Translators/OeSelectItem.cs index c5cfd20..0e8adee 100644 --- a/source/OdataToEntity/Parsers/Translators/OeSelectItem.cs +++ b/source/OdataToEntity/Parsers/Translators/OeSelectItem.cs @@ -15,8 +15,8 @@ internal enum OeNavigationSelectItemKind internal sealed class OeNavigationSelectItem { + private bool _allSelected; private readonly List _navigationItems; - private bool _selectAll; private readonly List _structuralItems; private readonly List _structuralItemsNotSelected; @@ -25,6 +25,7 @@ private OeNavigationSelectItem() _navigationItems = new List(); _structuralItems = new List(); _structuralItemsNotSelected = new List(); + _allSelected = true; } public OeNavigationSelectItem(ODataUri odataUri) : this() { @@ -49,15 +50,16 @@ public OeNavigationSelectItem(IEdmEntitySetBase entitySet, OeNavigationSelectIte internal void AddKeyRecursive(bool notSelected) { - if (StructuralItems.Count > 0) + if (!AllSelected) foreach (IEdmStructuralProperty keyProperty in EntitySet.EntityType().Key()) AddStructuralItem(keyProperty, notSelected); foreach (OeNavigationSelectItem childNavigationItem in _navigationItems) childNavigationItem.AddKeyRecursive(notSelected); } - internal OeNavigationSelectItem AddOrGetNavigationItem(OeNavigationSelectItem navigationItem) + internal OeNavigationSelectItem AddOrGetNavigationItem(OeNavigationSelectItem navigationItem, bool isExpand) { + _allSelected &= isExpand; OeNavigationSelectItem existingNavigationItem = FindChildrenNavigationItem(navigationItem.EdmProperty); if (existingNavigationItem == null) { @@ -67,16 +69,11 @@ internal OeNavigationSelectItem AddOrGetNavigationItem(OeNavigationSelectItem na if (existingNavigationItem.Kind == OeNavigationSelectItemKind.NotSelected && navigationItem.Kind == OeNavigationSelectItemKind.Normal) existingNavigationItem.Kind = OeNavigationSelectItemKind.Normal; - else if (existingNavigationItem.StructuralItems.Count == 0) - existingNavigationItem._selectAll = true; return existingNavigationItem; } public bool AddStructuralItem(IEdmStructuralProperty structuralProperty, bool notSelected) { - if (_selectAll) - return false; - if (notSelected) { if (FindStructuralItemIndex(_structuralItemsNotSelected, structuralProperty) != -1) @@ -155,7 +152,7 @@ public IReadOnlyList GetJoinPath() } public IReadOnlyList GetStructuralItemsWithNotSelected() { - if (_structuralItems.Count == 0) + if (AllSelected) throw new InvalidOperationException("StructuralItems.Count > 0"); if (_structuralItemsNotSelected.Count == 0) @@ -183,13 +180,13 @@ public bool HasNavigationItems() public IEdmNavigationProperty EdmProperty { get; } public IEdmEntitySetBase EntitySet { get; } public OeEntryFactory EntryFactory { get; set; } - public OeNavigationSelectItemKind Kind { get; private set; } + public OeNavigationSelectItemKind Kind { get; private set; } public IReadOnlyList NavigationItems => _navigationItems; public ExpandedNavigationSelectItem NavigationSelectItem { get; } public int PageSize { get; set; } public OeNavigationSelectItem Parent { get; } public ODataPath Path { get; } - public IReadOnlyList StructuralItems => _structuralItems; + public bool AllSelected => _allSelected && _structuralItems.Count == 0; } internal readonly struct OeStructuralSelectItem diff --git a/source/OdataToEntity/Parsers/Translators/OeSelectItemTranslator.cs b/source/OdataToEntity/Parsers/Translators/OeSelectItemTranslator.cs index 1a5b37d..321b186 100644 --- a/source/OdataToEntity/Parsers/Translators/OeSelectItemTranslator.cs +++ b/source/OdataToEntity/Parsers/Translators/OeSelectItemTranslator.cs @@ -15,6 +15,21 @@ public OeSelectItemTranslator(IEdmModel edmModel, bool notSelected) _notSelected = notSelected; } + private OeNavigationSelectItem AddOrGetNavigationItem(OeNavigationSelectItem parentNavigationItem, ExpandedNavigationSelectItem item, bool isExpand) + { + IEdmEntitySetBase entitySet = OeEdmClrHelper.GetEntitySet(_edmModel, item); + + OeNavigationSelectItemKind kind; + if (_notSelected) + kind = OeNavigationSelectItemKind.NotSelected; + else if (item.SelectAndExpand.IsNextLink()) + kind = OeNavigationSelectItemKind.NextLink; + else + kind = OeNavigationSelectItemKind.Normal; + + var childNavigationSelectItem = new OeNavigationSelectItem(entitySet, parentNavigationItem, item, kind); + return parentNavigationItem.AddOrGetNavigationItem(childNavigationSelectItem, isExpand); + } public void Translate(OeNavigationSelectItem parentNavigationItem, SelectItem item) { if (item is ExpandedNavigationSelectItem expandedNavigationSelectItem) @@ -39,18 +54,7 @@ private void Translate(OeNavigationSelectItem parentNavigationItem, OePageSelect } private void Translate(OeNavigationSelectItem parentNavigationItem, ExpandedNavigationSelectItem item) { - IEdmEntitySetBase entitySet = OeEdmClrHelper.GetEntitySet(_edmModel, item); - - OeNavigationSelectItemKind kind; - if (_notSelected) - kind = OeNavigationSelectItemKind.NotSelected; - else if (item.SelectAndExpand.IsNextLink()) - kind = OeNavigationSelectItemKind.NextLink; - else - kind = OeNavigationSelectItemKind.Normal; - - var childNavigationSelectItem = new OeNavigationSelectItem(entitySet, parentNavigationItem, item, kind); - childNavigationSelectItem = parentNavigationItem.AddOrGetNavigationItem(childNavigationSelectItem); + OeNavigationSelectItem childNavigationSelectItem = AddOrGetNavigationItem(parentNavigationItem, item, true); if (childNavigationSelectItem.Kind == OeNavigationSelectItemKind.NextLink) return; @@ -65,8 +69,8 @@ private void Translate(OeNavigationSelectItem parentNavigationItem, PathSelectIt if (navigationSource == null) navigationSource = OeEdmClrHelper.GetEntitySet(_edmModel, navigationSegment.NavigationProperty); - var selectItemClause = new ExpandedNavigationSelectItem(new ODataExpandPath(item.SelectedPath), navigationSource, new SelectExpandClause(null, true)); - Translate(parentNavigationItem, selectItemClause); + var expandedItem = new ExpandedNavigationSelectItem(new ODataExpandPath(item.SelectedPath), navigationSource, new SelectExpandClause(null, true)); + AddOrGetNavigationItem(parentNavigationItem, expandedItem, false); } else if (item.SelectedPath.LastSegment is PropertySegment propertySegment) parentNavigationItem.AddStructuralItem(propertySegment.Property, _notSelected); diff --git a/source/OdataToEntity/Parsers/Translators/OeSelectTranslator.cs b/source/OdataToEntity/Parsers/Translators/OeSelectTranslator.cs index 0f6b4d9..508799a 100644 --- a/source/OdataToEntity/Parsers/Translators/OeSelectTranslator.cs +++ b/source/OdataToEntity/Parsers/Translators/OeSelectTranslator.cs @@ -282,7 +282,7 @@ private MethodCallExpression CreateSelectExpression(Expression source) if (_rootNavigationItem.HasNavigationItems()) return (MethodCallExpression)source; - if (_rootNavigationItem.StructuralItems.Count == 0) + if (_rootNavigationItem.AllSelected) return (MethodCallExpression)source; ParameterExpression parameter = _joinBuilder.Visitor.Parameter; @@ -322,7 +322,7 @@ private static List FlattenNavigationItems(OeNavigationS } private static OePropertyAccessor[] GetAccessors(Type clrEntityType, OeNavigationSelectItem navigationItem) { - if (navigationItem.StructuralItems.Count == 0) + if (navigationItem.AllSelected) return OePropertyAccessor.CreateFromType(clrEntityType, navigationItem.EntitySet); IReadOnlyList structuralItems = navigationItem.GetStructuralItemsWithNotSelected(); @@ -396,29 +396,34 @@ private static Expression SelectStructuralProperties(Expression source, OeNaviga var newJoins = new Expression[joins.Count]; List navigationItems = FlattenNavigationItems(root, false); + bool isNavigationNullable = false; for (int i = 0; i < navigationItems.Count; i++) { newJoins[i] = joins[i]; - if (navigationItems[i].StructuralItems.Count > 0) + isNavigationNullable |= i > 0 && navigationItems[i].EdmProperty.Type.IsNullable; + if (!navigationItems[i].AllSelected) { IReadOnlyList structuralItems = navigationItems[i].GetStructuralItemsWithNotSelected(); - var properties = new Expression[structuralItems.Count]; - for (int j = 0; j < structuralItems.Count; j++) - if (structuralItems[j].EdmProperty is ComputeProperty computeProperty) - properties[j] = new ReplaceParameterVisitor(joins[i]).Visit(computeProperty.Expression); - else + if (structuralItems.Count > 0) + { + var properties = new Expression[structuralItems.Count]; + for (int j = 0; j < structuralItems.Count; j++) + if (structuralItems[j].EdmProperty is ComputeProperty computeProperty) + properties[j] = new ReplaceParameterVisitor(joins[i]).Visit(computeProperty.Expression); + else + { + PropertyInfo property = joins[i].Type.GetPropertyIgnoreCase(structuralItems[j].EdmProperty); + properties[j] = Expression.Property(joins[i], property); + } + Expression newTupleExpression = OeExpressionHelper.CreateTupleExpression(properties); + + if (isNavigationNullable) { - PropertyInfo property = joins[i].Type.GetPropertyIgnoreCase(structuralItems[j].EdmProperty); - properties[j] = Expression.Property(joins[i], property); + UnaryExpression nullConstant = Expression.Convert(OeConstantToVariableVisitor.NullConstantExpression, newTupleExpression.Type); + newTupleExpression = Expression.Condition(Expression.Equal(joins[i], OeConstantToVariableVisitor.NullConstantExpression), nullConstant, newTupleExpression); } - Expression newTupleExpression = OeExpressionHelper.CreateTupleExpression(properties); - - if (i > 0 && navigationItems[i].EdmProperty.Type.IsNullable) - { - UnaryExpression nullConstant = Expression.Convert(OeConstantToVariableVisitor.NullConstantExpression, newTupleExpression.Type); - newTupleExpression = Expression.Condition(Expression.Equal(joins[i], OeConstantToVariableVisitor.NullConstantExpression), nullConstant, newTupleExpression); + newJoins[i] = newTupleExpression; } - newJoins[i] = newTupleExpression; } } diff --git a/source/OdataToEntity/Query/Builder/EntityTypeConfiguration.cs b/source/OdataToEntity/Query/Builder/EntityTypeConfiguration.cs index dc83006..a2a2eb1 100644 --- a/source/OdataToEntity/Query/Builder/EntityTypeConfiguration.cs +++ b/source/OdataToEntity/Query/Builder/EntityTypeConfiguration.cs @@ -63,10 +63,7 @@ public EntityTypeConfiguration Page(int? maxTopValue, int? pageSizeValu } public PropertyConfiguration Property(Expression> propertyExpression) { - var property = (MemberExpression)propertyExpression.Body; - var propertyInfo = (PropertyInfo)property.Member; - IEdmProperty edmProperty = _entityType.GetPropertyIgnoreCase(propertyInfo.Name); - return new PropertyConfiguration(_modelBuilder, edmProperty); + return new PropertyConfiguration(_modelBuilder, _entityType, propertyExpression); } public EntityTypeConfiguration Select(SelectExpandType expandType, params String[] propertyNames) { diff --git a/source/OdataToEntity/Query/Builder/PropertyConfiguration.cs b/source/OdataToEntity/Query/Builder/PropertyConfiguration.cs index 1c6040f..c75253c 100644 --- a/source/OdataToEntity/Query/Builder/PropertyConfiguration.cs +++ b/source/OdataToEntity/Query/Builder/PropertyConfiguration.cs @@ -15,6 +15,10 @@ internal PropertyConfiguration(OeModelBoundFluentBuilder modelBuilder, IEdmPrope _modelBuilder = modelBuilder; _edmProperty = edmProperty; } + internal PropertyConfiguration(OeModelBoundFluentBuilder modelBuilder, IEdmEntityType entityType, Expression> propertyExpression) + : this(modelBuilder, GetEdmProperty(entityType, propertyExpression)) + { + } public PropertyConfiguration Count(QueryOptionSetting setting) { @@ -47,6 +51,16 @@ public PropertyConfiguration Filter(QueryOptionSetting setting, params } return this; } + private static IEdmProperty GetEdmProperty(IEdmStructuredType entityType, Expression> propertyExpression) + { + MemberExpression property; + if (propertyExpression.Body is UnaryExpression convert) + property = (MemberExpression)convert.Operand; + else + property = (MemberExpression)propertyExpression.Body; + var propertyInfo = (PropertyInfo)property.Member; + return entityType.GetPropertyIgnoreCase(propertyInfo.Name); + } public PropertyConfiguration NavigationNextLink() { var navigationProperty = (IEdmNavigationProperty)_edmProperty; @@ -84,10 +98,7 @@ public PropertyConfiguration Property(String propertyName) } public PropertyConfiguration Property(Expression> propertyExpression) { - var property = (MemberExpression)propertyExpression.Body; - var propertyInfo = (PropertyInfo)property.Member; - IEdmProperty edmProperty = _edmProperty.DeclaringType.GetPropertyIgnoreCase(propertyInfo.Name); - return new PropertyConfiguration(_modelBuilder, edmProperty); + return new PropertyConfiguration(_modelBuilder, GetEdmProperty(_edmProperty.DeclaringType, propertyExpression)); } public PropertyConfiguration Select(SelectExpandType expandType) { diff --git a/test/OdataToEntity.Test.DynamicDataContext/Program.cs b/test/OdataToEntity.Test.DynamicDataContext/Program.cs index 801b90f..43b6e9d 100644 --- a/test/OdataToEntity.Test.DynamicDataContext/Program.cs +++ b/test/OdataToEntity.Test.DynamicDataContext/Program.cs @@ -31,7 +31,7 @@ class Program static async Task Main() { //PerformanceCacheTest.RunTest(100); - await new PLNull(new PLNull_DbFixtureInitDb()).Table(0); + await new PLNull(new PLNull_DbFixtureInitDb()).DbQuery(0); //new PLNull_ManyColumns(new PLNull_ManyColumnsFixtureInitDb()).Filter(1).GetAwaiter().GetResult(); //IEdmModel edmModel = new OeEdmModelBuilder(new OrderDataAdapter(), new OeEdmModelMetadataProvider()).BuildEdmModel(); @@ -77,6 +77,7 @@ public static TableMapping[] GetMappings() { new NavigationMapping("FK_Orders_Customers", "Orders"), new NavigationMapping("FK_Orders_AltCustomers", "AltOrders"), + new NavigationMapping("FK_CustomerShippingAddress_Customers", "CustomerShippingAddresses"), new NavigationMapping(null, "ShippingAddresses") { ManyToManyTarget = "ShippingAddresses" } } }, @@ -111,7 +112,8 @@ public static TableMapping[] GetMappings() { new NavigationMapping("FK_CustomerShippingAddress_ShippingAddresses", "CustomerShippingAddresses"), } - } + }, + new TableMapping("dbo.OrderItemsView") }; } } diff --git a/test/OdataToEntity.Test.EfCore.SqlServer/EdmModelBuilderTest.cs b/test/OdataToEntity.Test.EfCore.SqlServer/EdmModelBuilderTest.cs index 220ab06..912ee4b 100644 --- a/test/OdataToEntity.Test.EfCore.SqlServer/EdmModelBuilderTest.cs +++ b/test/OdataToEntity.Test.EfCore.SqlServer/EdmModelBuilderTest.cs @@ -3,12 +3,6 @@ using OdataToEntity.EfCore; using OdataToEntity.Test.Model; using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using System.Xml.Linq; using Xunit; namespace OdataToEntity.Test diff --git a/test/OdataToEntity.Test/Common/SelectTest.cs b/test/OdataToEntity.Test/Common/SelectTest.cs index 6f7c62c..082a751 100644 --- a/test/OdataToEntity.Test/Common/SelectTest.cs +++ b/test/OdataToEntity.Test/Common/SelectTest.cs @@ -301,6 +301,33 @@ public async Task Expand(int pageSize, bool navigationNextLink) [InlineData(1, false)] [InlineData(0, true)] [InlineData(1, true)] + public async Task ExpandCollectionSingleSelectNestedName(int pageSize, bool navigationNextLink) + { + var parameters = new QueryParameters() + { + RequestUri = "Customers?$expand=CustomerShippingAddresses($select=ShippingAddress;$expand=ShippingAddress($select=Address))&$select=Name&$orderby=Country,Id", + Expression = t => t.Include(c => c.CustomerShippingAddresses).ThenInclude(s => s.ShippingAddress) + .OrderBy(c => c.Country).ThenBy(c => c.Id).Select(c => new + { + CustomerShippingAddresses = c.CustomerShippingAddresses.Select(s => new + { + ShippingAddress = new + { + s.ShippingAddress.Address + } + }), + c.Name + }), + NavigationNextLink = navigationNextLink, + PageSize = pageSize + }; + await Fixture.Execute(parameters).ConfigureAwait(false); + } + [Theory] + [InlineData(0, false)] + [InlineData(1, false)] + [InlineData(0, true)] + [InlineData(1, true)] public async Task ExpandManyToMany(int pageSize, bool navigationNextLink) { var parameters = new QueryParameters() diff --git a/test/OdataToEntity.Test/DbFixture.cs b/test/OdataToEntity.Test/DbFixture.cs index dd012e5..af453dd 100644 --- a/test/OdataToEntity.Test/DbFixture.cs +++ b/test/OdataToEntity.Test/DbFixture.cs @@ -35,7 +35,7 @@ private static OeModelBoundProvider CreateModelBoundProvider(IEdmModel edmModel) modelBoundBuilder.EntitySet("Customers").EntityType .Expand(SelectExpandType.Disabled, "AltOrders") .Expand(SelectExpandType.Automatic, "Orders") - .Property(c => c.Orders).Count(QueryOptionSetting.Disabled); + .Property(c => c.Orders).Count(QueryOptionSetting.Disabled).Property(c => c.Id); modelBoundBuilder.EntitySet("Orders").EntityType .Count(QueryOptionSetting.Allowed) diff --git a/test/OdataToEntity.Test/Model/Order.cs b/test/OdataToEntity.Test/Model/Order.cs index a7711d3..6b59f53 100644 --- a/test/OdataToEntity.Test/Model/Order.cs +++ b/test/OdataToEntity.Test/Model/Order.cs @@ -158,6 +158,14 @@ public sealed class ShippingAddress public sealed class CustomerShippingAddress { + public CustomerShippingAddress() + { + CustomerCountry = OpenTypeConverter.NotSetString; + CustomerId = Int32.MinValue; + ShippingAddressOrderId = Int32.MinValue; + ShippingAddressId = Int32.MinValue; + } + [ForeignKey("CustomerCountry,CustomerId")] public Customer Customer { get; set; } [Key, Column(Order = 0)]