EntityFramework.Docs icon indicating copy to clipboard operation
EntityFramework.Docs copied to clipboard

Request for better Keyless Type documentation

Open BenjaminAbt opened this issue 5 years ago • 1 comments

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<UserAccountEntity> .Select(u => new UserBlogStatsView( u.Id, DbSet<BlogPostEntity> .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.

BenjaminAbt avatar Jan 09 '20 22:01 BenjaminAbt

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

ajcvickers avatar Jan 10 '20 23:01 ajcvickers