Invalid Operation on projection with "as" operator

7 posts, 0 answers
  1. Paul
    Paul avatar
    17 posts
    Member since:
    May 2011

    Posted 20 May 2015 Link to this post

    Hi,

    I have been having trouble with some LINQ queries that Telerik can't seem to parse.  I have tried several different variations and I have come up with an example that fails every time.

    Setup:

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
     
    public class Teacher : Person
    {
    }
     
    public partial class FluentModelMetadataSource : FluentMetadataSource
    {
        protected override IList<MappingConfiguration> PrepareMapping()
        {
            List<MappingConfiguration> mappingConfigurations = new List<MappingConfiguration>();
     
            var person = new MappingConfiguration<Person>();
            person.MapType().ToTable("People");
            person.HasDiscriminator().ToColumn("PeopleType");
            person.HasProperty(p => p.Id).IsIdentity(KeyGenerator.Autoinc);
            mappingConfigurations.Add(person);
     
            var teacher = new MappingConfiguration<Teacher>();
            teacher.MapType().Inheritance(InheritanceStrategy.Flat).ToTable("People");
            mappingConfigurations.Add(teacher);
     
            return mappingConfigurations;
        }
     
        protected override void SetContainerSettings(MetadataContainer container)
        {
            container.Name = "FluentModel";
            container.DefaultNamespace = "TelerikProjection";
            container.NameGenerator.SourceStrategy = Telerik.OpenAccess.Metadata.NamingSourceStrategy.Property;
            container.NameGenerator.ResolveReservedWords = false;
            container.NameGenerator.RemoveCamelCase = false;
        }
    }

    Here is my query:

    var query = from t in context.GetAll<Teacher>()
                where t.Id == id
                select new
                {
                    Teacher = t,
                    Person = t as Person
                };
    query.ToList()

    Running the code above, I get the following failure:

    An exception occurred during the execution of 'Extent<TelerikProjection.Data.Entities.Teacher>().Where(t => (t.Id == value(TelerikProjection.Program+<>c__DisplayClass1).id)).Select(t => new <>f__AnonymousType0`2(Teacher = t, Person = (t As Person)))'. Failure: Object reference not set to an instance of an object.
    See InnerException for more details.
    Complete Expression:
    .Call System.Linq.Queryable.Select(
        .Call System.Linq.Queryable.Where(
            .Constant<Telerik.OpenAccess.Query.ExtentQueryImpl`1[TelerikProjection.Data.Entities.Teacher]>(Extent<TelerikProjection.Data.Entities.Teacher>()),
            '(.Lambda #Lambda1<System.Func`2[TelerikProjection.Data.Entities.Teacher,System.Boolean]>)),
        '(.Lambda #Lambda2<System.Func`2[TelerikProjection.Data.Entities.Teacher,<>f__AnonymousType0`2[TelerikProjection.Data.Entities.Teacher,TelerikProjection.Data.Entities.Person]]>))
     
    .Lambda #Lambda1<System.Func`2[TelerikProjection.Data.Entities.Teacher,System.Boolean]>(TelerikProjection.Data.Entities.Teacher $t)
    {
        $t.Id == .Constant<TelerikProjection.Program+<>c__DisplayClass1>(TelerikProjection.Program+<>c__DisplayClass1).id
    }
     
    .Lambda #Lambda2<System.Func`2[TelerikProjection.Data.Entities.Teacher,<>f__AnonymousType0`2[TelerikProjection.Data.Entities.Teacher,TelerikProjection.Data.Entities.Person]]>(TelerikProjection.Data.Entities.Teacher $t)
    {
        .New <>f__AnonymousType0`2[TelerikProjection.Data.Entities.Teacher,TelerikProjection.Data.Entities.Person](
            $t,
            $t .As TelerikProjection.Data.Entities.Person)
    }
    Stack Trace:
       at Telerik.OpenAccess.Query.ExpressionCompiler.PerformDatabaseQuery(Type type, Int32 elementAt, Object[] groupResolutionParamValues, Boolean single, Boolean checkOid)
       at Telerik.OpenAccess.Query.ExpressionExecution.PerformDatabaseQueryMulti[T](Expression expr, ExecutionSettings settings, Object[] grpVals, Boolean checkOid, QueryOptions options)
       at Telerik.OpenAccess.Query.Piece`1.ExecuteMultiple()
       at Telerik.OpenAccess.Query.Piece`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 TelerikProjection.Program.QueryData() in c:\Projects\Personal\TelerikProjection\TelerikProjection\Program.cs:line 109
       at TelerikProjection.Program.Main(String[] args) in c:\Projects\Personal\TelerikProjection\TelerikProjection\Program.cs:line 18
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
     
    Inner Exception:
    Object reference not set to an instance of an object.
    Stack Trace:
       at Telerik.OpenAccess.Query.ExpressionCompiler.PerformDatabaseQueryImpl(Type resultType, Int32 elementAt, Object[] groupResolutionParamValues, Boolean single, Boolean checkOid)
       at Telerik.OpenAccess.Query.ExpressionCompiler.PerformDatabaseQuery(Type type, Int32 elementAt, Object[] groupResolutionParamValues, Boolean single, Boolean checkOid)

    I do not get a failure with these two variants:

    var query = from t in context.GetAll<Teacher>()
                where t.Id == id
                select new
                {
                    // Teacher = t,
                    Person = t as Person
                };
    var result = query.ToList();
     
    var query = from t in context.GetAll<Teacher>()
                where t.Id == id
                select new
                {
                    Teacher = t,
                    // Person = t as Person
                };
    var result = query.ToList();

    Both of these compile and run perfectly.

    This is just a very simple breakdown of what I'm actually trying to do (populating complex DTOs), but it demonstrates the problem.  Unfortunately, the use of "as" instead of a Cast operation is part of the DTOs I am using, which I do not control.  Is there any setting or workaround I could use to allow my first query to run successfully?

    Thanks,

    Paul

  2. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 25 May 2015 Link to this post

    Hello Paul,

    Can you work around this issue by calling .ToList() first and then projecting the result however you want?
    If this is the complete query that you have you are already selecting all attributes for the result so with or without the projection you will load the same columns and rows of data.
    Here is what I mean:
    01.var query = from t in context.GetAll<Teacher>()
    02.            where t.Id == id
    03.            select t;
    04.var rawResult = query.ToList();
    05.var result = rawResult.Select(t => new
    06.            {
    07.                Teacher = t,
    08.                Person = t as Person
    09.            }).ToList();

    If you cannot apply the work around for some reason do not hesitate to contact us.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  3. DevCraft banner
  4. Paul
    Paul avatar
    17 posts
    Member since:
    May 2011

    Posted 25 May 2015 in reply to Viktor Zhivkov Link to this post

    Hi Viktor,

    Your suggestion works, but it is not ideal in my scenario.  In our actual system, the IQueryable<> object is handed out to a "builder" that is responsible for attaching the criteria as well as the projection into custom DTOs, based on various configuration and other user input.  As you noted, your approach is applicable "if this is the complete query", which it is not.  My example here is an isolation of what I determined to be the point of failure after the builder has done its job.

    In fact, the Where criteria is not even important to the failure, it appears to just be an issue with the projection.  The following is sufficient to cause the failure:

    var query = from t in context.GetAll<Teacher>()
                select new
                {
                    Teacher = t,
                    Person = t as Person
                };

     

    The builder is a separate component, and not wholly in our control.  Changes would be a significant undertaking.  As a result, I wanted to pursue the problem from the Telerik side, too.  On the surface, the query does not seem unreasonable, if a little contrived - is there anything I could do with container settings that would permit this query to run exactly as I presented it?

    Thanks,
    Paul

  5. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 28 May 2015 Link to this post

    Hello Paul,

    There is no different way to enable the projection that you want than to run it in-memory.
    There are variations how this can be done. If the most obvious one using .ToList() is not compatible with your code you can try this one:
    01.var query = from t in context.GetAll<Teacher>()
    02.        select t;
    03. 
    04.var inMemoryQuery = query.AsEnumerable() // cut the query into database-bound and in-memory one
    05.    .AsQueryable(); // lie to the next callers that this is database-bound query so you obey the original interface.
    06. 
    07.// build the projection
    08.var finalQuery = from t in inMemoryQuery
    09.           select new
    10.           {
    11.               Teacher = t,
    12.               Person = t as Person
    13.           };

    If you are adding all your filters before the .AsEnumerable() call the performance of the query will be unchanged. But if you are try to add a filter after that call, the filter will be really inefficient because the query will fetch unnecessary data.

    If you need any further assistance just let us know.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  6. Paul
    Paul avatar
    17 posts
    Member since:
    May 2011

    Posted 28 May 2015 in reply to Viktor Zhivkov Link to this post

    Hi Viktor,

    Using .AsEnumerable() and .AsQueryable() to cut over to a memory-bound collection has the same fundamental drawback as using .ToList() in my scenario - it still presumes I have access to the builder service and can inject behaviour between the filter and projection steps.  I don't have that access in my scenario, which means I am stuck with the really inefficient query that you described or somehow rebuilding my external tool.

    While I recognize that this is a strange projection to construct in a database-bound scenario, I don't feel it is an unreasonable one.  Is it possible that this projection will be possible in future versions of Telerik Data Access?

  7. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 02 Jun 2015 Link to this post

    Hi Paul,

    While the query in LINQ looks quite simple it is not the case when you try to translate it to SQL. As you may know relational storage does not work natively with the concept of inheritance. Changing the type of a record may mean joining one or more related tables or taking into account different set of columns so it is hardly a trivial thing to do. In addition you are trying to consume the same data twice, but interpreting it as different type which is another complication. 
    Given these circumstances I can not promise you when and if we will be able to change the behavior of our LINQ translation.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  8. Paul
    Paul avatar
    17 posts
    Member since:
    May 2011

    Posted 02 Jun 2015 in reply to Viktor Zhivkov Link to this post

    Hi Viktor,

    I appreciate the potential complexities of my request, but I had to investigate if it was possible.  Thanks for getting back to me.

    Paul S.

Back to Top
DevCraft banner