Swapping data access to use EF

Enabling data access against new EF stack.
Uses SQL Server when running on Net45 and an in-memory database for K10
(because SQL Client isn't available on K10 yet).
Various workarounds in place to compensate for missing features in EF
(all marked with "// TODO [EF]..."
This commit is contained in:
rowanmiller 2014-03-25 08:36:34 -07:00
parent 5497541e08
commit 965046813e
11 changed files with 764 additions and 610 deletions

View File

@ -11,9 +11,7 @@ namespace MusicStore.Controllers
//[Authorize]
public class CheckoutController : Controller
{
//Bug: Missing EF
//MusicStoreEntities storeDB = new MusicStoreEntities();
MusicStoreEntities storeDB = MusicStoreEntities.Instance;
MusicStoreContext db = new MusicStoreContext();
const string PromoCode = "FREE";
//
@ -48,19 +46,35 @@ namespace MusicStore.Controllers
}
else
{
// TODO [EF] Swap to store generated identity key when supported
var nextId = db.Orders.Any()
? db.Orders.Max(o => o.OrderId) + 1
: 1;
//Bug: Object values should come from page (putting in hard coded values as EF can't work with nulls against SQL Server yet)
//Bug: Identity not available
order.Username = null; //User.Identity.Name;
order.Username = "unknown"; //User.Identity.Name;
order.OrderId = nextId;
order.OrderDate = DateTime.Now;
order.FirstName = "John";
order.LastName = "Doe";
order.Address = "One Microsoft Way";
order.City = "Redmond";
order.State = "WA";
order.Country = "USA";
order.Email = "john.doe@example.com";
order.Phone = "555-555-5555";
order.PostalCode = "98052";
//Add the Order
storeDB.Orders.Add(order);
db.Orders.Add(order);
//Process the order
var cart = ShoppingCart.GetCart(storeDB, this.Context);
var cart = ShoppingCart.GetCart(db, this.Context);
cart.CreateOrder(order);
// Save all changes
storeDB.SaveChanges();
db.SaveChanges();
//Bug: Helper not available
//return RedirectToAction("Complete",
@ -87,7 +101,7 @@ namespace MusicStore.Controllers
// o => o.OrderId == id &&
// o.Username == User.Identity.Name);
bool isValid = storeDB.Orders.Any(
bool isValid = db.Orders.Any(
o => o.OrderId == id);
if (isValid)

View File

@ -9,9 +9,8 @@ namespace MvcMusicStore.Controllers
{
public class HomeController : Controller
{
/// Bug: Hacking to return a singleton. Should go away once we have EF.
//private MusicStoreEntities storeDB = new MusicStoreEntities();
private MusicStoreEntities storeDB = MusicStoreEntities.Instance;
private MusicStoreContext db = new MusicStoreContext();
//
// GET: /Home/
public IActionResult Index()
@ -27,7 +26,8 @@ namespace MvcMusicStore.Controllers
// Group the order details by album and return
// the albums with the highest count
return storeDB.Albums
// TODO [EF] We don't query related data as yet, so the OrderByDescending isn't doing anything
return db.Albums
.OrderByDescending(a => a.OrderDetails.Count())
.Take(count)
.ToList();

View File

@ -9,16 +9,14 @@ namespace MusicStore.Controllers
{
public class ShoppingCartController : Controller
{
//Bug: No EF yet
//private MusicStoreEntities storeDB = new MusicStoreEntities();
private MusicStoreEntities storeDB = MusicStoreEntities.Instance;
private MusicStoreContext db = new MusicStoreContext();
//
// GET: /ShoppingCart/
public IActionResult Index()
{
var cart = ShoppingCart.GetCart(storeDB, this.Context);
var cart = ShoppingCart.GetCart(db, this.Context);
// Set up our ViewModel
var viewModel = new ShoppingCartViewModel
@ -38,15 +36,15 @@ namespace MusicStore.Controllers
{
// Retrieve the album from the database
var addedAlbum = storeDB.Albums
var addedAlbum = db.Albums
.Single(album => album.AlbumId == id);
// Add it to the shopping cart
var cart = ShoppingCart.GetCart(storeDB, this.Context);
var cart = ShoppingCart.GetCart(db, this.Context);
cart.AddToCart(addedAlbum);
storeDB.SaveChanges();
db.SaveChanges();
// Go back to the main store page for more shopping
//Bug: Helper method not available
@ -62,16 +60,17 @@ namespace MusicStore.Controllers
public IActionResult RemoveFromCart(int id)
{
// Retrieve the current user's shopping cart
var cart = ShoppingCart.GetCart(storeDB, this.Context);
var cart = ShoppingCart.GetCart(db, this.Context);
// Get the name of the album to display confirmation
string albumName = storeDB.Carts
.Single(item => item.RecordId == id).Album.Title;
// TODO [EF] Turn into one query once query of related data is enabled
int albumId = db.Carts.Single(item => item.RecordId == id).AlbumId;
string albumName = db.Albums.Single(a => a.AlbumId == albumId).Title;
// Remove from cart
int itemCount = cart.RemoveFromCart(id);
storeDB.SaveChanges();
db.SaveChanges();
string removed = (itemCount > 0) ? " 1 copy of " : string.Empty;

View File

@ -8,15 +8,14 @@ namespace MusicStore.Controllers
{
public class StoreController : Controller
{
//Bug: Need to remove singleton instance after EF is implemented.
//MusicStoreEntities storeDB = new MusicStoreEntities();
MusicStoreEntities storeDB = MusicStoreEntities.Instance;
MusicStoreContext db = new MusicStoreContext();
//
// GET: /Store/
public IActionResult Index()
{
var genres = storeDB.Genres.ToList();
var genres = db.Genres.ToList();
return View(genres);
}
@ -27,21 +26,17 @@ namespace MusicStore.Controllers
public IActionResult Browse(string genre)
{
// Retrieve Genre genre and its Associated associated Albums albums from database
//Bug: Include is part of EF. We need to work around this temporarily
//var genreModel = storeDB.Genres.Include("Albums")
// .Single(g => g.Name == genre);
var genreModel = storeDB.Genres.Single(g => g.Name == genre);
genreModel.Albums = storeDB.Albums.Where(a => a.GenreId == genreModel.GenreId).ToList();
// TODO [EF] Swap to native support for loading related data when available
var genreModel = db.Genres.Single(g => g.Name == genre);
genreModel.Albums = db.Albums.Where(a => a.GenreId == genreModel.GenreId).ToList();
return View(genreModel);
}
public IActionResult Details(int id)
{
//Bug: Need Find method from EF.
//var album = storeDB.Albums.Find(id);
var album = storeDB.Albums.Single(a => a.AlbumId == id);
var album = db.Albums.Single(a => a.AlbumId == id);
return View(album);
}
@ -50,7 +45,8 @@ namespace MusicStore.Controllers
//[ChildActionOnly]
public IActionResult GenreMenu()
{
var genres = storeDB.Genres
// TODO [EF] We don't query related data as yet, so the OrderByDescending isn't doing anything
var genres = db.Genres
.OrderByDescending(
g => g.Albums.Sum(
a => a.OrderDetails.Sum(

View File

@ -2,6 +2,7 @@
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Data.Entity;
using MusicStore.Models;
using System.Linq;
@ -11,9 +12,7 @@ namespace MusicStore.Controllers
//[Authorize(Roles="Administrator")]
public class StoreManagerController : Controller
{
//Bug: No EF yet
//private MusicStoreEntities db = new MusicStoreEntities();
private MusicStoreEntities db = MusicStoreEntities.Instance;
private MusicStoreContext db = new MusicStoreContext();
//
// GET: /StoreManager/
@ -111,8 +110,7 @@ namespace MusicStore.Controllers
//Bug: ModelState.IsValid missing
//if (ModelState.IsValid)
{
//Bug: Missing EF
//db.Entry(album).State = EntityState.Modified;
db.ChangeTracker.Entry(album).State = EntityState.Modified;
db.SaveChanges();
//Bug: Missing RedirectToAction helper
//return RedirectToAction("Index");
@ -127,8 +125,6 @@ namespace MusicStore.Controllers
public IActionResult Delete(int id = 0)
{
//Bug: EF missing
//Album album = db.Albums.Find(id);
Album album = db.Albums.Single(a => a.AlbumId == id);
if (album == null)
{
@ -145,10 +141,9 @@ namespace MusicStore.Controllers
//TODO: How to have an action with same name 'Delete'??
public IActionResult DeleteConfirmed(int id)
{
//Bug: EF missing
//Album album = db.Albums.Find(id);
Album album = db.Albums.Single(a => a.AlbumId == id);
db.Albums.Remove(album);
// TODO [EF] Replace with EntitySet.Remove when querying attaches instances
db.ChangeTracker.Entry(album).State = EntityState.Deleted;
db.SaveChanges();
//Bug: Missing helper
//return RedirectToAction("Index");

View File

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Metadata;
using Microsoft.Data.InMemory;
using Microsoft.Data.SqlServer;
namespace MusicStore.Models
{
public class MusicStoreContext : EntityContext
{
private static EntityConfiguration _config;
public EntitySet<Album> Albums { get; set; }
public EntitySet<Artist> Artists { get; set; }
public EntitySet<Order> Orders { get; set; }
public EntitySet<Genre> Genres { get; set; }
public EntitySet<Cart> Carts { get; set; }
public EntitySet<OrderDetail> OrderDetails { get; set; }
public MusicStoreContext()
: base(GetConfiguration())
{ }
// TODO Not using OnModelCreating and OnConfiguring because model is not cached and that breaks InMemoryDataStore
// because IEntityType is a different instance for the same type between context instances
private static EntityConfiguration GetConfiguration()
{
if (_config == null)
{
var model = new Model();
var modelBuilder = new ModelBuilder(model);
modelBuilder.Entity<Album>().Key(a => a.AlbumId);
modelBuilder.Entity<Artist>().Key(a => a.ArtistId);
modelBuilder.Entity<Order>().Key(o => o.OrderId).StorageName("[Order]");
modelBuilder.Entity<Genre>().Key(g => g.GenreId);
modelBuilder.Entity<Cart>().Key(c => c.RecordId);
modelBuilder.Entity<OrderDetail>().Key(o => o.OrderDetailId);
new SimpleTemporaryConvention().Apply(model);
var builder = new EntityConfigurationBuilder();
// TODO [EF] Remove once SQL Client is available on K10
#if NET45
builder.UseSqlServer(@"Server=(localdb)\v11.0;Database=MusicStore;Trusted_Connection=True;");
#else
builder.UseDataStore(new InMemoryDataStore());
#endif
builder.UseModel(model);
_config = builder.BuildConfiguration();
}
return _config;
}
}
}

View File

@ -1,58 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace MusicStore.Models
{
/// <summary>
/// Bug: Mocked entities set. We should substitute this with DbSet once EF is available.
/// </summary>
public class MusicStoreEntities
{
public List<Album> Albums { get; set; }
public List<Genre> Genres { get; set; }
public List<Artist> Artists { get; set; }
public List<Cart> Carts { get; set; }
public List<Order> Orders { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
/// <summary>
/// Bug: Need to remove this method. Just adding this to unblock from compilation errors
/// </summary>
public void SaveChanges()
{
}
private static MusicStoreEntities instance;
public static MusicStoreEntities Instance
{
get
{
//TODO: Sync issues not handled.
if (instance == null)
{
instance = new MusicStoreEntities();
SampleData.Seed(instance);
}
return instance;
}
}
/// <summary>
/// Bug: This is to just initialize the lists. Once we have EF this should be removed.
/// </summary>
/// <param name="dummy"></param>
private MusicStoreEntities()
{
this.Albums = new List<Album>();
this.Genres = new List<Genre>();
this.Artists = new List<Artist>();
this.Carts = new List<Cart>();
this.Orders = new List<Order>();
this.OrderDetails = new List<OrderDetail>();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
@ -10,17 +11,17 @@ namespace MusicStore.Models
{
public partial class ShoppingCart
{
MusicStoreEntities _db;
MusicStoreContext _db;
string ShoppingCartId { get; set; }
public ShoppingCart(MusicStoreEntities db)
public ShoppingCart(MusicStoreContext db)
{
_db = db;
}
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(MusicStoreEntities db, HttpContext context)
public static ShoppingCart GetCart(MusicStoreContext db, HttpContext context)
{
var cart = new ShoppingCart(db);
cart.ShoppingCartId = cart.GetCartId(context);
@ -43,9 +44,15 @@ namespace MusicStore.Models
if (cartItem == null)
{
// TODO [EF] Swap to store generated key once we support identity pattern
var nextRecordId = _db.Carts.Any()
? _db.Carts.Max(c => c.RecordId) + 1
: 1;
// Create a new cart item if no cart item exists
cartItem = new Cart
{
RecordId = nextRecordId,
AlbumId = album.AlbumId,
CartId = ShoppingCartId,
Count = 1,
@ -58,6 +65,9 @@ namespace MusicStore.Models
{
// If the item does exist in the cart, then add one to the quantity
cartItem.Count++;
// TODO [EF] Remove this line once change detection is available
_db.ChangeTracker.Entry(cartItem).State = Microsoft.Data.Entity.EntityState.Modified;
}
}
@ -75,6 +85,10 @@ namespace MusicStore.Models
if (cartItem.Count > 1)
{
cartItem.Count--;
// TODO [EF] Remove this line once change detection is available
_db.ChangeTracker.Entry(cartItem).State = EntityState.Modified;
itemCount = cartItem.Count;
}
else
@ -93,7 +107,8 @@ namespace MusicStore.Models
foreach (var cartItem in cartItems)
{
_db.Carts.Remove(cartItem);
// TODO [EF] Change to EntitySet.Remove once querying attaches instances
_db.ChangeTracker.Entry(cartItem).State = EntityState.Deleted;
}
}
@ -119,10 +134,16 @@ namespace MusicStore.Models
// Multiply album price by count of that album to get
// the current price for each of those albums in the cart
// sum all album price totals to get the cart total
decimal? total = (from cartItems in _db.Carts
where cartItems.CartId == ShoppingCartId
select (int?)cartItems.Count * cartItems.Album.Price).Sum();
return total ?? decimal.Zero;
// TODO Collapse to a single query once EF supports querying related data
decimal total = 0;
foreach (var item in _db.Carts.Where(c => c.CartId == ShoppingCartId))
{
var album = _db.Albums.Single(a => a.AlbumId == item.AlbumId);
total += item.Count * album.Price;
}
return total;
}
public int CreateOrder(Order order)
@ -131,6 +152,11 @@ namespace MusicStore.Models
var cartItems = GetCartItems();
// TODO [EF] Swap to store generated identity key when supported
var nextId = _db.OrderDetails.Any()
? _db.OrderDetails.Max(o => o.OrderDetailId) + 1
: 1;
// Iterate over the items in the cart, adding the order details for each
foreach (var item in cartItems)
{
@ -140,6 +166,7 @@ namespace MusicStore.Models
var orderDetail = new OrderDetail
{
OrderDetailId = nextId,
AlbumId = item.AlbumId,
OrderId = order.OrderId,
UnitPrice = album.Price,
@ -147,9 +174,11 @@ namespace MusicStore.Models
};
// Set the order total of the shopping cart
orderTotal += (item.Count * item.Album.Price);
orderTotal += (item.Count * album.Price);
_db.OrderDetails.Add(orderDetail);
nextId++;
}
// Set the order's total to the orderTotal count

View File

@ -11,6 +11,7 @@ using Microsoft.AspNet.Identity.InMemory;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using MusicStore.Models;
using MusicStore.Web.Models;
using System;
using System.Collections.Generic;
@ -38,6 +39,8 @@ public class Startup
new { controller = "Home" });
app.UseRouter(routes);
SampleData.InitializeMusicStoreDatabase();
}
//Bug: We need EF to integrate with SQL server. Until then we will use in memory store

View File

@ -18,13 +18,18 @@
"Microsoft.AspNet.Security.DataProtection": "0.1-alpha-*",
"Microsoft.AspNet.Identity": "0.1-alpha-*",
"Microsoft.AspNet.Identity.Entity": "0.1-alpha-*",
"Microsoft.AspNet.Identity.InMemory": "0.1-alpha-*"
"Microsoft.AspNet.Identity.InMemory": "0.1-alpha-*",
"Microsoft.Data.Entity": "0.1-alpha-*",
"Microsoft.Data.Relational": "0.1-alpha-*",
"Microsoft.Data.SqlServer": "0.1-pre-*",
"Microsoft.Data.InMemory": "0.1-alpha-*"
},
"configurations": {
"net45": {
"dependencies": {
"System.Runtime": "",
"System.ComponentModel.DataAnnotations": ""
"System.ComponentModel.DataAnnotations": "",
"System.Data": ""
}
},
"k10": {