Entity framework - izznogooood/dotnet-wiki GitHub Wiki

Communication with a database can be done with the help of Entity Framework (EF) Core. This is a lightweight, extensible, open source and cross-platform version of the popular Entity Framework data access technology. EF Core can serve as an object-relational mapper (O/RM), which:

  • Enables .NET developers to work with a database using .NET objects.
  • Eliminates the need for most of the data-access code that typically needs to be written.

NB! The course will use EF Core 5 to support Windows Template Studio, this may change!

With EF Core, data access is performed using a model. A model is made up of entity classes and a context object that represents a session with the database. The context object allows querying and saving data.

Hands on introduction

Follow this tutorial to create a small app that performs data access against a SQLite database using EF Core.

Creating and configuring a model

Entity Framework Core uses a set of conventions to build a model based on the shape of your entity classes. You can specify additional configuration to supplement and/or override what was discovered by convention. Additional configuration can be made by suing either of these two methods:

  • Use data annotations to configure a model.
  • Use fluent API to configure a model.

See the article Creating and configuring a model for details on ways to set up a model.

A sample of a simple model that EF will interpret, create a database, and perform data-access for you:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public Author Author { get; set; }
}

public class Author
{
    public int AuthorId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Use data annotations to configure a model

You can also apply attributes (known as Data Annotations) to your classes and properties. Data annotations will override conventions, but will be overridden by fluent API configuration.

The sample below illustrates how attributes are used to supplement the information on the Title property for EF interpretation:

public class Book
{
    public int BookId { get; set; }
    [Required]
    [StringLength(50, MinimumLength = 1)]
    public string Title { get; set; }
    public Author Author { get; set; }
}

Use fluent API to configure a model

You can override the OnModelCreating method in your derived context and use the ModelBuilder API to configure your model. This is the most powerful method of configuration and allows configuration to be specified without modifying your entity classes. Fluent API configuration has the highest precedence and will override conventions and data annotations.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Author>()
        .Property(p => p.DisplayName)
        .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
}

To reduce the size of the OnModelCreating method all configuration for an entity type can be extracted to a separate class implementing IEntityTypeConfiguration<TEntity>, see this article for detailed information.

Handle many-to-many relations

Many-to-many relationships require a collection navigation property on both sides. They will be discovered by convention like other types of relationships.

The model below has a many-to-many relationship between books and authors, this is handled automatically be EF:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public ICollection<Author> Authors { get; set; }
}

public class Author
{
    public int AuthorId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public ICollection<Book> Books { get; set; }
}

Creating the context

Once you have a model, the primary class your application interacts with is System.Data.Entity.DbContext (often referred to as the context class). The context object allows querying and saving data.

The recommended way to work with context is to define a class that derives from DbContext and exposes DbSet properties that represent collections of the specified entities in the context.

The sample below illustrates how to create a context that handles books and authors:

public class LibraryContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }
}

See Working with DbContext for more information.

Seeding the model

Data seeding is the process of populating a database with an initial set of data. We recommend seeding data as part of the model configuration, with enough data to test against while developing the project to avoid having to manually populate the database, potentially many times as the database may have to be scrapped part of the development process.

The sample below illustrates how to seed the database with a book with two authors:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>().HasData(new Book { BookId = 1, Title = "Captain America Comics #1" });

    modelBuilder.Entity<Author>().HasData(new Author { AuthorId = 1, FirstName = "Joe", LastName = "Simon" });
    modelBuilder.Entity<Author>().HasData(new Author { AuthorId = 2, FirstName = "Jack", LastName = "Kirby" });

    modelBuilder.Entity<Author>()
        .HasMany(a => a.Books)
        .WithMany(b => b.Authors)
        .UsingEntity<Dictionary<string, object>>(
            "AuthorBook",
            r => r.HasOne<Book>().WithMany().HasForeignKey("BooksBookId"),
            l => l.HasOne<Author>().WithMany().HasForeignKey("AuthorsAuthorId"),
            je =>
            {
                je.HasKey("AuthorsAuthorId", "BooksBookId");
                je.HasData(
                    new { AuthorsAuthorId = 1, BooksBookId = 1 },
                    new { AuthorsAuthorId = 2, BooksBookId = 1 });
            });
}

See Data Seeding for more information.

Queering data

Entity Framework Core uses Language-Integrated Query (LINQ) to query data from the database. LINQ allows you to use C# (or your .NET language of choice) to write strongly typed queries. It uses your derived context and entity classes to reference database objects. EF Core passes a representation of the LINQ query to the database provider. Database providers in turn translate it to database-specific query language (for example, SQL for a relational database). Queries are always executed against the database even if the entities returned in the result already exist in the context.

The sample below illustrates how to retrieve all books, with all authors of each book:

using var context = new LibraryContext();
var books = context.Books
    .Include(book => book.Authors)
    .ToList();

See Querying Data and Basic LINQ Query Operations for more information on querying the database using EF.

Saving data

The context keeps track of changes, these changes are written to the database when you call SaveChanges.

The sample below illustrates how to add a book and save it to the database:

using var context = new LibraryContext();
var book = new Book { Title = "Captain America Comics #2" };
db.Books.Add(book);
db.SaveChanges();

See Saving Data for more information on saving data to the database using EF.

Important info and advice

  • Name your entities using single form nouns, e.g., Customer, Order, Invoice.
  • Name the key of your entity <type name>Id, e.g., CustomerId, OrderId, InvoiceId.
  • When working with Web applications, e.g., web API, use a context instance per request.
  • When working with UI, use a context instance per form (page). This lets you use change-tracking functionality that context provides.
  • If the context instance is created by a dependency injection container, it is usually the responsibility of the container to dispose the context.
  • If the context is created in application code, remember to dispose of the context when it is no longer required.
  • By default, the context manages connections to the database. The context opens and closes connections as needed. For example, the context opens a connection to execute a query, and then closes the connection when all the result sets have been processed.
  • To hide the details of how exactly the data is saved or retrieved from the underlying data source, you may use the Repository Pattern abstraction, see this article on Repository Pattern in ASP.NET Core REST API for more information.
⚠️ **GitHub.com Fallback** ⚠️