Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strongly-typed nested class in Select() throws exception #1026

Closed
AKlaus opened this issue Jun 6, 2018 · 7 comments
Closed

Strongly-typed nested class in Select() throws exception #1026

AKlaus opened this issue Jun 6, 2018 · 7 comments
Assignees
Labels
Milestone

Comments

@AKlaus
Copy link

AKlaus commented Jun 6, 2018

How to return a collection of a strongly-typed class with a nested class, like below?

sess.Query<Model>().Select(user => new UserViewDto
{
	Id = user.Id,
	Contact = new ContactReference { Id = user.ContactId, Name = contact.Name }
});

where

public class UserViewDto
{
	public Guid Id { get; set; }
	public ContactReference Contact { get; set; }
	public UserViewDto()
}
public class ContactReference 
{
	public Guid Id { get; set; }
	public string Name { get; set; }
	public ContactReference()
}

Currently, an attempt to return in Select() a strongly-typed nested class (like above) throws a "Sequence contains no elements" exception:

System.InvalidOperationException
  HResult=0x80131509
  Message=Sequence contains no elements
  Source=System.Linq
  StackTrace:
   at System.Linq.Enumerable.Last[TSource](IEnumerable`1 source)
   at Marten.Schema.Field..ctor(MemberInfo[] members)
   at Marten.Schema.JsonLocatorField..ctor(String dataLocator, EnumStorage enumStyle, Casing casing, MemberInfo[] members)
   at Marten.Schema.FieldCollection.<>c__DisplayClass8_0.<FieldFor>b__1(String _)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Marten.Linq.TargetObject.SetterBinding.ToJsonBuildObjectPair(IQueryableDocument mapping)
   at System.Linq.Enumerable.SelectListIterator`2.ToArray()
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at Marten.Linq.TargetObject.ToSelectField(IQueryableDocument mapping)
   at Marten.Linq.TargetObject.ToSelector[T](IQueryableDocument mapping, Boolean distinct)
   at Marten.Linq.SelectorParser.ChooseSelector[T](String dataLocator, ITenant tenant, IQueryableDocument mapping, QueryModel query, SelectManyQuery subQuery, ISerializer serializer, IIncludeJoin[] joins)
   at Marten.Linq.Model.LinqQuery`1.BuildSelector(IIncludeJoin[] joins, QueryStatistics stats, SelectManyQuery subQuery, IIncludeJoin[] includeJoins)
   at Marten.Linq.Model.LinqQuery`1..ctor(DocumentStore store, QueryModel model, IIncludeJoin[] joins, QueryStatistics stats)
   at Marten.Linq.MartenQueryable`1.ToLinqQuery()
   at Marten.Linq.MartenQueryable`1.toDiagnosticHandler(FetchType fetchType)
   at Marten.Linq.MartenQueryable`1.BuildCommand(FetchType fetchType)
   at My Class Here.<GetItem>d__5.MoveNext() 
@AKlaus
Copy link
Author

AKlaus commented Jun 6, 2018

It might be related to #565 ...

@jeremydmiller
Copy link
Member

@AKlaus It's not really the same functionality as #565, and all this is is that the Select() transform functionality in Marten's Linq provider isn't sophisticated enough to do what you're wanting to do there. You could pull that off with a Javascript function transformation though.

@AKlaus
Copy link
Author

AKlaus commented Jun 6, 2018

There are two things I'd like to achieve:

  1. Having a nested structure;
  2. Using strongly-typed objects everywhere.

The first one can be achieved by using Javascript Transformations. Perhaps, it even would works if I modify the code to:

sess.Query<Model>().Select(user => new 
{
	Id = user.Id,
	Contact = new { Id = user.ContactId, Name = contact.Name }
});

However, I can't achieve the second goal of using strong types.

@jeremydmiller, would you recommend to try using Dapper here?

@jeremydmiller
Copy link
Member

I don't see Dapper doing any better here. EF Core maybe.

The javascript transform will still let you use strong types, as long as what JSON it returns can be deserialized into your type.

@isen-ng
Copy link
Contributor

isen-ng commented Jan 22, 2019

I have met this as well.

this code did not work:

using (var session = _documentStore.OpenSession())
{
    return await session.Query<Entity>()
        .Where(entity => entity.TenantId == tenantId)
        .Select(entity => new Account(entity))
        .ToListAsync();
}

This is the generated exception:

System.InvalidOperationException: Sequence contains no elements
   at System.Linq.Enumerable.Last[TSource](IEnumerable`1 source)
   at Marten.Schema.Field..ctor(EnumStorage enumStorage, MemberInfo[] members)
   at Marten.Schema.JsonLocatorField..ctor(String dataLocator, EnumStorage enumStyle, Casing casing, MemberInfo[] members)
   at Marten.Schema.FieldCollection.<>c__DisplayClass8_0.<FieldFor>b__1(String _)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Marten.Schema.FieldCollection.FieldFor(IEnumerable`1 members)
   at Marten.Linq.SingleFieldSelector`1..ctor(IQueryableDocument mapping, MemberInfo[] members, Boolean distinct)
   at Marten.Linq.SelectorParser.ToSelector[T](String dataLocator, ITenant tenant, IQueryableDocument mapping)
   at Marten.Linq.SelectorParser.ChooseSelector[T](String dataLocator, ITenant tenant, IQueryableDocument mapping, QueryModel query, SelectManyQuery subQuery, ISerializer serializer, IIncludeJoin[] joins)
   at Marten.Linq.Model.LinqQuery`1.BuildSelector(IIncludeJoin[] joins, QueryStatistics stats, SelectManyQuery subQuery, IIncludeJoin[] includeJoins)
   at Marten.Linq.Model.LinqQuery`1..ctor(DocumentStore store, QueryModel model, IIncludeJoin[] joins, QueryStatistics stats)
   at Marten.Linq.MartenQueryable`1.executeAsync[TResult](Func`2 source, CancellationToken token)
   at Marten.Linq.MartenQueryable`1.ToListAsync[TResult](CancellationToken token)
   at Marten.QueryableExtensions.ToListAsync[T](IQueryable`1 queryable, CancellationToken token)
   ...
   (redacted)

Workaround: don't use Marten's select. Use regular linq select:

using (var session = _documentStore.OpenSession())
{
    var result = await session.Query<Entity>()
        .Where(entity => entity.TenantId == tenantId)
        .ToListAsync();

   return result.Select(entity => new Account(entity))
         .ToList();
}

I'm not exactly sure, but I think Marten is trying to project the entity against the selected resultant class and only select the fields required (instead of ALL fields) to save data transfer. I think this is hard to combine with regular transformation code. Even if it's possible to integrate, I think it would be confusing for users to use as well.

Even Spring do not mix projections. See their example. They require users to explicitly use their projections in separate method constructions so that they don't have to mix projections with regular queries.

@oskardudycz oskardudycz pinned this issue Jan 22, 2019
@oskardudycz oskardudycz unpinned this issue Jan 22, 2019
@jeremydmiller
Copy link
Member

or Select(e => new Account{Entity = entity}). Constructor functions aren't supported in the Select() support.

@jeremydmiller jeremydmiller self-assigned this Sep 18, 2020
@jeremydmiller
Copy link
Member

I'm afraid this one's gonna get "fixed" by adding a better exception message telling you that you can't use constructors and you'll have to use object initializers instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants