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

Request for better Keyless Type documentation #2124

Open
BenjaminAbt opened this issue Jan 9, 2020 · 1 comment
Open

Request for better Keyless Type documentation #2124

BenjaminAbt opened this issue Jan 9, 2020 · 1 comment

Comments

@BenjaminAbt
Copy link

I think the documentation of the Keyless Types is too simple.

Things that are not currently documented:

  • ToQuery method with lambda
  • What is the currently recommended way to use Keyless Types?
  • How can keyless types be reused as efficiently as possible?
  • What are the (better described) limitations

query documentation

Currently the documentation only shows how to use the keyless types in conjunction with database views.

db.Database.ExecuteSqlRaw(
    @"CREATE VIEW View_BlogPostCounts AS 
        SELECT b.Name, Count(p.PostId) as PostCount 
        FROM Blogs b
        JOIN Posts p on p.BlogId = b.BlogId
        GROUP BY b.Name");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<BlogPostsCount>(eb =>
        {
            eb.HasNoKey();
            eb.ToView("View_BlogPostCounts");
            eb.Property(v => v.BlogName).HasColumnName("Name");
        });
}

But how would this example look like if I simply used ToQuery instead of ToView?
My guess would be:

            b.Entity<BlogPostsCount>().HasNoKey();
            b.Entity<BlogPostsCount>().ToQuery(
                () => Blogs.Select(blog => blog.Name, blog.Posts.Count));

But after several tests I am at the point where I think that this does not (no longer) work.
I have an example here that is very similar to (and based on) the blog post. Just not the posts per blog, but per user.

            b.Entity<UserBlogStatsView>().HasNoKey();
            b.Entity<UserBlogStatsView>().ToQuery(
                () => UserAccounts.Select(
                    u => new UserBlogStatsView(u.Id, u.BlogPosts.Count())));

Unfortunately this ends in the following exception:

InvalidOperationException: The LINQ expression 'DbSet
.Select(u => new UserBlogStatsView(
u.Id,
DbSet
.Where(f => EF.Property<Nullable>(u, "Id") != null && EF.Property<Nullable>(u, "Id") == EF.Property<Nullable>(f, "PostedByUserId"))
.Count()
))
.Where(u0 => u0.UserId == 1)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

So, this is a regression?

View reuse

The next point would be the combination of views in order to reuse a view as often as possible.

I've now tried various ways this might work by now, but the only one that worked was raw SQL - which I like to avoid.

public class UserProfileView
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public UserBlogStatsView StatsView { get; set; }

    public UserProfileView(int id, string userName, UserBlogStatsView statsView)
    {
        Id = id;
        UserName = userName;
        StatsView = statsView)
    }
}

When I try to register this in the ModelBinder, I get the following exception:

The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'statsView' in 'UserProfileView(int id, string userName, UserBlogStatsView statsView)'.

Here I am relatively sure that this is currently not supported - but it would be nice. But what is the idea of reusing views as often and easily as possible?
If I cannot load the views at the same time ina simple single query, can I at least load the views with multi queries (that refer to one or more entities at the same time)?

            var cq = from user in _dbContext.UserAccounts
                select new
                {
                    UserProfile = _dbContext.UserIdentityView.Where(u => user.Id == u.UserId).Single(),
                    UserBlogStats = _dbContext.UserBlogStatsViews.Where(u => user.Id == u.UserId).Single(),
                };

            var cr = await cq.FirstOrDefaultAsync(cancellationToken);

Currently I only get an exception that the query could not be translated.

So I would be very happy if the documentation on how to use keyless types better and more efficient would be extended.

@ajcvickers
Copy link
Member

@BenjaminAbt Thanks for reporting this. We will transfer this to the docs repo soon and track updating the documentation there.

@ajcvickers ajcvickers transferred this issue from dotnet/efcore Feb 19, 2020
@ajcvickers ajcvickers added this to the Backlog milestone Feb 19, 2020
@ajcvickers ajcvickers self-assigned this Feb 19, 2020
@ajcvickers ajcvickers removed their assignment Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants