Open invzz opened 2 years ago
@invzz thanks for reporting. We are working on a better ef core integration. At the moment we do not know if EF Core (and what Features of EF Core) is used.
@PascalSenn thanks for your reply, I'm looking forward to it. My workaround is to work with flattened tables without tablesplitting and use automapper later to get the right entity subdivision.
ah you use Automapper? because then this is probably the issue
I'm not using Automapper in graphQL, although I have it registered in services and I use it for other endpoints in a controller which expose some other pieces of functionality (basic crud db interrogations)
I also tried to exclude Automapper ( deleting the .AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies())
line) from the loaded services and excluding it from my controller, but I had the same results
builder.Services
.Configure<As400Options>(builder.Configuration.GetSection(As400Options.OptionsKey))
.AddPooledDbContextFactory<AS400Context>(ob =>
{
ob.UseDb2(_connectionString, x => x.SetServerInfo(IBMDBServerType.AS400));
ob.LogTo(Console.WriteLine);
})
.AddDbContext<AS400Context>( ob => {
ob.UseDb2(_connectionString, x => x.SetServerInfo(IBMDBServerType.AS400));
ob.LogTo(Console.WriteLine);
})
.AddTransient<IReadOnlyRepository<ViaggioGviat>, ReadOnlyRepository<AS400Context, ViaggioGviat>>()
.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies())
.ConfigureApi()
.AddHttpClient()
.AddGraphQLServer()
.AddProjections()
.AddSorting()
.AddFiltering()
.RegisterDbContext<AS400Context>()
.AddQueryType<PaxQuery>();
Another "weird" and related thing is happening :
I changed my context and started using flattened tables which models mirrors the tables in the db schema:
using ConfigurationOptions.Options;
using EF400_Relational.Models.Entities;
using IBM.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace EF400_Relational
{
public class AS400Context : DbContext
{ public AS400Context(DbContextOptions<AS400Context> options) : base(options) { }
public virtual DbSet<TestataGbigt> Testate { get; set; } = null!;
public virtual DbSet<ViaggioGviat> Viaggi { get; set; } = null!;
public virtual DbSet<CartaImbarcoGcimb> CarteImbarco { get; set; } = null!;
public virtual DbSet<NaveTanav> Navi { get; set; } = null!;
public virtual DbSet<PortoAnpor> Porti { get; set; } = null!;
public virtual DbSet<MultitrattaGmvia> Multitratte { get; set; } = null!;
public virtual DbSet<CartaImbarcoOld> CarteImbarcoGassv { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ViaggioGviat.BuildModel(modelBuilder, e =>e.ToTable("GVIAT00F", "GNV2008"));
TestataGbigt.BuildModel(modelBuilder, e =>e.ToTable("GBIGT00F", "GNV2008"));
CartaImbarcoGcimb.BuildModel(modelBuilder, e =>e.ToTable("GCIMB00F", "GNV2008"));
NaveTanav.BuildModel(modelBuilder, e =>e.ToTable("TANAV00F", "GNV2008"));
PortoAnpor.BuildModel(modelBuilder, e =>e.ToTable("ANPOR00F", "GNV2008"));
MultitrattaGmvia.BuildModel(modelBuilder, e =>e.ToTable("GMVIA00F", "GNVUTL"));
CartaImbarcoOld.BuildModel(modelBuilder, e =>e.ToTable("GASSV00F", "GNVPROD"));
CategoriaPaxAncat.BuildModel(modelBuilder, e =>e.ToTable("ANCAT00F", "GNV2008"));
// _ = modelBuilder.Entity<NaveTanav>(e => { });
_ = modelBuilder.Entity<ViaggioGviat>(e =>
{
// relazione con testata
_ = e.HasMany(gviat => gviat.Testate).WithOne(x => x.Viaggio).HasForeignKey(t => new { t.IdSocieta, t.IdViaggio });
// testate di ritorno
_ = e.HasMany(gviat => gviat.TestateRitorno).WithOne(x => x.ViaggioRitorno).HasForeignKey(t => new { t.IdSocieta, t.IdViaggioRitorno }).IsRequired();
// relazione con Nave
_ = e.HasOne(x => x.Nave).WithMany().HasForeignKey(x => new { x.IdSocieta, x.IdNave }).IsRequired(true);
// Porto Arrivo
_ = e.HasOne(x => x.PortoArrivo).WithMany().HasForeignKey(x => x.IdPortoArrivo).IsRequired(true);
// PortoPartenza
_ = e.HasOne(x => x.PortoPartenza).WithMany().HasForeignKey(x => x.IdPortoPartenza).IsRequired(true);
// PortoPrimoScalo
_ = e.HasOne(x => x.PortoPrimoScalo).WithMany().HasForeignKey(x => x.IdPortoPrimoScalo).IsRequired(false);
});
_ = modelBuilder.Entity<MultitrattaGmvia>(e =>
{
//viaggio
_ = e.HasOne(p => p.ViaggioMultitratta).WithOne(p => p.Multitratta).HasForeignKey<MultitrattaGmvia>(p => new { p.IdViaggioMultitratta }).HasPrincipalKey<ViaggioGviat>(x => x.IdViaggio).IsRequired(false);
//viaggo
_ = e.HasOne(p => p.ViaggioSingolaTratta).WithOne(p => p.SingolaTratta).HasForeignKey<MultitrattaGmvia>(p => new { p.IdViaggioSingolaTratta }).HasPrincipalKey<ViaggioGviat>(x => x.IdViaggio).IsRequired(false);
});
_ = modelBuilder.Entity<TestataGbigt>(e =>
{
// relazione con logicalcartaimbarco
_ = e.HasMany(big => big.CarteImbarcoGcimb).WithOne().HasForeignKey(c => new { c.Societa, c.PrefissoBiglietto, c.IdBiglietto, });
// relazione con Nave
_ = e.HasOne(x => x.Nave).WithOne(x => x.Testata).HasForeignKey<TestataGbigt>(x => new { x.IdSocieta, x.IdNave });
// Porto Arrivo
_ = e.HasOne(x => x.PortoArrivo).WithMany(p => p.TestateArrivo).HasForeignKey(x => x.IdPortoArrivo).IsRequired(false);
// PortoPartenza
_ = e.HasOne(x => x.PortoPartenza).WithMany(p => p.TestatePartenza).HasForeignKey(x => x.IdPortoPartenza).IsRequired(false);
});
_ = modelBuilder.Entity<CartaImbarcoGcimb>(e =>
{
_ = e.HasOne(x => x.OldCartaImbarcoGassv).WithOne(x => x.NewCartaImbarco).HasForeignKey<CartaImbarcoGcimb>(b => new { b.ViaggioId, b.Societa, b.IdBiglietto, b.ProgressivoCartaImbarco }).IsRequired(false);
_ = e.HasOne(x => x.CategoriaPax).WithMany().IsRequired(false).HasForeignKey(x => x.IdCategoria);
});
}
}
}
Where the buildModel
method is a static method which for every property adds the configuration
entity.Property(x => x.Myproperty).HasColumnName("ColumnName").HasColumnType("MyType")
In this way I though to avoid using those owned types and table splitting approach.
My query class is a simple one:
namespace EF400RWebApi.QueryTypes
{
public class PaxQuery
{
[UseDbContext(typeof(AS400Context))]
[UseProjection]
[UseFiltering]
public IQueryable<ViaggioGviat> GetViaggiProjection([ScopedService] AS400Context db, int id) => db.Viaggi.Where(v => v.IdViaggio == id).Include(x => x.Nave).Include(x => x.Testate);
[UseDbContext(typeof(AS400Context))]
public IQueryable<ViaggioGviat> GetViaggi([ScopedService] AS400Context db, int id) => db.Viaggi.Where(v => v.IdViaggio == id).Include(x => x.Nave);
}
}
ViaggiProjection
) any property inside Nave
which have Many-to-1 relation with shadowed navigation property on One side I get that index out of range and the .Select(_s1 => new ViaggioGviat{ Nave = _s1.Nave != null ? new NaveTanav{ IdNave = _s1.Nave.IdNave } : null } )
expression (which causes the error)
query projected {
viaggiProjection(id: 19170) {
nave {
idNave
}
}
}
results in error
{
"errors": [
{
"message": "Unexpected Execution Error",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"viaggiProjection"
],
"extensions": {
"message": "Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')",
"stackTrace": " at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.GetProjection(ProjectionBindingExpression projectionBindingExpression)\n at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)\n at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)\n at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)\n at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)\n at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)\n at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)\n at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)\n at System.Linq.Expressions.ConditionalExpression.Accept(ExpressionVisitor visitor)\n at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)\n at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)\n at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)\n at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)\n at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, LambdaExpression& relatedDataLoaders, Int32& collectionId)\n at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)\n at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)\n at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)\n at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)\n at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)\n at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)\n at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()\n at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)\n at HotChocolate.Data.ToListMiddleware`1.InvokeAsync(IMiddlewareContext context)\n at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_1`1.<<UseDbContext>b__4>d.MoveNext()\n--- End of stack trace from previous location ---\n at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_1`1.<<UseDbContext>b__4>d.MoveNext()\n--- End of stack trace from previous location ---\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\n at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"
}
}
]
}
caused by the generated linq expression using projections
DbSet<ViaggioGviat>()
.Where(v => v.IdViaggio == __id_0)
.Include(x => x.Nave)
.Include(x => x.Testate)
.Select(_s1 => new ViaggioGviat{ Nave = _s1.Nave != null ? new NaveTanav{ IdNave = _s1.Nave.IdNave }
: null }
)
while the
query projected {
viaggiProjection(id: 19170) {
testate {
idBiglietto
}
}
}
works and reply
"data": {
"viaggiProjection": [
{
"testate": [
{
"idBiglietto": 9482819
},
{
"idBiglietto": 8764575
},
]
}
] }
everything works if I don't use projections as in GetFullViaggi
.
In My use case projections are necessary because the entities have 200+ fields, queries with few fields are much faster (lower in volume: for 1 full viaggio query i get 6 MB of data, if I use projections i get few kb)
for clarity I here the navigation properties inside viaggioGviat
class ViaggioGviat {
// omitted for brevity....
// reference to Nave
public string? IdNave { get; set; }
public virtual NaveTanav Nave { get; set; } = null!;
// reference to Porto
public string? IdPortoPartenza { get; set; }
public virtual PortoAnpor PortoPartenza { get; set; } = null!;
// reference to porto
public string? IdPortoArrivo { get; set; }
public virtual PortoAnpor PortoArrivo { get; set; } = null!;
// reference to porto
public string? IdPortoPrimoScalo { get; set; }
public virtual PortoAnpor PortoPrimoScalo { get; set; } = null!;
//reference to testata
public virtual ICollection<TestataGbigt>? Testate { get; set; }
public virtual ICollection<TestataGbigt>? TestateRitorno { get; set; }
// references Multitratta
public virtual MultitrattaGmvia Multitratta { get; set; } = null!;
public virtual MultitrattaGmvia SingolaTratta { get; set; } = null!;
// omitted for brevity....
}
I'm now trying to use the github repo instead of the nuget packages so I can further investigate through brakepoints what's happening, Hotchocolate project is huge and I'm not currently been able to set it up as I get some reference errors on builds
@PascalSenn
looks like my problem is here
public static Expression NotNull(Expression expression)
{
return Expression.NotEqual(expression, _null);
}
public static Expression NotNullAndAlso(Expression property, Expression condition)
{
return Expression.Condition(
NotNull(property),
condition,
Expression.Default(property.Type));
}
Looks like theNotNullAndAlso
is adding the condition which causes my problems:
return Expression.Default(property.Type));
I kinda solved all my problems and works like a charm. I don't know if this might help. Thanks
Is there an existing issue for this?
Describe the bug
I Have wrapped an old DB2 database with EF and created a DbContext which does not match the table schema on the database. I had to "split" tables in different entities using the "Owned" properties
so namely the table
Viaggi
is splitted ininfoPax
,infoMerci
, and so on. for each of the owned type I created a Owned navigation property using EFfor example here entity is of Type
Viaggio
I Have the usual repository accessing the data and returning an
IQueryable<Viaggio>
If I don't use Projections everything works great.
The problem starts when I add
[UseProjection]
on my query: It all works well until I try to access those owned (in theory non nullable) owned entities which really are in the same table ofViaggi
.That causes
I had a look in the produced Linq query :
and it looks fine but :
_s1.InfoPax != null ?
is causing the index out of range problem. If I remove the null check and the ternary operator it works and produces the rightSQL
Steps to reproduce
Relevant log output
Additional Context?
Im using IBM entity framework core to connect to an AS400 system which has a DB2 instance
Product
Hot Chocolate
Version
12.12.0