Skip to content

Instantly share code, notes, and snippets.

@GaProgMan GaProgMan/AddedShadowProperties.cs Secret
Last active Mar 2, 2017

Embed
What would you like to do?
Code snippets for the blog post /2017/03/02/webapi-tutorial-library-check-audit-and-join-all-the-things
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace webApiTutorial.Migrations
{
public partial class AddedShadowProperties : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Characters",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<string>(
name: "CreatedBy",
table: "Characters",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Modified",
table: "Characters",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ModifiedBy",
table: "Characters",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Books",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<string>(
name: "CreatedBy",
table: "Books",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Modified",
table: "Books",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ModifiedBy",
table: "Books",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Created",
table: "Characters");
migrationBuilder.DropColumn(
name: "CreatedBy",
table: "Characters");
migrationBuilder.DropColumn(
name: "Modified",
table: "Characters");
migrationBuilder.DropColumn(
name: "ModifiedBy",
table: "Characters");
migrationBuilder.DropColumn(
name: "Created",
table: "Books");
migrationBuilder.DropColumn(
name: "CreatedBy",
table: "Books");
migrationBuilder.DropColumn(
name: "Modified",
table: "Books");
migrationBuilder.DropColumn(
name: "ModifiedBy",
table: "Books");
}
}
}
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace webApiTutorial.Migrations
{
public partial class AddedShadowProperties : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Characters",
nullable: false,
defaultValue: DateTime.Now);
migrationBuilder.AddColumn<string>(
name: "CreatedBy",
table: "Characters",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Modified",
table: "Characters",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ModifiedBy",
table: "Characters",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Books",
nullable: false,
defaultValue: DateTime.Now);
migrationBuilder.AddColumn<string>(
name: "CreatedBy",
table: "Books",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "Modified",
table: "Books",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ModifiedBy",
table: "Books",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Created",
table: "Characters");
migrationBuilder.DropColumn(
name: "CreatedBy",
table: "Characters");
migrationBuilder.DropColumn(
name: "Modified",
table: "Characters");
migrationBuilder.DropColumn(
name: "ModifiedBy",
table: "Characters");
migrationBuilder.DropColumn(
name: "Created",
table: "Books");
migrationBuilder.DropColumn(
name: "CreatedBy",
table: "Books");
migrationBuilder.DropColumn(
name: "Modified",
table: "Books");
migrationBuilder.DropColumn(
name: "ModifiedBy",
table: "Books");
}
}
}
namespace webApiTutorial.Models
{
public class Book : IAuditable
{
public int BookId { get; set; }
public int BookOrdinal { get; set; }
public string BookName { get; set; }
public string BookIsbn10 { get; set; }
public string BookIsbn13 { get; set; }
public string BookDescription { get; set; }
}
}
namespace webApiTutorial.Models
{
public class BookCharacter : IAuditable
{
public int BookId { get; set; }
public virtual Book Book { get; set; }
public int CharacterId {get; set; }
public virtual Character Character { get; set; }
}
}
using System.Collections.Generic;
namespace webApiTutorial.DatabaseTools
{
public class BookCharacterSeedData
{
public string BookName {get; set;}
public List<string> CharacterNames {get; set;}
}
}
[
{
"BookName": "The Colour of Magic",
"CharacterNames": [
"Bel-Shamharoth",
"Hrun, the Barbarian",
"Liessa Wyrmbidder",
"The Luggage",
"Rincewind",
"Twoflower"
]
}
]
[
{
"BookName": "The Colour of Magic",
"BookOrdinal": "1",
"BookIsbn10": "086140324X",
"BookIsbn13": "9780552138932",
"BookDescription": "On a world supported on the back of a giant turtle (sex unknown), a gleeful, explosive, wickedly eccentric expedition sets out. There's an avaricious but inept wizard, a naive tourist whose luggage moves on hundreds of dear little legs, dragons who only exist if you believe in them, and of course THE EDGE of the planet ...",
"BookCoverImageUrl": "http://wiki.lspace.org/mediawiki/images/c/c9/Cover_The_Colour_Of_Magic.jpg"
}
]
// book entity
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace webApiTutorial.Models
{
public class Book : IAuditable
{
public int BookId { get; set; }
public int BookOrdinal { get; set; }
public string BookName { get; set; }
public string BookIsbn10 { get; set; }
public string BookIsbn13 { get; set; }
public string BookDescription { get; set; }
public virtual ICollection<BookCharacter> BookCharacter { get; set; } = new Collection<BookCharacter>();
}
}
// book service
private IEnumerable<Book> BaseQuery()
{
return _dwContext.Books
.AsNoTracking()
.Include(book => book.BookCharacter)
.ThenInclude(BookCharacter => BookCharacter.Character);
}
// book view model
using System.Collections.Generic;
namespace webApiTutorial.ViewModels
{
public class BookViewModel : BaseViewModel
{
public BookViewModel()
{
Characters = new List<string>();
}
public int BookOrdinal { get; set; }
public string BookName { get; set; }
public string BookIsbn10 { get; set; }
public string BookIsbn13 { get; set; }
public string BookDescription { get; set; }
public byte[] BookCoverImage { get; set; }
public string BookCoverImageUrl { get; set; }
public List<string> Characters { get; set; }
}
}
// book helper
public static BookViewModel ConvertToViewModel (Book dbModel)
{
var viewModel = new BookViewModel
{
BookOrdinal = dbModel.BookOrdinal,
BookName = dbModel.BookName,
BookIsbn10 = dbModel.BookIsbn10,
BookIsbn13 = dbModel.BookIsbn13,
BookDescription = dbModel.BookDescription,
};
foreach(var bc in dbModel.BookCharacter)
{
viewModel.Characters.Add(bc.Character.CharacterName ?? string.Empty);
}
return viewModel;
}
namespace webApiTutorial.Models
{
public class Character : IAuditable
{
public int CharacterId { get; set; }
public string CharacterName { get; set; }
public int CharacterOrdinal { get;set; }
}
}
[
{
"CharacterName": "Rincewind",
"CharacterOrdinal": "0"
},
{
"CharacterName": "Twoflower",
"CharacterOrdinal": "1"
},
{
"CharacterName": "The Luggage",
"CharacterOrdinal": "2"
},
{
"CharacterName": "Liessa Wyrmbidder",
"CharacterOrdinal": "3"
},
{
"CharacterName" : "Hrun, the Barbarian",
"CharacterOrdinal": "4"
},
{
"CharacterName" : "Bel-Shamharoth",
"CharacterOrdinal": "5"
}
]
// Character data model
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace webApiTutorial.Models
{
public class Character : IAuditable
{
public int CharacterId { get; set; }
public string CharacterName { get; set; }
public int CharacterOrdinal { get;set; }
public virtual ICollection<BookCharacter> BookCharacter { get; set; } = new Collection<BookCharacter>();
}
}
// character service
private IEnumerable<Character> BaseQuery()
{
return _dwContext.Characters
.AsNoTracking()
.Include(character => character.BookCharacter)
.ThenInclude(bookCharacter => bookCharacter.Book);
}
// character view model
using System.Collections.Generic;
namespace webApiTutorial.ViewModels
{
public class CharacterViewModel : BaseViewModel
{
public CharacterViewModel()
{
Books = new List<string>();
}
public string CharacterName { get; set; }
public List<string> Books { get; set; }
}
}
// character helper
public static CharacterViewModel ConvertToviewModel (Character dbModel)
{
var viewModel = new CharacterViewModel
{
CharacterName = dbModel.CharacterName
};
foreach (var book in dbModel.BookCharacter)
{
viewModel.Books.Add(book.Book.BookName ?? string.Empty);
}
return viewModel;
}
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using webApiTutorial.DatabaseContexts;
using webApiTutorial.Models;
namespace webApiTutorial.DatabaseTools
{
public class DatabaseSeeder
{
private DwContext _context;
public DatabaseSeeder(DwContext context)
{
_context = context;
}
public int SeedBookEntitiesFromJson()
{
var recordsAdded = default(int);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookSeedData.json");
if (File.Exists(filePath))
{
var dataSet = File.ReadAllText(filePath);
var seedData = JsonConvert.DeserializeObject<List<Book>>(dataSet);
// ensure that we only get the distinct books (based on their name)
var distinctSeedData = seedData.GroupBy(b => b.BookName).Select(b => b.First());
_context.Books.AddRange(distinctSeedData);
recordsAdded = _context.SaveChanges();
}
return recordsAdded;
}
public int SeedCharacterEntitiesFromJson()
{
var recordsAdded = default(int);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "CharacterSeedData.json");
if (File.Exists(filePath))
{
var dataSet = File.ReadAllText(filePath);
var seedData = JsonConvert.DeserializeObject<IEnumerable<Character>>(dataSet);
// ensure that we only get the distinct characters (based on their name)
var distinctSeedData = seedData.GroupBy(c => c.CharacterName).Select(c => c.First());
_context.Characters.AddRange(distinctSeedData);
recordsAdded = _context.SaveChanges();
}
return recordsAdded;
}
public int SeedBookCharacterEntriesFromJson()
{
var recordsAdded = default(int);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookCharacterSeedData.json");
if (File.Exists(filePath))
{
var dataSet = File.ReadAllText(filePath);
var seedData = JsonConvert.DeserializeObject<List<BookCharacterSeedData>>(dataSet);
foreach(var seedBook in seedData)
{
var dbBook = _context.Books.Single(b => b.BookName == seedBook.BookName);
foreach (var seedChar in seedBook.CharacterNames)
{
var dbChar = _context.Characters.FirstOrDefault(c => c.CharacterName == seedChar);
if (dbChar != null)
{
_context.BookCharacters.Add(new BookCharacter
{
Book = dbBook,
Character = dbChar
});
}
}
}
recordsAdded = _context.SaveChanges();
}
return recordsAdded;
}
}
}
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using webApiTutorial.Models;
using System.Reflection;
namespace webApiTutorial.DatabaseContexts
{
public class DwContext : DbContext
{
public DwContext(DbContextOptions<DwContext> options) : base(options) { }
public DwContext() { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Create our audit fields as shadow properties
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(IAuditable).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime>("Created");
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime?>("Modified");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("CreatedBy");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("ModifiedBy");
}
modelBuilder.Entity<BookCharacter>().HasKey(x => new { x.BookId, x.CharacterId });
base.OnModelCreating(modelBuilder);
}
public override int SaveChanges()
{
ApplyAuditInformation();
return base.SaveChanges();
}
private void ApplyAuditInformation()
{
var modifiedEntities = ChangeTracker.Entries<IAuditable>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
foreach (var entity in modifiedEntities)
{
entity.Property("Modified").CurrentValue = DateTime.UtcNow;
entity.Property("ModifiedBy").CurrentValue = String.Empty;
if (entity.State == EntityState.Added)
{
entity.Property("Created").CurrentValue = DateTime.UtcNow;
entity.Property("CreatedBy").CurrentValue = "Migration";
}
}
}
public DbSet<Book> Books { get; set; }
public DbSet<Character> Characters { get; set; }
public DbSet<BookCharacter> BookCharacters { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Reflection;
using webApiTutorial.Models;
namespace webApiTutorial.DatabaseContexts
{
public class DwContext : DbContext
{
public DwContext(DbContextOptions<DwContext> options) : base(options) { }
public DwContext() { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Create our audit fields as shadow properties
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(IAuditable).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime>("Created");
modelBuilder.Entity(entityType.ClrType)
.Property<DateTime?>("Modified");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("CreatedBy");
modelBuilder.Entity(entityType.ClrType)
.Property<string>("ModifiedBy");
}
base.OnModelCreating(modelBuilder);
}
public override int SaveChanges()
{
ApplyAuditInformation();
return base.SaveChanges();
}
private void ApplyAuditInformation()
{
var modifiedEntities = ChangeTracker.Entries<IAuditable>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
foreach (var entity in modifiedEntities)
{
entity.Property("Modified").CurrentValue = DateTime.UtcNow;
entity.Property("ModifiedBy").CurrentValue = String.Empty;
if (entity.State == EntityState.Added)
{
entity.Property("Created").CurrentValue = DateTime.UtcNow;
entity.Property("CreatedBy").CurrentValue = "Migration";
}
}
}
public DbSet<Book> Books { get; set; }
public DbSet<Character> Characters { get; set; }
}
}
using webApiTutorial.Models;
using webApiTutorial.DatabaseTools;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace webApiTutorial.DatabaseContexts
{
public static class DatabaseContextExtentsions
{
public static bool AllMigrationsApplied(this DwContext context)
{
var applied = context.GetService<IHistoryRepository>()
.GetAppliedMigrations()
.Select(m => m.MigrationId);
var total = context.GetService<IMigrationsAssembly>()
.Migrations
.Select(m => m.Key);
return !total.Except(applied).Any();
}
public static void EnsureSeedData(this DwContext context)
{
if (context.AllMigrationsApplied())
{
var dbSeeder = new DatabaseSeeder(context);
if (!context.Books.Any())
{
dbSeeder.SeedBookEntitiesFromJson();
}
if (!context.Characters.Any())
{
dbSeeder.SeedCharacterEntitiesFromJson();
}
if (!context.BookCharacters.Any())
{
dbSeeder.SeedBookCharacterEntriesFromJson();
}
context.SaveChanges();
}
}
}
}
namespace webApiTutorial.Models
{
public interface IAuditable
{
}
}
dotnet run
dotnet ef migrations add AddedShadowProperties
dotnet ef database update
dotnet run
dotnet ef migrations add AddedBookCharacterTable
dotnet ef database update
dotnet run
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using webApiTutorial.DatabaseContexts;
using webApiTutorial.Services;
namespace webApiTutorial
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Give ourselves access to the DwContext
services.AddDbContext<DwContext>(options =>
options.UseSqlite(Configuration["Data:SqliteConnection:ConnectionString"]));
// DI our Book service into our controllers
services.AddTransient<IBookService, BookService>();
services.AddTransient<ICharacterService, CharacterService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
// seed the database using an extension method
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<DwContext>();
context.Database.Migrate();
context.EnsureSeedData();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.