In addition to regular entity types, an EF Core model can contain keyless entity types, which can be used to carry out database queries against data that doesn't contain key values.
[Keyless]
Data Annotation became available in EFCore 5.0.Keyless entity types support many of the same mapping capabilities as regular entity types, like inheritance mapping and navigation properties. On relational stores, they can configure the target database objects and columns via fluent API methods or data annotations.
However, they are different from regular entity types, such as:
DbContext
and therefore are never inserted, updated, or deleted on the database.[Keyless]
data annotation or a .HasNoKey()
method call.The most used scenarios for keyless entity types are as follows:
Keyless entity types can be defined using either the Data Annotation or the Fluent API.
class EntityContext : DbContext
{
public DbSet<AuthorBooksCount> AuthorBookCounts { get; set; }
}
[Keyless]
public class AuthorBooksCount
{
public string AuthorName { get; set; }
public int BookCount { get; set; }
}
class EntityContext : DbContext
{
public DbSet<AuthorBooksCount> AuthorBookCounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AuthorBooksCount>()
.HasNoKey();
}
}
public class AuthorBooksCount
{
public string AuthorName { get; set; }
public int BookCount { get; set; }
}
Mapping a keyless entity type to a database object is achieved using the ToTable
or ToView
fluent API. From the perspective of EF Core, the database object specified in this method is a view, meaning that it is treated as a read-only query source and cannot be the target of the update, insert or delete operations.
ToTable
method can be treated as a table, meaning that it can be used as a query source but also targeted by the update, delete and insert operations.ToTable
and everything should work fine as long as the view is configured to be updatable on the database.[!NOTE]
ToView
assumes that the object already exists in the database and won't be created by migrations.
The following example shows how to use keyless entity types to query a database view. Let's define a simple Author
and Book
model.
public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; }
}
Now we will define a simple database view that will allow us to query the number of Books
associated with each Author
.
db.Database.ExecuteSqlRaw(
@"CREATE VIEW View_AuthorBookCounts AS
SELECT b.Name, Count(p.BookId) as BookCount
FROM Authors b
JOIN Books p on p.AuthorId = b.AuthorId
GROUP BY b.Name");
Next, we define a class to hold the result from the database view:
public class AuthorBooksCount
{
public string AuthorName { get; set; }
public int BookCount { get; set; }
}
Let's configure the keyless entity type in OnModelCreating
using the HasNoKey
API. We use fluent API to configure the mapping for the keyless entity type:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<AuthorBooksCount>(eb =>
{
eb.HasNoKey();
eb.ToView("View_AuthorBookCounts");
eb.Property(v => v.AuthorName).HasColumnName("Name");
});
}
Next, we need to configure the DbContext
to include the DbSet<T>
.
public DbSet<AuthorBooksCount> AuthorBookCounts { get; set; }
Finally, we can query the database view as shown below.
var bookCounts = db.AuthorBookCounts.ToList();
foreach (var bookCount in bookCounts)
{
Console.WriteLine($"{bookCount.AuthorName} has {bookCount.BookCount} books.");
Console.WriteLine();
}