Skip to content

Instantly share code, notes, and snippets.

@jsheridanwells
Last active May 15, 2018 21:13
Show Gist options
  • Save jsheridanwells/3d018e4d0153e246f293f56d6f1a4697 to your computer and use it in GitHub Desktop.
Save jsheridanwells/3d018e4d0153e246f293f56d6f1a4697 to your computer and use it in GitHub Desktop.
Using Web API 2 with EF 6

Using ASP.NET Web API 2 with EF 6

Creating the project:

New Project -> ASP.NET Web Application -> Web API

Creating Entities:

Main Model:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace BookService.Models
{
    public class Author
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
    }
}

Child Model (One Foreign Key + Navigation Property):

using System.ComponentModel.DataAnnotations;

namespace BookService.Models
{
    public class Book
    {
        public int Id { get; set; }
        [Required]
        public string Title { get; set; }
        public int Year { get; set; }
        public decimal Price { get; set; }
        public string Genre { get; set; }

        // Foreign Key
        public int AuthorId { get; set; }
        // Navigation property
        public Author Author { get; set; }
    }
}

Controller w/ scaffold can be added:

Add -> Controller -> Web API 2 Controller with Actions, using EF

In the Add Controller dialog: Click Add -> Name the data context (Additional controllers will still use this context)

This will add controller + db context

Seeding

In Package Console: Enable-Migrations. This will add migrations folder.

In created Configuration.cs file, import Models: using BookService.Models.

Add to Configuration:Seed method:

protected override void Seed(BookService.Models.BookServiceContext context)
{
    context.Authors.AddOrUpdate(x => x.Id,
        new Author() { Id = 1, Name = "Jane Austen" },
        new Author() { Id = 2, Name = "Charles Dickens" },
        new Author() { Id = 3, Name = "Miguel de Cervantes" }
        );

    context.Books.AddOrUpdate(x => x.Id,
        new Book() { Id = 1, Title = "Pride and Prejudice", Year = 1813, AuthorId = 1, 
            Price = 9.99M, Genre = "Comedy of manners" },
        new Book() { Id = 2, Title = "Northanger Abbey", Year = 1817, AuthorId = 1, 
            Price = 12.95M, Genre = "Gothic parody" },
        new Book() { Id = 3, Title = "David Copperfield", Year = 1850, AuthorId = 2, 
            Price = 15, Genre = "Bildungsroman" },
        new Book() { Id = 4, Title = "Don Quixote", Year = 1617, AuthorId = 3, 
            Price = 8.95M, Genre = "Picaresque" }
        );
}

In package console: Add-Migration Initial, then Update-Database

Handling Entity Relations

You can add SQL query logs in the context constructor:

public BookServiceContext() : base("name=BookServiceContext")
{
    // New code:
    this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}

Eager Loading:

Serialize related data (this creates inner join in SQL query):

In BooksController:

public IQueryable<Book> GetBooks()
{
    return db.Books
        // new code:
        .Include(b => b.Author);
}

Lazy Loading:

Loads on when nav reference is called. Accomplished by making property virtual.

public class Book
{
    // (Other properties)

    // Virtual navigation property
    public virtual Author Author { get; set; }
}
var books = db.Books.ToList();  // Does not load authors
var author = books[0].Author;   // Loads the author for books[0]

Data Transfer Objects

DTOs help you customize how data is organized on return.

Create two new classes BookDTO and BookDetailDTO

Add:

namespace BookService.Models
{
    public class BookDTO
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string AuthorName { get; set; }
    }
}

namespace BookService.Models
{
    public class BookDetailDTO
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int Year { get; set; }
        public decimal Price { get; set; }
        public string AuthorName { get; set; }
        public string Genre { get; set; }
    }
}

Change BooksController:

// GET api/Books
public IQueryable<BookDTO> GetBooks()
{
    var books = from b in db.Books
                select new BookDTO()
                {
                    Id = b.Id,
                    Title = b.Title,
                    AuthorName = b.Author.Name
                };

    return books;
}

// GET api/Books/5
[ResponseType(typeof(BookDetailDTO))]
public async Task<IHttpActionResult> GetBook(int id)
{
    var book = await db.Books.Include(b => b.Author).Select(b =>
        new BookDetailDTO()
        {
            Id = b.Id,
            Title = b.Title,
            Year = b.Year,
            Price = b.Price,
            AuthorName = b.Author.Name,
            Genre = b.Genre
        }).SingleOrDefaultAsync(b => b.Id == id);
    if (book == null)
    {
        return NotFound();
    }

    return Ok(book);
}

Modify PostBook to return DTO:

[ResponseType(typeof(Book))]
public async Task<IHttpActionResult> PostBook(Book book)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Books.Add(book);
    await db.SaveChangesAsync();

    // New code:
    // Load author name
    db.Entry(book).Reference(x => x.Author).Load();

    var dto = new BookDTO()
    {
        Id = book.Id,
        Title = book.Title,
        AuthorName = book.Author.Name
    };

    return CreatedAtRoute("DefaultApi", new { id = book.Id }, dto);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment