Request for better Keyless Type documentation
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 Thanks for reporting this. We will transfer this to the docs repo soon and track updating the documentation there.