GithubHelp home page GithubHelp logo

voronov-maxim / odatatoentity Goto Github PK

View Code? Open in Web Editor NEW
154.0 15.0 32.0 2.14 MB

OData .net core

License: MIT License

C# 99.23% PLpgSQL 0.50% TSQL 0.27%
odata entity-framework linq2db asp-net-core graphql expression-tree odata-query

odatatoentity's People

Contributors

genusp avatar makarov628 avatar techniq avatar voronov-maxim avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

odatatoentity's Issues

Potential CWE-352 vulnerability in OdataToEntity.AspNetCore

Hello,

So I've run a SAST scan with a certain tool against OdataToEntity source code and it uncovered the following issue: CWE-352 in OdataToEntity.AspNetCore.OeBatchController BatchCore() and Batch() methods.

I think it should be fixed on the application level, not by the library, by introducing CSRF token middleware or authorization filter. The OeBatchController can also be made abstract so that the responsibility for CSRF prevention be moved to the calling application.

Thoughts?

Cheers,
Dmitry

$count option behaviour

Hi!
It is written in OData specification, that the $count system query option ignores any $top, $skip, or $expand query options, and returns the total count of results across all pages including only those results matching any specified $filter and $search
(http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_The_$inlinecount_System).
While testing OdataToEntity I noticed, that query like this (.../entity_name?count=true&$top=0) returns 0 in count, but it should return real count of entities, regardless $top option.
Please, fix $count behaviour according to specification.

Hide a property

Hi,
Can an object property be hidden form the result ?
Is this the way to do it ?

    .Select(SelectExpandType.Disabled, "Id", "OrderId");

Write "how to build" manual please

Hi! Thank you for nice library!
I tried to build project in visual studio and in command prompt - no luck.
To produce attached build logs

build-log-OdataToEntity.Test.sln.txt
build-log-OdataToEntity.Wcf.sln.txt
build-log-OdataToEntityCore.Asp.sln.txt
build-log-OdataToEntityCore.sln.txt
build-log-OdataToEntityCore.Test.sln.txt
build-log-OdataToEntity.AspClient.sln.txt
build-log-OdataToEntity.sln.txt
build-log-OdataToEntity.Test.Linq2Db.sln.txt

I used command line:

C:\Src\OdataToEntity\sln>for /f %f in ('dir *.sln /b') do msbuild %f > build-log-%f.txt

EdmName option bug

Hi!
I'm tested version 2.6 AspServer example with config:

{
  "Tables": [
    {
      "DbName": "dbo.account",
      "EdmName": "Accounts"
    }
  ]
}

and the request GET http://localhost:5000/api/Accounts do responsing :

{"@odata.context":"http://localhost:5000/api/$metadata#Accounts","value":[

the log error:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HM5DIGDM65NR", Request id "0HM5DIGDM65NR:00000002": An unhandled exception was thrown by the application.
      Microsoft.Data.SqlClient.SqlException (0x80131904): Недопустимое имя объекта "dbo.Accounts".
         at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__169_0(Task`1 result)
         at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
         at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)

if do change config to:

{
  "Tables": [
    {
      "DbName": "dbo.account",
      "EdmName": "account"
    }
  ]
}

the request GET http://localhost:5000/api/account return right response.

But need Accounts instead;) How to fix it?

Thank you!

Support for Query Types (DbQuery)

Hi, I'm back with a little bit of time to re-evaluate switching one of my projects from OData/WebApi to OdataToEntity but I've ran into another show stopper right out the gate.

I make use of a few Query Types (DbQuery) in my DbContext and when I attempt to start the project, I get a vague exception thrown that took a bit of time to identity. The exception is thrown in OeEfCoreDataAdapter .CreateDbSetInvoker as it is expecting all properties to be DbSets and it encounters a DbQuery property (I currently have 3 defined on my DbContext).

image

Support selecting [NotMapped] properties

Currently if a property is marked with [NotMapped] it is not being returned by default, and if it's requested explicitly $select=MyNotMappedProperty it throws Could not find a property named 'MyNotMappedProperty' on type 'MyClass'.

I have used [NotMapped] to provide some server generated (non-persisted) properties in my current models. For example:

public class MyClass
{
    // ...
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }

    [NotMapped]
    public string FullName { get { return @"{FirstName} {MiddleName} {LastName}"; } }
}

Support DbContext with DbContextOptions<TDbContext> options and multi-parameter constructors

It is recommended (and in some cases needed) to use the generic DbContextOptions<TDbContext> and not just DbContextOptions, and is what we use in our project.

I've temporarily worked around the issue by taking in the non-generic version, but it would be nice to get around this issue:

"The DbContext of type 'MyCommonDbContext' cannot be pooled because it does not have a single public constructor accepting a single parameter of type DbContextOptions."

image

Also, our CommonDbContext constructor requires a ILogger instance as the second parameter, which complicates things with the way OdataToEntity uses pooling. To workaround this, I subclass CommonDbContext as MyCommonDbContext and create the logger myself, instead of having it dependency injected.

public class FinanceDbDataAdapter : OeEfCoreDataAdapter<MyCommonDbContext>
{
    public FinanceDbDataAdapter(DbContextOptions<MyCommonDbContext> options) : base(options, new OeQueryCache(false))
    {
    }
}

public class MyCommonDbContext : CommonDbContext
{
        public MyCommonDbContext(DbContextOptions<CommonDbContext> options) : base(options, new LoggerFactory().CreateLogger<MyCommonDbContext>())
    {
    }
}

It would be great if I didn't have to do that and instead could just use

public class FinanceDbDataAdapter : OeEfCoreDataAdapter<CommonDbContext>
{
    public FinanceDbDataAdapter(DbContextOptions<CommonDbContext> options) : base(options, new OeQueryCache(false))
    {
    }
}

These might be helpful

Custom ControllerModelConvention to support 'normal' api controller actions?

I want to support 'normal' api controller actions like:

  • /Employees/1 --> return a single employee with id = 1
  • /Employees/1,2,3 --> return a multiple employees with ids = 1, 2 and 3

To get this working, I need to add something to HttpGet("") and probably need a custom ControllerModelConvention (which should be loaded before your conventions) to support this.

Can you help me?

TypeScript client code generator

Hi,

Is there a typescript odata client that you are aware of and can work with this lib and can do nested expand and select ?

Thanks.

ODataUnrecognizedPathException: Resource not found for the segment 'DimCustomer'.

hi,
I am trying to expose a entity framework core dbset (connected to sql server) as odata.
I modified example from tests but got an error:

_An unhandled exception occurred while processing the request.

ODataUnrecognizedPathException: Resource not found for the segment 'DimCustomer'.
Microsoft.OData.UriParser.ODataPathParser.CreateDynamicPathSegment(ODataPathSegment previous, string identifier, string parenthesisExpression)
(see details attached)_
odata05.zip
odata-error.txt

I used the database from this article https://www.codeproject.com/Articles/658912/Create-First-OLAP-Cube-in-SQL-Server-Analysis-Serv

If possible, please provide a complete example with entity framework core in a simple controller (e.g. so I can secure it with Authorize attribute or a custom attribute)

Pagination does not work

Hi

The Pagination according to the wiki does not work. I set it to paginate on size 4.
https://github.com/voronov-maxim/OdataToEntity/wiki/Server-Driven-Paging

This is how I create the dataAdapter (I also tried to use its default constructor). I had to delete the default constructor for connection pooling.
`var optionsBuilder = new DbContextOptionsBuilder();

optionsBuilder = optionsBuilder.UseNpgsql(connStringLocal);
var dataAdapter = new OeEfCoreDataAdapter(optionsBuilder.Options)
{
IsDatabaseNullHighestValue = true //PostgreSql
};`

System.IndexOutOfRangeException: Index was outside the bounds of the array within OeEdmModelMetadataProvider.SortClrPropertyByOrder

Currently running into an issue in my model when using a composite primary key made up of foreign keys.

System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at OdataToEntity.ModelBuilder.OeEdmModelMetadataProvider.SortClrPropertyByOrder(List`1 clrProperties) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/OeEdmModelMetadataProvider.cs:line 86
   at OdataToEntity.ModelBuilder.FKeyInfo.GetDependentStructuralProperties(OeEdmModelMetadataProvider metadataProvider, EntityTypeInfo dependentInfo, PropertyInfo dependentProperty) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/FKeyInfo.cs:line 85
   at OdataToEntity.ModelBuilder.FKeyInfo.Create(OeEdmModelMetadataProvider metadataProvider, Dictionary`2 entityTypes, EntityTypeInfo dependentInfo, PropertyInfo dependentNavigationProperty) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/FKeyInfo.cs:line 49
   at OdataToEntity.ModelBuilder.EntityTypeInfo.BuildProperty(Dictionary`2 entityTypes, Dictionary`2 enumTypes, Dictionary`2 complexTypes, PropertyInfo clrProperty) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/EntityTypeInfo.cs:line 102
   at OdataToEntity.ModelBuilder.EntityTypeInfo.BuildProperties(Dictionary`2 entityTypes, Dictionary`2 enumTypes, Dictionary`2 complexTypes) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/EntityTypeInfo.cs:line 128
   at OdataToEntity.ModelBuilder.OeEdmModelBuilder.BuildEdmModel() in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/OeEdmModelBuilder.cs:line 70
   at OdataToEntity.EfCore.OeEfCoreDataAdapterExtension.BuildEdmModelFromEfCoreModel(OeDataAdapter dataAdapter) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity.EfCore/OeEfCoreDataAdapterExtension.cs:line 18
   at OdataToEntityTest.Startup.ConfigureServices(IServiceCollection services) in /Users/techniq/Documents/Development/playground/odata-to-entity/OdataToEntityTest/Startup.cs:line 40

Here is a model that demonstrates the issue (not my actual model, just a contrived example)

public class MyCommonDbContext : DbContext
    {
        public DbSet<Car> Cars { get; set; }
        public DbSet<State> States { get; set; }
        public DbSet<LicensePlate> LicensePlates { get; set; }

        public MyCommonDbContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Car>().HasKey(c => new { c.StateId, c.LicensePlateId });
        }
    }

    public class Car
    {
        public int StateId { get; set; }
        public int LicensePlateId { get; set; }

        public string Make { get; set; }
        public string Model { get; set; }

        public virtual State State { get; set; }
        public virtual LicensePlate LicensePlate { get; set; }
    }

    public class State
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Car> Cars { get; set; }
    }

    public class LicensePlate
    {
        public int Id { get; set; }
        public string Text { get; set; }

        public virtual ICollection<Car> Cars { get; set; }
    }

In OeEdmModelMetadataProvider.SortClrPropertyByOrder it builds a propertyList based on the size of the passed in clrProperties, but the value returned from OeEfCoreEdmModelMetadataProvider.GetOrder is bigger

public PropertyInfo[] SortClrPropertyByOrder(List<PropertyInfo> clrProperties)
{
    var propertyList = new PropertyInfo[clrProperties.Count];
    foreach (PropertyInfo clrProperty in clrProperties)
    {
        int order = GetOrder(clrProperty);
        if (order == -1)
        {
            clrProperties.CopyTo(propertyList);
            break;
        }
        propertyList[order] = clrProperty;
    }

    return propertyList;
}

Question : configuring logging does not seem to work ?

When adding this code for EnableSensitiveDataLogging and custom loggerfactory, there is still no logging visible in the console when running the api.

var dbLoggerFactory = LoggerFactory.Create(builder =>
            {
                builder
                    .AddFilter("Default", LogLevel.Debug)
                    .AddFilter("Microsoft", LogLevel.Information)
                    //.AddFilter("System", LogLevel.Information)
                    .AddDebug()
                    .AddConsole();
            });

            var optionsBuilder = new DbContextOptionsBuilder<NorthwindContext>();
            optionsBuilder.UseLoggerFactory(dbLoggerFactory); // Warning: Do not create a new ILoggerFactory instance each time
            optionsBuilder.UseSqlServer(Configuration.GetConnectionString("NorthwindContext"), opt => opt.UseRelationalNulls());
            optionsBuilder.EnableSensitiveDataLogging();
            optionsBuilder.EnableDetailedErrors();

            var dataAdapter = new NorthwindDataAdapter(optionsBuilder.Options, true);
            services.AddOdataToEntityMvc(dataAdapter.BuildEdmModelFromEfCoreModel());

Project : https://github.com/StefH/ODataToEntityExampleWebApi/tree/master/ODataToEntityExampleWebApi

"Object reference not set to an instance of an object" within OeEfCoreEdmModelMetadataProvider.GetForeignKey

I'm attempting to migrate from Microsoft.AspNetCore.OData to OdataToEntity but I'm receiving the following exception during dataAdapter.BuildEdmModelFromEfCoreModel() witihin OeEfCoreEdmModelMetadataProvider.GetForeignKey() when I added my relatively complex DbContext/Models (same one I'm currently using with Microsoft.AspNetCore.OData)

System.NullReferenceException: "Object reference not set to an instance of an object."
  at OdataToEntity.EfCore.OeEfCoreEdmModelMetadataProvider.GetForeignKey(PropertyInfo propertyInfo)
   at OdataToEntity.ModelBuilder.FKeyInfo.GetDependentStructuralProperties(OeEdmModelMetadataProvider metadataProvider, EntityTypeInfo dependentInfo, PropertyInfo dependentProperty)
   at OdataToEntity.ModelBuilder.FKeyInfo.Create(OeEdmModelMetadataProvider metadataProvider, Dictionary`2 entityTypes, EntityTypeInfo dependentInfo, PropertyInfo dependentNavigationProperty)
   at OdataToEntity.ModelBuilder.EntityTypeInfo.BuildProperty(Dictionary`2 entityTypes, Dictionary`2 enumTypes, Dictionary`2 complexTypes, PropertyInfo clrProperty)
   at OdataToEntity.ModelBuilder.EntityTypeInfo.BuildProperties(Dictionary`2 entityTypes, Dictionary`2 enumTypes, Dictionary`2 complexTypes)
   at OdataToEntity.ModelBuilder.OeEdmModelBuilder.BuildEdmModel()
   at OdataToEntity.EfCore.OeEfCoreDataAdapterExtension.BuildEdmModelFromEfCoreModel(OeDataAdapter dataAdapter)
   at OdataToEntityTest.Startup.ConfigureServices(IServiceCollection services) in /Users/techniq/Documents/Development/playground/odata-to-entity/OdataToEntityTest/Startup.cs:line 34
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at OdataToEntityTest.Program.BuildWebHost(String[] args) in /Users/techniq/Documents/Development/playground/odata-to-entity/OdataToEntityTest/Program.cs:line 21
   at OdataToEntityTest.Program.Main(String[] args) in /Users/techniq/Documents/Development/playground/odata-to-entity/OdataToEntityTest/Program.cs:17

Support ASP.NET Core 2.1

Finally found a little bit of time to evaluate ODataToEntity for our projects again (with OData/WebApi still lagging behind) and getting the following stack trace while attempting to access a simple Get

[HttpGet]
public ODataResult<Department> Get()
{
    var asyncEnumerator = GetAsyncEnumerator(HttpContext, HttpContext.Response.Body);
    return OData<Department>(asyncEnumerator);
}
System.TypeLoadException: Could not load type 'Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.FrameRequestHeaders' from assembly 'Microsoft.AspNetCore.Server.Kestrel.Core, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
   at OdataToEntity.AspNetCore.OeControllerBase.GetAsyncEnumerator(HttpContext httpContext, Stream responseStream, Boolean navigationNextLink, Nullable`1 maxPageSize)
   at Finance.Web.Controllers.Organizations.DepartmentsController.Get() in /Users/techniq/Documents/Development/sbcs-chh/app-finance/Finance.Web/Controllers/Organizations/DepartmentsController.cs:line 38
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware:Error: An unhandled exception has occurred while executing the request.

I'm using ASP.NET Core / EFCore version 2.1.1 so not sure if there is a compatibility issue with it.

image

I was also getting the error reported in #16 again on startup but I've simplified my DbContext and will slowly add back classes until I can identity the root cause and submit a simplified case.

Lastly, could you push an updated NuGet package that includes the fix for composite keys (it can be done after we figure this issue out). Thanks!

Creating OData API via "OdataToEntity.EfCore.DynamicDataContext"

Hi!

I am trying to create OData API via referring "OdataToEntity.EfCore.DynamicDataContext" project in my main .net core web application project.

Below is the Startup.cs Configure function:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
            var optionsBuilder = new DbContextOptionsBuilder<DynamicDbContext>();
            optionsBuilder = optionsBuilder.UseSqlServer("Server=tcmy****dbsvr.database.windows.net,1433;Initial Catalog=my*****db;Persist Security Info=False;User ID=*****;Password=****;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
            using (ProviderSpecificSchema providerSchema = new SqlServerSchema(optionsBuilder.Options))
            {
                IEdmModel edmModel = DynamicMiddlewareHelper.CreateEdmModel(providerSchema, informationSchemaMapping: null);
                app.UseOdataToEntityMiddleware<OePageMiddleware>("/api", edmModel);
            }
        }

But on running the project, it throwing me error in below code:

namespace OdataToEntity.EfCore.DynamicDataContext.ModelBuilder
class DynamicModelBuilder
private EntityType CreateEntityType(***)
Line: entityTypeBuilder.Metadata.IsKeyless = true;

System.InvalidOperationException
HResult=0x80131509
Message=The entity type 'DynamicType1' cannot be marked as keyless because it contains a key.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Metadata.Internal.EntityType.HasNoKey(Nullable1 keyless, ConfigurationSource configurationSource) at Microsoft.EntityFrameworkCore.Metadata.Internal.EntityType.set_IsKeyless(Boolean value) at OdataToEntity.EfCore.DynamicDataContext.ModelBuilder.DynamicModelBuilder.CreateEntityType(ModelBuilder modelBuilder, String tableEdmName, Boolean isQueryType) in C:\OdataToEntity\source\OdataToEntity.EfCore.DynamicDataContext\ModelBuilder\DynamicModelBuilder.cs:line 65 at OdataToEntity.EfCore.DynamicDataContext.ModelBuilder.DynamicModelBuilder.Build(ModelBuilder modelBuilder) in C:\OdataToEntity\source\OdataToEntity.EfCore.DynamicDataContext\ModelBuilder\DynamicModelBuilder.cs:line 28 at OdataToEntity.EfCore.DynamicDataContext.DynamicDbContext.OnModelCreating(ModelBuilder modelBuilder) in C:\OdataToEntity\source\OdataToEntity.EfCore.DynamicDataContext\DynamicDbContext.cs:line 26 at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel() at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_Model()
at OdataToEntity.EfCore.DynamicDataContext.DynamicTypeDefinitionManager.Create(DynamicMetadataProvider metadataProvider) in C:\OdataToEntity\source\OdataToEntity.EfCore.DynamicDataContext\DynamicTypeDefinitionManager.cs:line 55
at OdataToEntity.EfCore.DynamicDataContext.DynamicMiddlewareHelper.CreateEdmModel(ProviderSpecificSchema providerSchema, InformationSchemaMapping informationSchemaMapping) in C:\OdataToEntity\source\OdataToEntity.EfCore.DynamicDataContext\DynamicMiddlewareHelper.cs:line 12
at WebApplication1.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env) in C:\OdataToEntity\sln\WebApplication1\Startup.cs:line 59
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.b__0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass13_0.b__2(IApplicationBuilder app)
at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter.<>c__DisplayClass2_0.b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.d__31.MoveNext()

Is there a way to ignore properties on objects (join tables)?

Hi,

I am receiving the following error, b/c a join table doesn't have an explicit key, but rather a composite key.

InvalidOperationException: The entity type 'RolePermissionSet' requires a primary key to be defined.

I know I could add an explicit key, and then change my composite key into a unique constraint, but I figured I'd check for other scenarios that I don't really want to be a part of my OData middleware.

Thanks!

Chris

Why OdataToEntity.EfCore depends on Standard 1.6 when Microsoft.EntityFrameworkCore by itself depends on Standard 1.3 ?

Why OdataToEntity.EfCore depends on Standard 1.6 Framework when EF Core by itself depends on Standard 1.3 ?
It is more logical to keep both on the same version (to be useful for all EF Core users).

Even when my VS 2017 Class Standard Library framework is setuped to target Standard 1.6 (1.6.1 to be precise ) I got an error on adding Nuget package to it:

Restoring packages for D:\cot\DashboardCode\AdminkaV1\DataAccessEfCore.OdataToEntity\AdminkaV1.DataAccessEfCore.OdataToEntity.csproj...
GET https://api.nuget.org/v3-flatcontainer/odatatoentity/index.json
GET https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='OdataToEntity'
OK https://api.nuget.org/v3-flatcontainer/odatatoentity/index.json 640ms
GET https://api.nuget.org/v3-flatcontainer/odatatoentity/1.0.0.1/odatatoentity.1.0.0.1.nupkg
OK https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='OdataToEntity' 765ms
OK https://api.nuget.org/v3-flatcontainer/odatatoentity/1.0.0.1/odatatoentity.1.0.0.1.nupkg 758ms
GET https://api.nuget.org/v3-flatcontainer/microsoft.odata.core/index.json
GET https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='Microsoft.OData.Core'
GET https://api.nuget.org/v3-flatcontainer/microsoft.odata.edm/index.json
GET https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='Microsoft.OData.Edm'
GET https://api.nuget.org/v3-flatcontainer/microsoft.spatial/index.json
GET https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='Microsoft.Spatial'
OK https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='Microsoft.OData.Core' 183ms
OK https://api.nuget.org/v3-flatcontainer/microsoft.odata.core/index.json 628ms
GET https://api.nuget.org/v3-flatcontainer/microsoft.odata.core/7.0.0/microsoft.odata.core.7.0.0.nupkg
OK https://api.nuget.org/v3-flatcontainer/microsoft.odata.edm/index.json 665ms
GET https://api.nuget.org/v3-flatcontainer/microsoft.odata.edm/7.0.0/microsoft.odata.edm.7.0.0.nupkg
OK https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='Microsoft.OData.Edm' 719ms
OK https://ci.appveyor.com/nuget/nugetizer3000/FindPackagesById()?id='Microsoft.Spatial' 900ms
OK https://api.nuget.org/v3-flatcontainer/microsoft.odata.core/7.0.0/microsoft.odata.core.7.0.0.nupkg 577ms
OK https://api.nuget.org/v3-flatcontainer/microsoft.spatial/index.json 1261ms
GET https://api.nuget.org/v3-flatcontainer/microsoft.spatial/7.0.0/microsoft.spatial.7.0.0.nupkg
OK https://api.nuget.org/v3-flatcontainer/microsoft.spatial/7.0.0/microsoft.spatial.7.0.0.nupkg 572ms
OK https://api.nuget.org/v3-flatcontainer/microsoft.odata.edm/7.0.0/microsoft.odata.edm.7.0.0.nupkg 1451ms
Installing Microsoft.OData.Edm 7.0.0.
Installing Microsoft.Spatial 7.0.0.
Installing Microsoft.OData.Core 7.0.0.
Installing OdataToEntity 1.0.0.1.
Package Microsoft.OData.Core 7.0.0 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package Microsoft.OData.Core 7.0.0 supports: portable-net45+win8+wpa81 (.NETPortable,Version=v0.0,Profile=Profile111)
Package Microsoft.Spatial 7.0.0 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package Microsoft.Spatial 7.0.0 supports: portable-net45+win8+wpa81 (.NETPortable,Version=v0.0,Profile=Profile111)
Package Microsoft.OData.Edm 7.0.0 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package Microsoft.OData.Edm 7.0.0 supports: portable-net45+win8+wpa81 (.NETPortable,Version=v0.0,Profile=Profile111)
One or more packages are incompatible with .NETStandard,Version=v1.6.

Package restore failed. Rolling back package changes for 'AdminkaV1.DataAccessEfCore.OdataToEntity'.
Time Elapsed: 00:00:05.2998164

As a result OdataToEntity.EfCore nuget package was not added to the VS2017 project.

Potential CWE-113 vulnerability in OdataToEntity.AspNetCore

So I've run a SAST scan with a certain tool against OdataToEntity source code and it uncovered the following issue: CWE-113 in OdataToEntity.AspNetCore MoveNext() method.

It probably originates in some foreach but I couldn't pinpoint the exact location. It can probably can also be fixed by a filter/middleware that would clean the inputs.

Thoughts?

Cheers,
Dmitry

Inheriting entity from abstract class without keys cause InvalidOperationException

Assume we have

public abstract class Animal
{
}

and

public class Cat:Animal
{
  [Key]
  public int Id {get; set;}
}

public class MyDbContext: DbContext {
  public DbSet<Cat> Cats {get; set; }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
     optionsBuilder.UseInMemoryDatabase("MyDbName");
  }
}

and then we try

var model = new OeEfCoreDataAdapter<MyDbContext>().BuildEdmModelFromEfCoreModel();

This will cause InvalidOperationException: Key property not matching

It is not clear how to parametrize DbContext creation with DbContextOptions<TContext> ?

What I see from this code fragment:

//Create adapter data access, where OrderEf6Context your DbContext
var dataAdapter = new OeEf6DataAdapter<OrderEf6Context>();
//Build OData Edm Model
EdmModel edmModel = dataAdapter.BuildEdmModelFromEf6Model();

is that OrderEf6Context will be instantiated somewhere in OdataToEntity internals. But what if I need to pass my DbContextOptions to it? (Case of not empty constructor)

Attempting to call stored procedure with parameters in DynamicDataContext

I am trying to invoke a stored with parameters using context generated from connection string.

ActionImport got mapped into EDMX.

....

However none of odata routing conventions pick up a call:

  1. .../api/dbo.usp_patch_something(id=1) //The request URI is not valid. The segment 'dbo.usp_patch_something' cannot include key predicates, however it may end with empty parenthesis.
  2. .../api/dbo.usp_patch_something(1) //The request URI is not valid. The segment 'dbo.usp_patch_something' cannot include key predicates, however it may end with empty parenthesis.
  3. .../api/dbo.usp_patch_something()?id=1 //Procedure or function 'usp_patch_something' expects parameter '@id', which was not supplied.

I see that odata uri parser is not even picking up a list of parameters in (3) case. Could you suggest a workaround or solution?

Error when getting Odata service doucment

Fist of all thank you for the great work ! I was able to download the source code, generating the Database in SQL server, runt the "\test\OdataToEntity.Test.DynamicDataContext.AspServer" project and succesfully getting the data, $metadatam, $json-schema, but I am getting the below error (from the console) when I just queried the base URI to get the service document, is there anything I could missed? (I think I only thing I changed in the database connection string in the appsetting.json file), thanks.

=========================================
Request starting HTTP/1.1 GET http://localhost:5000/api/
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HLVKIUD5GM79", Request id "0HLVKIUD5GM79:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: Invalid path first sgment is null
at OdataToEntity.OeEdmClrHelper.GetEdmModel(IEdmModel edmModel, ODataPath path) in C:\Users\User\source\repos\OdataToEntity-master\source\OdataToEntity\OeEdmClrHelper.cs:line 119
at OdataToEntity.OeParser.ExecuteQueryAsync(ODataUri odataUri, OeRequestHeaders headers, Stream responseStream, CancellationToken cancellationToken) in C:\Users\User\source\repos\OdataToEntity-master\source\OdataToEntity\OeParser.cs:line 143
at OdataToEntity.OeParser.ExecuteGetAsync(Uri requestUri, OeRequestHeaders headers, Stream responseStream, CancellationToken cancellationToken) in C:\Users\User\source\repos\OdataToEntity-master\source\OdataToEntity\OeParser.cs:line 139
at OdataToEntity.AspNetCore.OeMiddleware.InvokeApi(HttpContext httpContext) in C:\Users\User\source\repos\OdataToEntity-master\source\OdataToEntity.AspNetCore\OeMiddleware.cs:line 74
at OdataToEntity.AspNetCore.OeMiddleware.Invoke(HttpContext httpContext) in C:\Users\User\source\repos\OdataToEntity-master\source\OdataToEntity.AspNetCore\OeMiddleware.cs:line 64
at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 180363.7422ms 500

Support selecting subset of properties on an expanded property

Currently the following fails: Accounts(1)?$expand=Organization($select=Name) (as well as Accounts?$top=5&$expand=Organization($select=Name))

System.ArgumentException: Object of type 'System.Tuple`1[System.String]' cannot be converted to type 'Common.Models.Organizations.Taxonomy.Organization'.
   at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
   at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
   at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
   at OdataToEntity.AspNetCore.OeEntityAsyncEnumerator`1.<MoveNext>d__11.MoveNext()

How change index in scheme for table with several indexes?

i have a mssql table with two indexes id and guid

CREATE TABLE [dbo].[myproducts](
	[guid] [uniqueidentifier] NOT NULL,
	[id] [int] NOT NULL,
	[name] [varchar](50) NULL,
	[cost] [money] NULL,
	[start_time] [smalldatetime] NULL,
 CONSTRAINT [PK_myproducts] PRIMARY KEY CLUSTERED 
(
	[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [guid_construint] UNIQUE NONCLUSTERED 
(
	[guid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

and OdataToEntity.Test.DynamicDataContext.AspServer.exe generates the scheme:

...
<Key>
  <PropertyRef Name="id"/>
</Key>
<Property Name="id" Type="Edm.Int32" Nullable="false"/>
<Property Name="cost" Type="Edm.Decimal"/>
<Property Name="guid" Type="Edm.Guid" Nullable="false"/>
<Property Name="name" Type="Edm.String"/>
<Property Name="start_time" Type="Edm.DateTimeOffset"/>

...
</edmx:Edmx>

How do i determine the index 'guid' instead index 'id' by dataschema? Is it possible?
Or how do i patch code to add such functionality yourself?

Thank you.

"Not found table for clr type" when make OData POST request

Description

I am having trouble to insert an object using a POST request. DictAreas entity are associated with a one-to-many DictCities entity. When I submit a request, it sends me a response with this exception and a 500 status code, but when I check the DB, I see that the value for DictAreas has been added. Is it possible to somehow disable the hierarchical data insertion mode or solve this problem differently?

I am using models created by Linq2Db with T4 templates (MS SQL Server). We are also trying to make one controller for all entities, GET and PATCH requests works fine, but there are problems with this exception.

Exception

System.InvalidOperationException: Not found table for clr type ExampleProject.DB.DictCity
   at OdataToEntity.Linq2Db.OeLinq2DbDataContext.UpdateIdentities(OeLinq2DbTable table, Int32 lastIndex)
   at OdataToEntity.Linq2Db.OeLinq2DbDataContext.SaveChanges(DataConnection dataConnection)
   at OdataToEntity.Linq2Db.OeLinq2DbDataAdapter`1.SaveChangesAsync(Object dataContext, CancellationToken cancellationToken)
   at OdataToEntity.AspNetCore.OeBatchFilterAttributeAttribute.OnActionExecuted(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---

Code

Models

using System;
using System.Collections.Generic;

using LinqToDB;
using LinqToDB.Data;
using LinqToDB.Mapping;

using OdataToEntity.Linq2Db;


namespace ExampleProject.DB
{
    public partial class ExampleDB : DataConnection, IOeLinq2DbDataContext
    {
        OeLinq2DbDataContext IOeLinq2DbDataContext.DataContext
        {
            get;
            set;
        }
        public ITable<DictArea> DictAreas { get { return this.GetTable<DictArea>(); } }
        public ITable<DictCity> DictCities { get { return this.GetTable<DictCity>(); } }
        
    }

    [Table(Schema = "dbo", Name = "DictArea")]
    public partial class DictArea
    {
        [PrimaryKey, Identity] public int AreaID { get; set; } // int
        [Column, NotNull] public string Name { get; set; } // nvarchar(50)
        [Column, Nullable] public string Code { get; set; } // nvarchar(10)
 
        [Association(ThisKey = "AreaID", OtherKey = "AreaID", CanBeNull = true, Relationship = LinqToDB.Mapping.Relationship.OneToMany, IsBackReference = true)]
        public IEnumerable<DictCity> DictCities { get; set; }

    }


    [Table(Schema = "dbo", Name = "DictCities")]
    public partial class DictCity
    {
        [PrimaryKey, Identity] public int CitiesID { get; set; } // int
        [Column, NotNull] public string Name { get; set; } // nvarchar(50)
        [Column, NotNull] public int AreaID { get; set; } // int
        [Column, Nullable] public bool? IsVillage { get; set; } // bit
        [Column, NotNull] public bool IsActive { get; set; } // bit
        [Column, Nullable] public string Code { get; set; } // nvarchar(10)
        [Column, NotNull] public bool IsDefault { get; set; } // bit
        [Column, Nullable] public int? LocationID { get; set; } // int

    
        [Association(ThisKey = "AreaID", OtherKey = "AreaID", CanBeNull = false, Relationship = LinqToDB.Mapping.Relationship.ManyToOne, KeyName = "FK_DictCities_DictArea", BackReferenceName = "DictCities")]
        public DictArea Area { get; set; }

    }
}

Controller

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;

using OdataToEntity.Query;
using OdataToEntity.AspNetCore;
using ExampleProject.DB;

namespace ExampleProject.Controllers
{
    [Route("api/v1/[controller]")]
    public sealed class DictAreasController
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public DictAreasController(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        [HttpGet]
        public async Task Get()
        {
            var edmModel = (IEdmModel)_httpContextAccessor.HttpContext.RequestServices.GetService(typeof(IEdmModel));
            OeModelBoundProvider modelBoundProvider = OeAspHelper.CreateModelBoundProvider(edmModel, 50, false);
            await OeAspQueryParser.Get(_httpContextAccessor.HttpContext, modelBoundProvider).ConfigureAwait(false);
        }

        [HttpPost]
        public void Post(OeDataContext dataContext, DictArea obj)
        {
            dataContext.Update(obj);
        }
        
        [HttpPatch]
        public void Patch(OeDataContext dataContext, IDictionary<String, Object> obj)
        {
            dataContext.Update(obj);
        }
    }
}

`$select` on nullable `$expand` property throws ODataException

When I execute the following query (where an Account has a nullable/optional DepartmentId / Department:

  • http://localhost:5000/odata/Accounts?$top=50&$expand=Department($select=Name)

I get the following exception.

Microsoft.OData.ODataException: The property 'Name[Nullable=False]' of type 'Edm.String' has a null value, which is not allowed.
   at Microsoft.OData.WriterValidationUtils.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, IEdmModel model)
   at Microsoft.OData.WriterValidator.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, Boolean isTopLevel, IEdmModel model)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteNullProperty(ODataProperty property)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteProperties(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
   at Microsoft.OData.JsonLight.ODataJsonLightWriter.StartResource(ODataResource resource)
   at Microsoft.OData.ODataWriterCore.InterceptException(Action action)
   at OdataToEntity.AspNetCore.ODataResult`1.WriteEntry(ODataWriter writer, Object entity, EntityPropertiesInfo& entityPropertiesInfo)
   at OdataToEntity.AspNetCore.ODataResult`1.WriteNavigationProperty(ODataWriter writer, Object value, PropertyInfo navigationProperty)
   at OdataToEntity.AspNetCore.ODataResult`1.WriteEntry(ODataWriter writer, Object entity, EntityPropertiesInfo& entityPropertiesInfo)
   at OdataToEntity.AspNetCore.ODataResult`1.SerializeAsync(ODataWriter writer)
   at OdataToEntity.AspNetCore.ODataResult`1.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
      Request finished in 259.252ms 200 application/json;odata.metadata=minimal;odata.streaming=true;charset=utf-8

If I adjust the query with either of the following, the query executes successfully

  • Do not apply the $select on the $expand
    • http://localhost:5000/odata/Accounts?$top=50&$expand=Department
  • Filter null Department entities
    • http://localhost:5000/odata/Accounts?$top=50&$expand=Department($select=Name)&$filter=DepartmentId ne null

This led me to believe the combination of $expanding a nullable property and $select was the culprit.

I was able to setup a local database and successfully run your entire test suite, and I attempted to write a failing test to confirm the issue using the existing models

[Theory]
[InlineData(0, false)]
[InlineData(1, false)]
[InlineData(0, true)]
[InlineData(1, true)]
public async Task ExpandNullableNestedSelect(int pageSize, bool navigationNextLink)
{
    var parameters = new QueryParameters<Order>()
    {
        RequestUri = "Orders?$expand=AltCustomers($select=Name)&$orderby=Id",
        Expression = t => t.Include(o => o.AltCustomer).OrderBy(o => o.Id),
        NavigationNextLink = navigationNextLink,
        PageSize = pageSize
    };
    await Fixture.Execute(parameters).ConfigureAwait(false);
}

but I am unable to execute the test due to a cleanup failure I can't figure out.

image

I think there is a similar issue with ICollection properties, but I haven't spent as much time trying to dig in and confirm yet.

Why OdataToEntity was migrated to NET5?

This is only the question from developer to developer.
What was the reason to migrate OdataToEntity from netstandard2.1 to net5.0 ?
I mean do you obtain any advantages in net5.0 that was not available on netstandard2.1 ?

Explain WcfService sample

Please write several words about WcfService

It so differ from "standard" EntityFrameworkDataService that I can't get it should work.

What is the reason for this service:

[ServiceContract]
public interface IOrderDb
{
    [OperationContract]
    void Init();
    [OperationContract]
    void Reset();
}

Expand Bug ?

Hi,

I have this code:

using GomoiuWeb.Base;
using GomoiuWeb.Shared.Data.DB;
using OdataToEntity;
using OdataToEntity.EfCore;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ODat
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Global.DefaultConnectionString = "...";
            //Create adapter data access, where OrderContext your DbContext
            var dataAdapter = new OeEfCoreDataAdapter<AppDB>();
            //Create query parser
            var parser = new OeParser(new Uri("http://dummy"), dataAdapter.BuildEdmModelFromEfCoreModel());
            //Query
            var uri = new Uri("http://dummy/Doctors?$select=Name&$expand=ClientAppointments");
            //The result of the query
            var response = new MemoryStream();
            //Execute query
            await parser.ExecuteGetAsync(uri, OeRequestHeaders.JsonDefault, response, CancellationToken.None);

            Console.WriteLine(Encoding.ASCII.GetString(response.ToArray()));

            Console.ReadKey();
        }
    }
}

And I get an exception with the $expand:

"System.InvalidOperationException: Nullable object must have a value.\r\n   at lambda_method(Closure , ClientAppointments )\r\n   at System.Linq.Internal.Lookup`2.CreateForJoinAsync(IAsyncEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer, CancellationToken cancellationToken)\r\n   at System.Linq.AsyncEnumerable.GroupJoinAsyncEnumerable`4.GroupJoinAsyncEnumerator.MoveNext(CancellationToken cancellationToken)\r\n   at System.Linq.AsyncEnumerable.SelectManyAsyncIterator`3.MoveNextCore(CancellationToken cancellationToken)\r\n   at System.Linq.AsyncEnumerable.AsyncIterator`1.MoveNext(CancellationToken cancellationToken)\r\n   at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator`2.MoveNextCore(CancellationToken cancellationToken)\r\n   at System.Linq.AsyncEnumerable.AsyncIterator`1.MoveNext(CancellationToken cancellationToken)\r\n   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken can
cellationToken)\r\n   at OdataToEntity.Db.OeDbEnumerator.MoveNextAsync() in D:\\programming\\OdataToEntity\\source\\OdataToEntity\\Db\\OeDbEnumerator.cs:line 134\r\n   at OdataToEntity.Writers.OeGetWriter.GetWriter.SerializeAsync(OeEntryFactory entryFactory, OeAsyncEnumerator asyncEnumerator, OeQueryContext queryContext) in D:\\programming\\OdataToEntity\\source\\OdataToEntity\\Writers\\OeGetWriter.cs:line 129\r\n   at OdataToEntity.Writers.OeGetWriter.SerializeAsync(OeQueryContext queryContext, OeAsyncEnumerator asyncEnumerator, String contentType, Stream stream) in D:\\programming\\OdataToEntity\\source\\OdataToEntity\\Writers\\OeGetWriter.cs:line 246\r\n   at OdataToEntity.OeGetParser.ExecuteAsync(ODataUri odataUri, OeRequestHeaders headers, Stream stream, CancellationToken cancellationToken) in D:\\programming\\OdataToEntity\\source\\OdataToEntity\\Parsers\\OeGetParser.cs:line 120\r\n   at OdataToEntity.OeParser.ExecuteQueryAsync(ODataUri odataUri, OeRequestHeaders headers, Stream responseStream, Cancellat
ionToken cancellationToken) in D:\\programming\\OdataToEntity\\source\\OdataToEntity\\Parsers\\OeParser.cs:line 113\r\n   at OdataToEntity.OeParser.ExecuteGetAsync(Uri requestUri, OeRequestHeaders headers, Stream responseStream, CancellationToken cancellationToken) in D:\\programming\\OdataToEntity\\source\\OdataToEntity\\Parsers\\OeParser.cs:line 108\r\n   at ODat.Program.Main(String[] args) in C:\\Work\\gomoiu\\ODat\\Program.cs:line 27\r\n   at ODat.Program.<Main>(String[] args)"

Is this a bug or I should configure something to get it to work?

Thanks.

Support accessing expression builder from

Creating this issue from our discussion in #28 (comment)


It would be nice if you could get an expression tree from the parser, and then apply it later. Similar to how the tests are structured...

RequestUri = "Orders?$apply=filter(Status eq OdataToEntity.Test.Model.OrderStatus'Unknown')/groupby((Name), aggregate(Id with countdistinct as cnt))",
Expression = t => t.Where(o => o.Status == OrderStatus.Unknown).GroupBy(o => o.Name).Select(g => new { Name = g.Key, cnt = g.Count() }),

You could pass in a the OData query string and get the expression back, and then apply the expression later (I guess via an IQueryableProvider). Maybe something like

var parser = new OeParse(baseUri, edmModel);
var expression = parser.GetExpression<Order>("$apply=filter(Status eq OdataToEntity.Test.Model.OrderStatus'Unknown')/groupby((Name), aggregate(Id with countdistinct as cnt))");
// t => t.Where(o => o.Status == OrderStatus.Unknown).GroupBy(o => o.Name).Select(g => new { Name = g.Key, cnt = g.Count() }), 
var results = DbContext.Orders.AsQueryable().Provider.Execute(expression)

or using the OeAspQueryParser

var parser = new OeAspQueryParser(_httpContextAccessor.HttpContext);
var expression = parser.GetExpression<Order>();
// use `expression` to modify query, use as subselect, etc
var query = DbContext.Orders.AsQueryable().Provider.Execute(expression);
IAsyncEnumerable<Order> orders = parser.ExecuteReader<Order>(query);
return parser.OData(orders);

A lot of times I just want the OData query string parsed as an expression tree, and then let me worry about the execution (or further refinement of the query).

I'm thinking this will help a lot with my use cases to support. For example, porting this OData/WebApi function...

[HttpGet]
public IActionResult WithRoles(ODataQueryOptions<User> queryOptions, [FromODataUri] IEnumerable<string> roleCodes)
{
    var userIdsWithRoleCodes = from ur in DbContext.UserRoles
                                where roleCodes.Contains(ur.Role.Code)
                                select ur.UserId;

    var query = from u in GetQuery()
                where userIdsWithRoleCodes.Contains(u.Id)
                select u;

    var result = queryOptions.ApplyTo(query);

    return Ok(result);
}

...to use OdataToEntity might be

[HttpGet("WithRoles(roleCodes={roleCodes})]
public IActionResult WithRoles(IEnumerable<string> roleCodes)
{
    var userIdsWithRoleCodes = from ur in DbContext.UserRoles
                                where roleCodes.Contains(ur.Role.Code)
                                select ur.UserId;

    var query = from u in GetQuery()
                where userIdsWithRoleCodes.Contains(u.Id)
                select u;

  var parser = new OeAspQueryParser(_httpContextAccessor.HttpContext);
  var expression = parser.GetExpression<User>();
  var query = query.Provider.Execute(expression);
  IAsyncEnumerable<User> users = parser.ExecuteReader<User>(query);
  return parser.OData(users);
}

along with parser.GetExpression<User>(), it would also be useful to get the individual expressions for $filter, $expand, etc

var url = "...";
var filterExpression = parser.GetFilterExpression<Order>(url);
var expandExpression = parser.GetExpandExpression<Order>(url);
var applyExpression = parser.GetApplyExpression<Order>(url);

Support OData Functions/Actions

Currently with OData/WebApi I am able to register an OData function in the EdmModel...

builder.EntitySet<User>("Users");
var userWithRoles = builder
    .EntityType<User>()
    .Collection
    .Function("WithRoles")
    .ReturnsFromEntitySet<User>("Users");
userWithRoles.CollectionParameter<string>("roleCodes");

..and then expose it in the Controller

[HttpGet]
public IActionResult WithRoles(ODataQueryOptions<User> queryOptions, [FromODataUri] IEnumerable<string> roleCodes)
{
    var userIdsWithRoleCodes = from ur in DbContext.UserRoles
                                where roleCodes.Contains(ur.Role.Code)
                                select ur.UserId;

    var query = from u in GetQuery()
                where userIdsWithRoleCodes.Contains(u.Id)
                select u;

    var result = queryOptions.ApplyTo(query);

    return Ok(result);
}

I can then call it using

http://localhost:5000/odata/security/Users/WithRoles(roleCodes=['Foo','Bar','Baz'])?$select=Id,FirstName,MiddleName,LastName&$top=10&$count=true&$orderby=LastName, FirstName

where you pass the function parameters via (roleCodes=['Foo','Bar','Baz']) as well as process the OData query string $select, $top, etc.

I attempted something similar using pure ASP.NET Core routing and ODataToEntity...

[HttpGet("WithRoles(roleCodes=[[{roleCodes}]])")]
public IActionResult WithRoles(string roleCodes = "")
{
    // TODO: Not properly parsing string (ex. `"['Foo','Bar','Baz']"` to IList<string> (or IEnumerable<string>, string[], etc).  Preferable to take in `IEnumerable<string> roleCodes` via param binding
    // var roleCodesAsArray = new StringValues(roleCodes.Split(',')).ToList();
    var roleCodesAsArray = new[] { "ProductivityViewer", "ProductivityReporter", "ProductivityAdministrator" };

    var userIdsWithRoleCodes = from ur in DbContext.UserRoles
                                where roleCodesAsArray.Contains(ur.Role.Code)
                                select ur.UserId;

    var query = from u in GetQuery()
                where userIdsWithRoleCodes.Contains(u.Id)
                select u;

    var parser = new OeAspQueryParser(HttpContext);
    var result = parser.ExecuteReader<User>(query);
    return parser.OData(result);
}

...but am running into the following issues

  • I'm struggling to take in the roleCodes param as an array (ex. WithRoles(roleCodes=['Foo','Bar','Baz']))
  • If I hard code the params to temporarily work around the issue, I run into an Exception from parser.ExecuteReader<User>(query) call
       An unhandled exception has occurred while executing the request.
Microsoft.OData.ODataException: The operation import overloads matching 'WithRoles' are invalid. This is most likely an error in the IEdmModel. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.OData.Metadata.EdmLibraryExtensions.RemoveActionImports(IEnumerable`1 source, IList`1& actionImportItems)
   at Microsoft.OData.UriParser.FunctionOverloadResolver.ResolveOperationImportFromList(String identifier, IList`1 parameterNames, IEdmModel model, IEdmOperationImport& matchingOperationImport, ODataUriResolver resolver)
   --- End of inner exception stack trace ---
   at Microsoft.OData.UriParser.FunctionOverloadResolver.ResolveOperationImportFromList(String identifier, IList`1 parameterNames, IEdmModel model, IEdmOperationImport& matchingOperationImport, ODataUriResolver resolver)
   at Microsoft.OData.UriParser.ODataPathParser.TryBindingParametersAndMatchingOperationImport(String identifier, String parenthesisExpression, ODataUriParserConfiguration configuration, ICollection`1& boundParameters, IEdmOperationImport& matchingFunctionImport)
   at Microsoft.OData.UriParser.ODataPathParser.TryCreateSegmentForOperationImport(String identifier, String parenthesisExpression)
   at Microsoft.OData.UriParser.ODataPathParser.CreateFirstSegment(String segmentText)
   at Microsoft.OData.UriParser.ODataPathParser.ParsePath(ICollection`1 segments)
   at Microsoft.OData.UriParser.ODataPathFactory.BindPath(ICollection`1 segments, ODataUriParserConfiguration configuration)
   at Microsoft.OData.UriParser.ODataUriParser.Initialize()
   at Microsoft.OData.UriParser.ODataUriParser.ParsePath()
   at OdataToEntity.OeParser.ParseUri(IEdmModel model, Uri serviceRoot, Uri uri) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/OeParser.cs:line 171
   at OdataToEntity.AspNetCore.OeAspQueryParser.GetAsyncEnumerator(IQueryable source, Boolean navigationNextLink, Nullable`1 maxPageSize) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity.AspNetCore/OeAspQueryParser.cs:line 90
   at OdataToEntity.AspNetCore.OeAspQueryParser.ExecuteReader[T](IQueryable source, Boolean navigationNextLink, Nullable`1 maxPageSize) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity.AspNetCore/OeAspQueryParser.cs:line 65
   at Finance.Web.Controllers.Security.UsersController.WithRoles(String roleCodes) in /Users/techniq/Documents/Development/sbcs-chh/app-finance/Finance.Web/Controllers/Security/UsersController.cs:line 133
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[]parameters)
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope&scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)

I think to support this use case, the following needs to be supported:

[HttpGet("WithRoles(roleCodes={roleCodes})")]
public IActionResult WithRoles([OeArrayParameter]IEnumerable<string> roleCodes)
  • See this article and related source for potential approach
    • Note: this implementation does not wrap individual strings in quotes, put them in an array, or wrap all of this in params (for example they use /products?sizes=s,m,l and not /products(sizes=['s','m','l'])
  • Resolve issue when calling parser.ExecuteReader<User>(query)
    • Maybe required to register function in EdmModel like in OData/WebApi (although this isn't the case with stored procedures in ODataToEntity, so I dunno).

OeMiddleware - does support POST / PATCH / DELETE ?

I have tried to create a new entity with Dynamic Data from connection string -
app.UseOdataToEntityMiddleware("/odata", edmModel);

and shows data as it was a GET , not a POST .

Apparently, from the code in
public class OeMiddleware public async Task Invoke(HttpContext httpContext) private async Task InvokeApi(HttpContext httpContext)

it calls always GET , not POST.

There is a
parser.ExecutePostAsync
but it is not called. And , when I try to call POST , verifies if it is batch or OperationImportSegment , then it call anyway the ExecuteQueryAsync

OeMiddleware - support POST / PATCH / DELETE ?

Better error message when System.NullReferenceException / IsNotMapped is thrown

Currently it is difficult to track down a System.NullReferenceException exception due to aIsNotMapped issue...

image

Stacktrace:

   at OdataToEntity.EfCore.OeEfCoreEdmModelMetadataProvider.IsNotMapped(PropertyInfo propertyInfo) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity.EfCore/OeEfCoreEdmModelMetadataProvider.cs:line 114
   at OdataToEntity.ModelBuilder.EntityTypeInfo.BuildProperties(Dictionary`2 entityTypes, Dictionary`2 enumTypes, Dictionary`2 complexTypes) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/EntityTypeInfo.cs:line 124
   at OdataToEntity.ModelBuilder.OeEdmModelBuilder.BuildEdmModel() in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity/ModelBuilder/OeEdmModelBuilder.cs:line 67
   at OdataToEntity.EfCore.OeEfCoreDataAdapterExtension.BuildEdmModelFromEfCoreModel(OeDataAdapter dataAdapter) in /Users/techniq/Documents/Development/open-source/OdataToEntity/source/OdataToEntity.EfCore/OeEfCoreDataAdapterExtension.cs:line 18
   at Finance.Web.Startup.ConfigureServices(IServiceCollection services) in /Users/techniq/Documents/Development/sbcs-chh/app-finance/Finance.Web/Startup.cs:line 81
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at Finance.Web.Program.Main(String[] args) in /Users/techniq/Documents/Development/sbcs-chh/app-finance/Finance.Web/Program.cs:line 10

I believe the issue is due to the model building being wrapped in a try and swallowing/not re-throwing the exception.

try
{
var metadataProvider = new OeEfCoreEdmModelMetadataProvider(context.Model);
var modelBuilder = new OeEdmModelBuilder(metadataProvider);
modelBuilder.AddEntitySetRange(dataAdapter.EntitySetAdapters.GetEntitySetNamesEntityTypes());
OeDataAdapterExtension.BuildOperations(dataAdapter, modelBuilder);
return modelBuilder.BuildEdmModel();
}
finally
{
dataAdapter.CloseDataContext(context);
}

It would be awesome if when a IsNotMapped is raised if more details were added, primarily what entity is not mapped (and maybe which entity is expecting it to be). Thanks.

Support for inline counts (?$count=true)

The $count system query option ignores any $top, $skip, or $expand query options, and returns the total count of results across all pages including only those results matching any specified $filter and $search

Will include "@odata.count": {NUMBER}, in the response with the total number of entries across all pages (taking into affect the $filter) which is really useful for paginated interfaces (data tables, etc). Implementations I've seen have been to issue a second select count(*) ... query.

Specification: 11.2.5.5 System Query Option $count

Example: https://services.odata.org/Experimental/Northwind/Northwind.svc/Customers?$count=true&$top=5

OdataToEntity.EfCore.DynamicDataContext, DynamicTypeDefinitionManager and LRU caching

Hi,

I'm here to pitch an idea for an improvement.
So, we're using DynamicDataContext and we quickly ran into 'DynamicDbContext out of range' exception.

We kind of resolved this problem by generating DynamicDbContexts and DynamicTypes at runtime in TypeResolve AppDomain event.

The next improvement would be to keep generated types in LRU cache, to keep memory usage constant.

Thoughts?

Cheers,
Dmitry

Cache does not consider function within $filter

If caching is left enabled, calling a query that uses contains, startswith, etc will return the same result set of the initial call.

For example, calling:

http://localhost:5000/odata/Departments?$top=10&$filter=contains(Name,'Admin')

and then calling

http://localhost:5000/odata/Departments?$top=10&$filter=contains(Name,'Information')

will return the same result as the first. If you stop and restart the server, the second result set is then returned (but calling the first will now return the second's, etc)

As a workaround, I've disabled the cache in the data adapter

public class CommonDbDataAdapter : OeEfCoreDataAdapter<CommonDbContext>
    {
        public CommonDbDataAdapter(DbContextOptions options) : base(options, new OeQueryCache(false)) {}
    }
}

Update NuGet packages

I haven't had a chance to give this library a spin just yet, but initial impressions from looking at the code and tests looks very promising.

It appears this library supports the new .NET Core 2.1 that Microsoft's library currently does not, along with $apply support (which is also currently unsupported). I've always been looking for something a little lighter weight that Microsoft.AspNetCore.OData and this might just be it.

A few questions:

  • I appears the NuGet packages haven't been updated in 2 months. Could you push a new release soon? It looks like support for $apply and query cache would be included in the next release.
  • Is it possible to apply restrictions to the query at runtime with this library (ex. do not allow a user to $expand deeply unless they have permission)? Something like this.
  • Looking at SelectTest it looks like most of my use cases are already supported (nested select/expand, various operators like contains, startswith, any, etc). Do you know of any areas that currently lack support?

Btw I've created a few javascript libraries around OData, including odata-query and react-odata that you may be interested in. odata-query provides a simple object-based syntax similar to MongoDB and js-data's query structure (making it easier to build queries as building the OData query string dynamically with it's nested structure can be challenging otherwise).

Support Model Bound Attributes to add select/expand limits, etc

Currently using OData/WebApi, I use the ODataConventionModelBuilder to build the EdmModel, but this also gives me access to the entity types before building the EdmModel, which allows me to add restrictions (we keep our model constrained as much as possible, especially with regards to expanding through properties.

For example:

var builder = new ODataConventionModelBuilder(serviceProvider);

builder
    .EntitySet<Department>("Departments")
    .EntityType
    .Expand(1, x => x.Organization)
    .Expand(3, x => x.CategoryMembership)
    .IgnoreExcept(c => new
    {
        c.Id,
        c.Code,
        c.Name,
        c.OrganizationId,
        c.Organization,
        c.Roles,
        c.CategoryMembership
    });

builder
  .EntitySet<Employee>("Employees")
  .EntityType
  .Expand(2, x => x.Department)
  .Expand(1, x => x.Position)
  .IgnoreExcept(c => new
  {
      c.Id,
      c.FirstName,
      c.MiddleName,
      c.LastName,
      c.DepartmentId,
      c.Department,
      c.PositionId,
      c.Position
  });

// ...

return builder.GetEdmModel();

After some reading, I think this could be best accomplished in ODataToEntity by supporting the Model Bound Attributes. For example (pulled from referenced page):

[Expand("Orders", "Friend", "CountableOrders", MaxDepth = 10)]
[Expand("AutoExpandOrder", ExpandType = SelectExpandType.Automatic, MaxDepth = 8)]
[Page(MaxTop = 5, PageSize = 1)]
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    [Expand(ExpandType = SelectExpandType.Disabled)]
    public Order Order { get; set; }
    public Order AutoExpandOrder { get; set; }
    public Address Address { get; set; }
    [Expand("Customers", MaxDepth = 2)]
    [Count(Disabled = true)]
    [Page(MaxTop = 2, PageSize = 1)]
    public List<Order> Orders { get; set; }
    public List<Order> CountableOrders { get; set; }
    public List<Order> NoExpandOrders { get; set; }
    public List<Address> Addresses { get; set; }
    [Expand(MaxDepth = 2)]
    public Customer Friend { get; set; }
}

which would match this Fluent API approach (just here for reference)

var builder = new ODataConventionModelBuilder();
builder.EntitySet<Customer>("Customers")
    .EntityType.Expand(10, "Orders", "Friend", "CountableOrders")
    .Expand(8, SelectExpandType.Automatic, "AutoExpandOrder")
    .Page(5, 2);
builder.EntityType<Customer>()
    .HasMany(p => p.Orders)
    .Expand(2, "Customers")
    .Page(2, 1)
    .Count(QueryOptionSetting.Disabled);
builder.EntityType<Customer>()
    .HasMany(p => p.CountableOrders)
    .Count();
builder.EntityType<Customer>()
    .HasOptional(p => p.Order)
    .Expand(SelectExpandType.Disabled);

Missing foreign key property throws NullReferenceException in FKeyInfo.GetEdmMultiplicity

If a model is missing a foreign key property, you will have a NullReferenceException thrown in FKeyInfo.GetEdmMultiplicity. It would be nice if this was handled more gracefully / better logging to point at the issue.

public class MyFinanceDbContext : DbContext
    {
        public DbSet<Car> Cars { get; set; }
        public DbSet<State> States { get; set; }

        public MyFinanceDbContext(DbContextOptions options) : base(options)
        {
        }
    }

    public class Car
    {
        public int Id { get; set; }

        // Missing: `public int StateId { get; set; }`
        public virtual State State { get; set; }
    }

    public class State
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Car> Cars { get; set; }
    }

I ultimately threw in some debug logging which allowed me to determine a single null entry was in the dependentStructuralProperties array

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.