Initial commit

This commit is contained in:
Mark J Price 2022-03-05 15:45:55 +00:00
parent 01d6ccf414
commit dd097904c2
54 changed files with 37154 additions and 0 deletions

View file

@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations.Schema; // [Column]
namespace Packt.Shared;
public class Category
{
// these properties map to columns in the database
public int CategoryId { get; set; }
public string? CategoryName { get; set; }
[Column(TypeName = "ntext")]
public string? Description { get; set; }
// defines a navigation property for related rows
public virtual ICollection<Product> Products { get; set; }
public Category()
{
// to enable developers to add products to a Category we must
// initialize the navigation property to an empty collection
Products = new HashSet<Product>();
}
}

View file

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Using Include="System.Console" Static="true" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0-*" />
</ItemGroup>
<ItemGroup>
<None Update="Northwind.db">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore; // DbContext, DbContextOptionsBuilder
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace Packt.Shared;
// this manages the connection to the database
public class Northwind : DbContext
{
// these properties map to tables in the database
public DbSet<Category>? Categories { get; set; }
public DbSet<Product>? Products { get; set; }
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
string path = Path.Combine(
Environment.CurrentDirectory, "Northwind.db");
string connection = $"Filename={path}";
ConsoleColor previousColor = ForegroundColor;
ForegroundColor = ConsoleColor.DarkYellow;
WriteLine($"Connection: {connection}");
ForegroundColor = previousColor;
optionsBuilder.UseSqlite(connection);
}
protected override void OnModelCreating(
ModelBuilder modelBuilder)
{
// example of using Fluent API instead of attributes
// to limit the length of a category name to 15
modelBuilder.Entity<Category>()
.Property(category => category.CategoryName)
.IsRequired() // NOT NULL
.HasMaxLength(15);
if (Database.ProviderName?.Contains("Sqlite") ?? false)
{
// added to "fix" the lack of decimal support in SQLite
modelBuilder.Entity<Product>()
.Property(product => product.Cost)
.HasConversion<double>();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations; // [Required], [StringLength]
using System.ComponentModel.DataAnnotations.Schema; // [Column]
namespace Packt.Shared;
public class Product
{
public int ProductId { get; set; } // primary key
[Required]
[StringLength(40)]
public string ProductName { get; set; } = null!;
[Column("UnitPrice", TypeName = "money")]
public decimal? Cost { get; set; } // property name != column name
[Column("UnitsInStock")]
public short? Stock { get; set; }
public bool Discontinued { get; set; }
// these two define the foreign key relationship
// to the Categories table
public int CategoryId { get; set; }
public virtual Category Category { get; set; } = null!;
}

View file

@ -0,0 +1,163 @@
using System.Xml; // XmlWriter
using System.Text.Json; // Utf8JsonWriter, JsonWriterOptions
using Packt.Shared; // Category, Product
using static System.IO.Path;
using static System.Environment;
// we want to easily show the difference between outputting
// XML using elements or attributes so we will define a
// delegate to reference the two different methods.
delegate void WriteDataDelegate(string name, string? value);
partial class Program
{
static void GenerateXmlFile(
IQueryable<Category> categories, bool useAttributes = true)
{
string which = useAttributes ? "attibutes" : "elements";
string xmlFile = $"categories-and-products-using-{which}.xml";
using (FileStream xmlStream = File.Create(
Combine(CurrentDirectory, xmlFile)))
{
using (XmlWriter xml = XmlWriter.Create(xmlStream,
new XmlWriterSettings { Indent = true }))
{
WriteDataDelegate writeMethod;
if (useAttributes)
{
writeMethod = xml.WriteAttributeString;
}
else // use elements
{
writeMethod = xml.WriteElementString;
}
xml.WriteStartDocument();
xml.WriteStartElement("categories");
foreach (Category c in categories)
{
xml.WriteStartElement("category");
writeMethod("id", c.CategoryId.ToString());
writeMethod("name", c.CategoryName);
writeMethod("desc", c.Description);
writeMethod("product_count", c.Products.Count.ToString());
xml.WriteStartElement("products");
foreach (Product p in c.Products)
{
xml.WriteStartElement("product");
writeMethod("id", p.ProductId.ToString());
writeMethod("name", p.ProductName);
writeMethod("cost", p.Cost is null ? "0" : p.Cost.Value.ToString());
writeMethod("stock", p.Stock.ToString());
writeMethod("discontinued", p.Discontinued.ToString());
xml.WriteEndElement(); // </product>
}
xml.WriteEndElement(); // </products>
xml.WriteEndElement(); // </category>
}
xml.WriteEndElement(); // </categories>
}
}
WriteLine("{0} contains {1:N0} bytes.",
arg0: xmlFile,
arg1: new FileInfo(xmlFile).Length);
}
static void GenerateCsvFile(IQueryable<Category> categories)
{
string csvFile = "categories-and-products.csv";
using (FileStream csvStream = File.Create(Combine(CurrentDirectory, csvFile)))
{
using (StreamWriter csv = new(csvStream))
{
csv.WriteLine("CategoryId,CategoryName,Description,ProductId,ProductName,Cost,Stock,Discontinued");
foreach (Category c in categories)
{
foreach (Product p in c.Products)
{
csv.Write("{0},\"{1}\",\"{2}\",",
arg0: c.CategoryId,
arg1: c.CategoryName,
arg2: c.Description);
csv.Write("{0},\"{1}\",{2},",
arg0: p.ProductId,
arg1: p.ProductName,
arg2: p.Cost is null ? 0 : p.Cost.Value);
csv.WriteLine("{0},{1}",
arg0: p.Stock,
arg1: p.Discontinued);
}
}
}
}
WriteLine("{0} contains {1:N0} bytes.",
arg0: csvFile,
arg1: new FileInfo(csvFile).Length);
}
static void GenerateJsonFile(IQueryable<Category> categories)
{
string jsonFile = "categories-and-products.json";
using (FileStream jsonStream = File.Create(Combine(CurrentDirectory, jsonFile)))
{
using (Utf8JsonWriter json = new(jsonStream,
new JsonWriterOptions { Indented = true }))
{
json.WriteStartObject();
json.WriteStartArray("categories");
foreach (Category c in categories)
{
json.WriteStartObject();
json.WriteNumber("id", c.CategoryId);
json.WriteString("name", c.CategoryName);
json.WriteString("desc", c.Description);
json.WriteNumber("product_count", c.Products.Count);
json.WriteStartArray("products");
foreach (Product p in c.Products)
{
json.WriteStartObject();
json.WriteNumber("id", p.ProductId);
json.WriteString("name", p.ProductName);
json.WriteNumber("cost", p.Cost is null ? 0 : p.Cost.Value);
json.WriteNumber("stock", p.Stock is null ? 0 : p.Stock.Value);
json.WriteBoolean("discontinued", p.Discontinued);
json.WriteEndObject(); // product
}
json.WriteEndArray(); // products
json.WriteEndObject(); // category
}
json.WriteEndArray(); // categories
json.WriteEndObject();
}
}
WriteLine("{0} contains {1:N0} bytes.",
arg0: jsonFile,
arg1: new FileInfo(jsonFile).Length);
}
}

View file

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore; // Include extension method
using Packt.Shared; // Northwind, Category, Product
WriteLine("Creating four files containing serialized categories and products.");
using (Northwind db = new())
{
// a query to get all categories and their related products
IQueryable<Category>? categories = db.Categories?.Include(c => c.Products);
if (categories is null)
{
WriteLine("No categories found.");
return;
}
GenerateXmlFile(categories);
GenerateXmlFile(categories, useAttributes: false);
GenerateCsvFile(categories);
GenerateJsonFile(categories);
WriteLine($"Current directory: {Environment.CurrentDirectory}");
}