linq2db / linq2db

Linq to database provider.
MIT License
2.99k stars 457 forks source link

Regression in L2DB 3.0.0-rc0 + linq2db.EntityFrameworkCore 3.2.0 - Null reference exception #4624

Open brunolau opened 4 years ago

brunolau commented 4 years ago

I'm facing regression on following code path, I receive crashes with null reference exception. The code worked fine with L2DB 2.xx + EF Core provider 2.xx

I'm attaching a sample repro for the problem CrashRep.zip

Sample code goes like this:

var query = db.ACL_ItemTicketDate.ToLinqToDB()
                                 .Where(p => p.Id < 1000)
                                 .GroupBy(p => p.ACL_Item)
                                 .Select(p => new 
                                 {
                                     AclItemId = p.Key.Id,
                                     AclItemName = p.Key.Name,
                                     TotalEntryCount = p.Key.ACL_Entries.Sum(ec => ec.EntriesCount),
                                     TotalEntryAllowed = p.Key.ACL_ItemTicketDates.Select(at => new 
                                     {
                                         EntryCount = at.EntryCount
                                     }).ToList()
                                 });

And the crash stack trace like this:

at LinqToDB.EntityFrameworkCore.EFCoreMetadataReader.CompareProperty(MemberInfo property, MemberInfo memberInfo)
at LinqToDB.EntityFrameworkCore.EFCoreMetadataReader.CompareProperty(IProperty property, MemberInfo memberInfo)
at LinqToDB.EntityFrameworkCore.EFCoreMetadataReader.<>c__DisplayClass8_0`1.<GetAttributes>b__1(IProperty p)
at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found)
at LinqToDB.EntityFrameworkCore.EFCoreMetadataReader.GetAttributes[T](Type type, MemberInfo memberInfo, Boolean inherit)
at LinqToDB.Mapping.MappingSchema.<>c__DisplayClass51_0`1.<GetAttributes>b__0(IMetadataReader mr)
at System.Linq.Enumerable.<SelectManyIterator>d__180`3.MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at LinqToDB.Mapping.MappingSchema.GetAttributes[T](Type type, MemberInfo memberInfo, Boolean inherit)
at LinqToDB.Mapping.MappingSchema.GetAttributes[T](Type type, MemberInfo memberInfo, Func`2 configGetter, Boolean inherit, Boolean exactForConfiguration)
at LinqToDB.Mapping.EntityDescriptor.Init()
at LinqToDB.Mapping.EntityDescriptor..ctor(MappingSchema mappingSchema, Type type)
at LinqToDB.Mapping.MappingSchema.<>c__DisplayClass91_0.<GetEntityDescriptor>b__0(ICacheEntry o)
at LinqToDB.Common.Internal.Cache.CacheExtensions.GetOrCreate[TItem](IMemoryCache cache, Object key, Func`2 factory)
at LinqToDB.Mapping.MappingSchema.GetEntityDescriptor(Type type)
at LinqToDB.SqlQuery.SqlTable..ctor(MappingSchema mappingSchema, Type objectType, String physicalName)
at LinqToDB.Linq.Builder.TableBuilder.TableContext..ctor(ExpressionBuilder builder, BuildInfo buildInfo, Type originalType)
at LinqToDB.Linq.Builder.TableBuilder.BuildSequence(ExpressionBuilder builder, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo)
at LinqToDB.Linq.Builder.WhereBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.MethodCallBuilder.BuildSequence(ExpressionBuilder builder, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo)
at LinqToDB.Linq.Builder.GroupByBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.MethodCallBuilder.BuildSequence(ExpressionBuilder builder, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo)
at LinqToDB.Linq.Builder.SelectBuilder.BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.MethodCallBuilder.BuildSequence(ExpressionBuilder builder, BuildInfo buildInfo)
at LinqToDB.Linq.Builder.ExpressionBuilder.BuildSequence(BuildInfo buildInfo)
at LinqToDB.Linq.Builder.ExpressionBuilder.Build[T]()
at LinqToDB.Linq.Query`1.CreateQuery(IDataContext dataContext, Expression expr)
at LinqToDB.Linq.Query`1.GetQuery(IDataContext dataContext, Expression& expr)
at LinqToDB.Linq.ExpressionQuery`1.GetQuery(Expression& expression, Boolean cache)
at LinqToDB.Linq.ExpressionQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at CrashRepro.Program.Main(String[] args) in C:\Users\Bruno\Desktop\CrashRep\CrashRepro\Program.cs:line 35
sdanyliv commented 4 years ago

@brunolau, sorry such queries are not supported. I think i have to throw more informative exception. You are trying to select grouped items - this is not transleable to SQL. Add AsEnumerable() before grouping. But this also will not help if you use associations after AsEnumerable(). They has to be Included.

var query = db.ACL_ItemTicketDate.ToLinqToDB()
                                 .Where(p => p.Id < 1000)
                                 .AsEnumerable()
                                 .GroupBy(p => p.ACL_Item)
                                 .Select(p => new 
                                 {
                                     AclItemId = p.Key.Id,
                                     AclItemName = p.Key.Name,
                                     TotalEntryCount = p.Key.ACL_Entries.Sum(ec => ec.EntriesCount),
                                     TotalEntryAllowed = p.Key.ACL_ItemTicketDates.Select(at => new 
                                     {
                                         EntryCount = at.EntryCount
                                     }).ToList()
                                 });
sdanyliv commented 4 years ago

Anyway, this query can be optimized if you introduce Window Functions.

brunolau commented 4 years ago

@sdanyliv I undestand this might not be supported by the actual version, what I however wanted to point out is that the query did work on prior versions [Linq2Db 2.9.6 + linq2db.EntityFrameworkCore 1.7.1]. See attached project with downgraded package dependencies

CrashRep.zip

sdanyliv commented 4 years ago

It works, but it has N+1 query problem. I'll check maybe i can reuse old functionality in this case.