mirror of
https://github.com/markjprice/cs11dotnet7.git
synced 2026-01-01 13:59:57 +01:00
Initial commit
This commit is contained in:
parent
01d6ccf414
commit
dd097904c2
24
vs4win/Chapter10/Ch10Ex02DataSerialization/Category.cs
Normal file
24
vs4win/Chapter10/Ch10Ex02DataSerialization/Category.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
47
vs4win/Chapter10/Ch10Ex02DataSerialization/Northwind.cs
Normal file
47
vs4win/Chapter10/Ch10Ex02DataSerialization/Northwind.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
vs4win/Chapter10/Ch10Ex02DataSerialization/Northwind.db
Normal file
BIN
vs4win/Chapter10/Ch10Ex02DataSerialization/Northwind.db
Normal file
Binary file not shown.
8722
vs4win/Chapter10/Ch10Ex02DataSerialization/Northwind4SQLite.sql
Normal file
8722
vs4win/Chapter10/Ch10Ex02DataSerialization/Northwind4SQLite.sql
Normal file
File diff suppressed because it is too large
Load diff
26
vs4win/Chapter10/Ch10Ex02DataSerialization/Product.cs
Normal file
26
vs4win/Chapter10/Ch10Ex02DataSerialization/Product.cs
Normal 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!;
|
||||
}
|
||||
163
vs4win/Chapter10/Ch10Ex02DataSerialization/Program.Functions.cs
Normal file
163
vs4win/Chapter10/Ch10Ex02DataSerialization/Program.Functions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
23
vs4win/Chapter10/Ch10Ex02DataSerialization/Program.cs
Normal file
23
vs4win/Chapter10/Ch10Ex02DataSerialization/Program.cs
Normal 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}");
|
||||
}
|
||||
37
vs4win/Chapter10/Chapter10.sln
Normal file
37
vs4win/Chapter10/Chapter10.sln
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.2.32210.308
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkingWithEFCore", "WorkingWithEFCore\WorkingWithEFCore.csproj", "{4D815158-DA92-4AD6-A417-BEC800167D1E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoursesAndStudents", "CoursesAndStudents\CoursesAndStudents.csproj", "{B4E2819E-6E1B-4314-BBC4-80253EC7764E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ch10Ex02DataSerialization", "Ch10Ex02DataSerialization\Ch10Ex02DataSerialization.csproj", "{C9B3475A-66BB-48DC-B0A7-643A415A3D51}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4D815158-DA92-4AD6-A417-BEC800167D1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4D815158-DA92-4AD6-A417-BEC800167D1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4D815158-DA92-4AD6-A417-BEC800167D1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4D815158-DA92-4AD6-A417-BEC800167D1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B4E2819E-6E1B-4314-BBC4-80253EC7764E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B4E2819E-6E1B-4314-BBC4-80253EC7764E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B4E2819E-6E1B-4314-BBC4-80253EC7764E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B4E2819E-6E1B-4314-BBC4-80253EC7764E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C9B3475A-66BB-48DC-B0A7-643A415A3D51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C9B3475A-66BB-48DC-B0A7-643A415A3D51}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C9B3475A-66BB-48DC-B0A7-643A415A3D51}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C9B3475A-66BB-48DC-B0A7-643A415A3D51}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FFE356D0-B615-4575-B486-42CBA73ED30C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
91
vs4win/Chapter10/CoursesAndStudents/Academy.cs
Normal file
91
vs4win/Chapter10/CoursesAndStudents/Academy.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using Microsoft.EntityFrameworkCore; // DbContext, DbSet<T>
|
||||
|
||||
namespace Packt.Shared;
|
||||
|
||||
public class Academy : DbContext
|
||||
{
|
||||
public DbSet<Student>? Students { get; set; }
|
||||
public DbSet<Course>? Courses { get; set; }
|
||||
|
||||
protected override void OnConfiguring(
|
||||
DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
string path = Path.Combine(
|
||||
Environment.CurrentDirectory, "Academy.db");
|
||||
|
||||
string connection = $"Filename={path}";
|
||||
// string connection = @"Data Source=.;Initial Catalog=Academy;Integrated Security=true;MultipleActiveResultSets=true;";
|
||||
|
||||
WriteLine($"Connection: {connection}");
|
||||
|
||||
optionsBuilder.UseSqlite(connection);
|
||||
// optionsBuilder.UseSqlServer(connection);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Fluent API validation rules
|
||||
modelBuilder.Entity<Student>()
|
||||
.Property(s => s.LastName).HasMaxLength(30).IsRequired();
|
||||
|
||||
// populate database with sample data
|
||||
Student alice = new()
|
||||
{
|
||||
StudentId = 1,
|
||||
FirstName = "Alice",
|
||||
LastName = "Jones"
|
||||
};
|
||||
|
||||
Student bob = new()
|
||||
{
|
||||
StudentId = 2,
|
||||
FirstName = "Bob",
|
||||
LastName = "Smith"
|
||||
};
|
||||
|
||||
Student cecilia = new()
|
||||
{
|
||||
StudentId = 3,
|
||||
FirstName = "Cecilia",
|
||||
LastName = "Ramirez"
|
||||
};
|
||||
|
||||
Course csharp = new()
|
||||
{
|
||||
CourseId = 1,
|
||||
Title = "C# 10 and .NET 6"
|
||||
};
|
||||
|
||||
Course webdev = new()
|
||||
{
|
||||
CourseId = 2,
|
||||
Title = "Web Development"
|
||||
};
|
||||
|
||||
Course python = new()
|
||||
{
|
||||
CourseId = 3,
|
||||
Title = "Python for Beginners"
|
||||
};
|
||||
|
||||
modelBuilder.Entity<Student>()
|
||||
.HasData(alice, bob, cecilia);
|
||||
|
||||
modelBuilder.Entity<Course>()
|
||||
.HasData(csharp, webdev, python);
|
||||
|
||||
modelBuilder.Entity<Course>()
|
||||
.HasMany(c => c.Students)
|
||||
.WithMany(s => s.Courses)
|
||||
.UsingEntity(e => e.HasData(
|
||||
// all students signed up for C# course
|
||||
new { CoursesCourseId = 1, StudentsStudentId = 1 },
|
||||
new { CoursesCourseId = 1, StudentsStudentId = 2 },
|
||||
new { CoursesCourseId = 1, StudentsStudentId = 3 },
|
||||
// only Bob signed up for Web Dev
|
||||
new { CoursesCourseId = 2, StudentsStudentId = 2 },
|
||||
// only Cecilia signed up for Python
|
||||
new { CoursesCourseId = 3, StudentsStudentId = 3 }
|
||||
));
|
||||
}
|
||||
}
|
||||
14
vs4win/Chapter10/CoursesAndStudents/Course.cs
Normal file
14
vs4win/Chapter10/CoursesAndStudents/Course.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Packt.Shared;
|
||||
|
||||
public class Course
|
||||
{
|
||||
public int CourseId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(60)]
|
||||
public string? Title { get; set; }
|
||||
|
||||
public ICollection<Student>? Students { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<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-*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0-*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
25
vs4win/Chapter10/CoursesAndStudents/Program.cs
Normal file
25
vs4win/Chapter10/CoursesAndStudents/Program.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Microsoft.EntityFrameworkCore; // for GenerateCreateScript()
|
||||
using Packt.Shared; // Academy
|
||||
|
||||
using (Academy a = new())
|
||||
{
|
||||
bool deleted = await a.Database.EnsureDeletedAsync();
|
||||
WriteLine($"Database deleted: {deleted}");
|
||||
|
||||
bool created = await a.Database.EnsureCreatedAsync();
|
||||
WriteLine($"Database created: {created}");
|
||||
|
||||
WriteLine("SQL script used to create database:");
|
||||
WriteLine(a.Database.GenerateCreateScript());
|
||||
|
||||
foreach (Student s in a.Students.Include(s => s.Courses))
|
||||
{
|
||||
WriteLine("{0} {1} attends the following {2} courses:",
|
||||
s.FirstName, s.LastName, s.Courses.Count);
|
||||
|
||||
foreach (Course c in s.Courses)
|
||||
{
|
||||
WriteLine($" {c.Title}");
|
||||
}
|
||||
}
|
||||
}
|
||||
9
vs4win/Chapter10/CoursesAndStudents/Student.cs
Normal file
9
vs4win/Chapter10/CoursesAndStudents/Student.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Packt.Shared;
|
||||
|
||||
public class Student
|
||||
{
|
||||
public int StudentId { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public ICollection<Course>? Courses { get; set; }
|
||||
}
|
||||
29
vs4win/Chapter10/WorkingWithEFCore/AutoGenModels/Category.cs
Normal file
29
vs4win/Chapter10/WorkingWithEFCore/AutoGenModels/Category.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace WorkingWithEFCore.AutoGen
|
||||
{
|
||||
[Index("CategoryName", Name = "CategoryName")]
|
||||
public partial class Category
|
||||
{
|
||||
public Category()
|
||||
{
|
||||
Products = new HashSet<Product>();
|
||||
}
|
||||
|
||||
[Key]
|
||||
public long CategoryId { get; set; }
|
||||
[Column(TypeName = "nvarchar (15)")]
|
||||
public string CategoryName { get; set; } = null!;
|
||||
[Column(TypeName = "ntext")]
|
||||
public string? Description { get; set; }
|
||||
[Column(TypeName = "image")]
|
||||
public byte[]? Picture { get; set; }
|
||||
|
||||
[InverseProperty("Category")]
|
||||
public virtual ICollection<Product> Products { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace WorkingWithEFCore.AutoGen
|
||||
{
|
||||
public partial class Northwind : DbContext
|
||||
{
|
||||
public Northwind()
|
||||
{
|
||||
}
|
||||
|
||||
public Northwind(DbContextOptions<Northwind> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual DbSet<Category> Categories { get; set; } = null!;
|
||||
public virtual DbSet<Product> Products { get; set; } = null!;
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
{
|
||||
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
|
||||
optionsBuilder.UseSqlite("Filename=Northwind.db");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Category>(entity =>
|
||||
{
|
||||
entity.Property(e => e.CategoryId).ValueGeneratedNever();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Product>(entity =>
|
||||
{
|
||||
entity.Property(e => e.ProductId).ValueGeneratedNever();
|
||||
|
||||
entity.Property(e => e.Discontinued).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.ReorderLevel).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.UnitPrice).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.UnitsInStock).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.UnitsOnOrder).HasDefaultValueSql("0");
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
}
|
||||
|
||||
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
|
||||
}
|
||||
}
|
||||
41
vs4win/Chapter10/WorkingWithEFCore/AutoGenModels/Product.cs
Normal file
41
vs4win/Chapter10/WorkingWithEFCore/AutoGenModels/Product.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace WorkingWithEFCore.AutoGen
|
||||
{
|
||||
[Index("CategoryId", Name = "CategoriesProducts")]
|
||||
[Index("CategoryId", Name = "CategoryId")]
|
||||
[Index("ProductName", Name = "ProductName")]
|
||||
[Index("SupplierId", Name = "SupplierId")]
|
||||
[Index("SupplierId", Name = "SuppliersProducts")]
|
||||
public partial class Product
|
||||
{
|
||||
[Key]
|
||||
public long ProductId { get; set; }
|
||||
[Column(TypeName = "nvarchar (40)")]
|
||||
public string ProductName { get; set; } = null!;
|
||||
[Column(TypeName = "int")]
|
||||
public long? SupplierId { get; set; }
|
||||
[Column(TypeName = "int")]
|
||||
public long? CategoryId { get; set; }
|
||||
[Column(TypeName = "nvarchar (20)")]
|
||||
public string? QuantityPerUnit { get; set; }
|
||||
[Column(TypeName = "money")]
|
||||
public byte[]? UnitPrice { get; set; }
|
||||
[Column(TypeName = "smallint")]
|
||||
public long? UnitsInStock { get; set; }
|
||||
[Column(TypeName = "smallint")]
|
||||
public long? UnitsOnOrder { get; set; }
|
||||
[Column(TypeName = "smallint")]
|
||||
public long? ReorderLevel { get; set; }
|
||||
[Column(TypeName = "bit")]
|
||||
public byte[] Discontinued { get; set; } = null!;
|
||||
|
||||
[ForeignKey("CategoryId")]
|
||||
[InverseProperty("Products")]
|
||||
public virtual Category? Category { get; set; }
|
||||
}
|
||||
}
|
||||
24
vs4win/Chapter10/WorkingWithEFCore/Category.cs
Normal file
24
vs4win/Chapter10/WorkingWithEFCore/Category.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
57
vs4win/Chapter10/WorkingWithEFCore/Northwind.cs
Normal file
57
vs4win/Chapter10/WorkingWithEFCore/Northwind.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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);
|
||||
|
||||
optionsBuilder.LogTo(WriteLine, // Console
|
||||
new[] { RelationalEventId.CommandExecuting })
|
||||
.EnableSensitiveDataLogging();
|
||||
|
||||
optionsBuilder.UseLazyLoadingProxies();
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
// global filter to remove discontinued products
|
||||
modelBuilder.Entity<Product>()
|
||||
.HasQueryFilter(p => !p.Discontinued);
|
||||
}
|
||||
}
|
||||
BIN
vs4win/Chapter10/WorkingWithEFCore/Northwind.db
Normal file
BIN
vs4win/Chapter10/WorkingWithEFCore/Northwind.db
Normal file
Binary file not shown.
8722
vs4win/Chapter10/WorkingWithEFCore/Northwind4SQLite.sql
Normal file
8722
vs4win/Chapter10/WorkingWithEFCore/Northwind4SQLite.sql
Normal file
File diff suppressed because it is too large
Load diff
26
vs4win/Chapter10/WorkingWithEFCore/Product.cs
Normal file
26
vs4win/Chapter10/WorkingWithEFCore/Product.cs
Normal 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!;
|
||||
}
|
||||
28
vs4win/Chapter10/WorkingWithEFCore/Program.Helpers.cs
Normal file
28
vs4win/Chapter10/WorkingWithEFCore/Program.Helpers.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
partial class Program
|
||||
{
|
||||
static void SectionTitle(string title)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
ForegroundColor = ConsoleColor.Yellow;
|
||||
WriteLine("*");
|
||||
WriteLine($"* {title}");
|
||||
WriteLine("*");
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
|
||||
static void Fail(string message)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
ForegroundColor = ConsoleColor.Red;
|
||||
WriteLine($"Fail > {message}");
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
|
||||
static void Info(string message)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
ForegroundColor = ConsoleColor.Cyan;
|
||||
WriteLine($"Info > {message}");
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
}
|
||||
107
vs4win/Chapter10/WorkingWithEFCore/Program.Modifications.cs
Normal file
107
vs4win/Chapter10/WorkingWithEFCore/Program.Modifications.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using Microsoft.EntityFrameworkCore.ChangeTracking; // EntityEntry<T>
|
||||
using Microsoft.EntityFrameworkCore.Storage; // IDbContextTransaction
|
||||
using Packt.Shared;
|
||||
|
||||
partial class Program
|
||||
{
|
||||
static void ListProducts(int? productIdToHighlight = null)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
if ((db.Products is null) || (db.Products.Count() == 0))
|
||||
{
|
||||
Fail("There are no products.");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLine("| {0,-3} | {1,-35} | {2,8} | {3,5} | {4} |",
|
||||
"Id", "Product Name", "Cost", "Stock", "Disc.");
|
||||
|
||||
foreach (Product p in db.Products)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
|
||||
if (productIdToHighlight == p.ProductId)
|
||||
{
|
||||
ForegroundColor = ConsoleColor.Green;
|
||||
}
|
||||
|
||||
WriteLine("| {0:000} | {1,-35} | {2,8:$#,##0.00} | {3,5} | {4} |",
|
||||
p.ProductId, p.ProductName, p.Cost, p.Stock, p.Discontinued);
|
||||
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static (int affected, int productId) AddProduct(
|
||||
int categoryId, string productName, decimal? price)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
Product p = new()
|
||||
{
|
||||
CategoryId = categoryId,
|
||||
ProductName = productName,
|
||||
Cost = price,
|
||||
Stock = 72
|
||||
};
|
||||
|
||||
// set product as added in change tracking
|
||||
EntityEntry<Product> entity = db.Products.Add(p);
|
||||
WriteLine($"State: {entity.State}, ProductId: {p.ProductId}");
|
||||
|
||||
// save tracked change to database
|
||||
int affected = db.SaveChanges();
|
||||
WriteLine($"State: {entity.State}, ProductId: {p.ProductId}");
|
||||
|
||||
return (affected, p.ProductId);
|
||||
}
|
||||
}
|
||||
|
||||
static (int affected, int productId) IncreaseProductPrice(
|
||||
string productNameStartsWith, decimal amount)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
// get first product whose name starts with name
|
||||
Product updateProduct = db.Products.First(
|
||||
p => p.ProductName.StartsWith(productNameStartsWith));
|
||||
|
||||
updateProduct.Cost += amount;
|
||||
|
||||
int affected = db.SaveChanges();
|
||||
|
||||
return (affected, updateProduct.ProductId);
|
||||
}
|
||||
}
|
||||
|
||||
static int DeleteProducts(string productNameStartsWith)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
using (IDbContextTransaction t = db.Database.BeginTransaction())
|
||||
{
|
||||
WriteLine("Transaction isolation level: {0}",
|
||||
arg0: t.GetDbTransaction().IsolationLevel);
|
||||
|
||||
IQueryable<Product>? products = db.Products?.Where(
|
||||
p => p.ProductName.StartsWith(productNameStartsWith));
|
||||
|
||||
if (products is null)
|
||||
{
|
||||
WriteLine("No products found to delete.");
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
db.Products.RemoveRange(products);
|
||||
}
|
||||
|
||||
int affected = db.SaveChanges();
|
||||
t.Commit();
|
||||
return affected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
199
vs4win/Chapter10/WorkingWithEFCore/Program.Queries.cs
Normal file
199
vs4win/Chapter10/WorkingWithEFCore/Program.Queries.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
using Microsoft.EntityFrameworkCore; // Include extension method
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking; // CollectionEntry
|
||||
using Packt.Shared; // Northwind, Category, Product
|
||||
|
||||
partial class Program
|
||||
{
|
||||
static void QueryingCategories()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Categories and how many products they have:");
|
||||
|
||||
// a query to get all categories and their related products
|
||||
IQueryable<Category>? categories;
|
||||
// = db.Categories;
|
||||
// .Include(c => c.Products);
|
||||
|
||||
db.ChangeTracker.LazyLoadingEnabled = false;
|
||||
|
||||
Write("Enable eager loading? (Y/N): ");
|
||||
bool eagerLoading = (ReadKey(intercept: true).Key == ConsoleKey.Y);
|
||||
bool explicitLoading = false;
|
||||
WriteLine();
|
||||
|
||||
if (eagerLoading)
|
||||
{
|
||||
categories = db.Categories?.Include(c => c.Products);
|
||||
}
|
||||
else
|
||||
{
|
||||
categories = db.Categories;
|
||||
Write("Enable explicit loading? (Y/N): ");
|
||||
explicitLoading = (ReadKey(intercept: true).Key == ConsoleKey.Y);
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
if (categories is null)
|
||||
{
|
||||
Fail("No categories found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// execute query and enumerate results
|
||||
foreach (Category c in categories)
|
||||
{
|
||||
if (explicitLoading)
|
||||
{
|
||||
Write($"Explicitly load products for {c.CategoryName}? (Y/N): ");
|
||||
ConsoleKeyInfo key = ReadKey(intercept: true);
|
||||
WriteLine();
|
||||
|
||||
if (key.Key == ConsoleKey.Y)
|
||||
{
|
||||
CollectionEntry<Category, Product> products =
|
||||
db.Entry(c).Collection(c2 => c2.Products);
|
||||
|
||||
if (!products.IsLoaded) products.Load();
|
||||
}
|
||||
}
|
||||
|
||||
WriteLine($"{c.CategoryName} has {c.Products.Count} products.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FilteredIncludes()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Products with a minimum number of units in stock.");
|
||||
|
||||
string? input;
|
||||
int stock;
|
||||
|
||||
do
|
||||
{
|
||||
Write("Enter a minimum for units in stock: ");
|
||||
input = ReadLine();
|
||||
} while (!int.TryParse(input, out stock));
|
||||
|
||||
IQueryable<Category>? categories = db.Categories?
|
||||
.Include(c => c.Products.Where(p => p.Stock >= stock));
|
||||
|
||||
if (categories is null)
|
||||
{
|
||||
Fail("No categories found.");
|
||||
return;
|
||||
}
|
||||
|
||||
Info($"ToQueryString: {categories.ToQueryString()}");
|
||||
|
||||
foreach (Category c in categories)
|
||||
{
|
||||
WriteLine($"{c.CategoryName} has {c.Products.Count} products with a minimum of {stock} units in stock.");
|
||||
foreach (Product p in c.Products)
|
||||
{
|
||||
WriteLine($" {p.ProductName} has {p.Stock} units in stock.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void QueryingProducts()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Products that cost more than a price, highest at top.");
|
||||
|
||||
string? input;
|
||||
decimal price;
|
||||
|
||||
do
|
||||
{
|
||||
Write("Enter a product price: ");
|
||||
input = ReadLine();
|
||||
} while (!decimal.TryParse(input, out price));
|
||||
|
||||
IQueryable<Product>? products = db.Products?
|
||||
.Where(product => product.Cost > price)
|
||||
.OrderByDescending(product => product.Cost);
|
||||
|
||||
if (products is null)
|
||||
{
|
||||
Fail("No products found.");
|
||||
return;
|
||||
}
|
||||
|
||||
Info($"ToQueryString: {products.ToQueryString()}");
|
||||
|
||||
foreach (Product p in products)
|
||||
{
|
||||
WriteLine(
|
||||
"{0}: {1} costs {2:$#,##0.00} and has {3} in stock.",
|
||||
p.ProductId, p.ProductName, p.Cost, p.Stock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void QueryingWithLike()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Pattern matching with LIKE.");
|
||||
|
||||
Write("Enter part of a product name: ");
|
||||
string? input = ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
Fail("You did not enter part of a product name.");
|
||||
return;
|
||||
}
|
||||
|
||||
IQueryable<Product>? products = db.Products?
|
||||
.Where(p => EF.Functions.Like(p.ProductName, $"%{input}%"));
|
||||
|
||||
if (products is null)
|
||||
{
|
||||
Fail("No products found.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Product p in products)
|
||||
{
|
||||
WriteLine("{0} has {1} units in stock. Discontinued? {2}",
|
||||
p.ProductName, p.Stock, p.Discontinued);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GetRandomProduct()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Get a random product.");
|
||||
|
||||
int? rowCount = db.Products?.Count();
|
||||
|
||||
if (rowCount == null)
|
||||
{
|
||||
Fail("Products table is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
Product? p = db.Products?.FirstOrDefault(
|
||||
p => p.ProductId == (int)(EF.Functions.Random() * rowCount));
|
||||
|
||||
if (p == null)
|
||||
{
|
||||
Fail("Product not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLine($"Random product: {p.ProductId} {p.ProductName}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
vs4win/Chapter10/WorkingWithEFCore/Program.cs
Normal file
48
vs4win/Chapter10/WorkingWithEFCore/Program.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using Packt.Shared;
|
||||
|
||||
/*
|
||||
Northwind db = new();
|
||||
WriteLine($"Provider: {db.Database.ProviderName}");
|
||||
*/
|
||||
|
||||
// QueryingCategories();
|
||||
|
||||
//FilteredIncludes();
|
||||
|
||||
//QueryingProducts();
|
||||
|
||||
//QueryingWithLike();
|
||||
|
||||
//GetRandomProduct();
|
||||
|
||||
var resultAdd = AddProduct(categoryId: 6,
|
||||
productName: "Bob's Burgers", price: 500M);
|
||||
|
||||
if (resultAdd.affected == 1)
|
||||
{
|
||||
WriteLine("Add product successful.");
|
||||
}
|
||||
|
||||
ListProducts(productIdToHighlight: resultAdd.productId);
|
||||
|
||||
var resultUpdate = IncreaseProductPrice(
|
||||
productNameStartsWith: "Bob", amount: 20M);
|
||||
|
||||
if (resultUpdate.affected == 1)
|
||||
{
|
||||
WriteLine("Increase product price successful.");
|
||||
}
|
||||
|
||||
ListProducts(productIdToHighlight: resultUpdate.productId);
|
||||
|
||||
WriteLine("About to delete all products whose name starts with Bob.");
|
||||
Write("Press Enter to continue: ");
|
||||
if (ReadKey(intercept: true).Key == ConsoleKey.Enter)
|
||||
{
|
||||
int deleted = DeleteProducts(productNameStartsWith: "Bob");
|
||||
WriteLine($"{deleted} product(s) were deleted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("Delete was cancelled.");
|
||||
}
|
||||
26
vs4win/Chapter10/WorkingWithEFCore/WorkingWithEFCore.csproj
Normal file
26
vs4win/Chapter10/WorkingWithEFCore/WorkingWithEFCore.csproj
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<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-*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0-*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0-*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Northwind.db">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
24
vscode/Chapter10/Ch10Ex02DataSerialization/Category.cs
Normal file
24
vscode/Chapter10/Ch10Ex02DataSerialization/Category.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
47
vscode/Chapter10/Ch10Ex02DataSerialization/Northwind.cs
Normal file
47
vscode/Chapter10/Ch10Ex02DataSerialization/Northwind.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
vscode/Chapter10/Ch10Ex02DataSerialization/Northwind.db
Normal file
BIN
vscode/Chapter10/Ch10Ex02DataSerialization/Northwind.db
Normal file
Binary file not shown.
8722
vscode/Chapter10/Ch10Ex02DataSerialization/Northwind4SQLite.sql
Normal file
8722
vscode/Chapter10/Ch10Ex02DataSerialization/Northwind4SQLite.sql
Normal file
File diff suppressed because it is too large
Load diff
26
vscode/Chapter10/Ch10Ex02DataSerialization/Product.cs
Normal file
26
vscode/Chapter10/Ch10Ex02DataSerialization/Product.cs
Normal 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!;
|
||||
}
|
||||
163
vscode/Chapter10/Ch10Ex02DataSerialization/Program.Functions.cs
Normal file
163
vscode/Chapter10/Ch10Ex02DataSerialization/Program.Functions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
23
vscode/Chapter10/Ch10Ex02DataSerialization/Program.cs
Normal file
23
vscode/Chapter10/Ch10Ex02DataSerialization/Program.cs
Normal 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}");
|
||||
}
|
||||
13
vscode/Chapter10/Chapter10.code-workspace
Normal file
13
vscode/Chapter10/Chapter10.code-workspace
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "WorkingWithEFCore"
|
||||
},
|
||||
{
|
||||
"path": "CoursesAndStudents"
|
||||
},
|
||||
{
|
||||
"path": "Ch10Ex02DataSerialization"
|
||||
}
|
||||
]
|
||||
}
|
||||
91
vscode/Chapter10/CoursesAndStudents/Academy.cs
Normal file
91
vscode/Chapter10/CoursesAndStudents/Academy.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using Microsoft.EntityFrameworkCore; // DbContext, DbSet<T>
|
||||
|
||||
namespace Packt.Shared;
|
||||
|
||||
public class Academy : DbContext
|
||||
{
|
||||
public DbSet<Student>? Students { get; set; }
|
||||
public DbSet<Course>? Courses { get; set; }
|
||||
|
||||
protected override void OnConfiguring(
|
||||
DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
string path = Path.Combine(
|
||||
Environment.CurrentDirectory, "Academy.db");
|
||||
|
||||
string connection = $"Filename={path}";
|
||||
// string connection = @"Data Source=.;Initial Catalog=Academy;Integrated Security=true;MultipleActiveResultSets=true;";
|
||||
|
||||
WriteLine($"Connection: {connection}");
|
||||
|
||||
optionsBuilder.UseSqlite(connection);
|
||||
// optionsBuilder.UseSqlServer(connection);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Fluent API validation rules
|
||||
modelBuilder.Entity<Student>()
|
||||
.Property(s => s.LastName).HasMaxLength(30).IsRequired();
|
||||
|
||||
// populate database with sample data
|
||||
Student alice = new()
|
||||
{
|
||||
StudentId = 1,
|
||||
FirstName = "Alice",
|
||||
LastName = "Jones"
|
||||
};
|
||||
|
||||
Student bob = new()
|
||||
{
|
||||
StudentId = 2,
|
||||
FirstName = "Bob",
|
||||
LastName = "Smith"
|
||||
};
|
||||
|
||||
Student cecilia = new()
|
||||
{
|
||||
StudentId = 3,
|
||||
FirstName = "Cecilia",
|
||||
LastName = "Ramirez"
|
||||
};
|
||||
|
||||
Course csharp = new()
|
||||
{
|
||||
CourseId = 1,
|
||||
Title = "C# 10 and .NET 6"
|
||||
};
|
||||
|
||||
Course webdev = new()
|
||||
{
|
||||
CourseId = 2,
|
||||
Title = "Web Development"
|
||||
};
|
||||
|
||||
Course python = new()
|
||||
{
|
||||
CourseId = 3,
|
||||
Title = "Python for Beginners"
|
||||
};
|
||||
|
||||
modelBuilder.Entity<Student>()
|
||||
.HasData(alice, bob, cecilia);
|
||||
|
||||
modelBuilder.Entity<Course>()
|
||||
.HasData(csharp, webdev, python);
|
||||
|
||||
modelBuilder.Entity<Course>()
|
||||
.HasMany(c => c.Students)
|
||||
.WithMany(s => s.Courses)
|
||||
.UsingEntity(e => e.HasData(
|
||||
// all students signed up for C# course
|
||||
new { CoursesCourseId = 1, StudentsStudentId = 1 },
|
||||
new { CoursesCourseId = 1, StudentsStudentId = 2 },
|
||||
new { CoursesCourseId = 1, StudentsStudentId = 3 },
|
||||
// only Bob signed up for Web Dev
|
||||
new { CoursesCourseId = 2, StudentsStudentId = 2 },
|
||||
// only Cecilia signed up for Python
|
||||
new { CoursesCourseId = 3, StudentsStudentId = 3 }
|
||||
));
|
||||
}
|
||||
}
|
||||
14
vscode/Chapter10/CoursesAndStudents/Course.cs
Normal file
14
vscode/Chapter10/CoursesAndStudents/Course.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Packt.Shared;
|
||||
|
||||
public class Course
|
||||
{
|
||||
public int CourseId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(60)]
|
||||
public string? Title { get; set; }
|
||||
|
||||
public ICollection<Student>? Students { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<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-*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0-*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
25
vscode/Chapter10/CoursesAndStudents/Program.cs
Normal file
25
vscode/Chapter10/CoursesAndStudents/Program.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Microsoft.EntityFrameworkCore; // for GenerateCreateScript()
|
||||
using Packt.Shared; // Academy
|
||||
|
||||
using (Academy a = new())
|
||||
{
|
||||
bool deleted = await a.Database.EnsureDeletedAsync();
|
||||
WriteLine($"Database deleted: {deleted}");
|
||||
|
||||
bool created = await a.Database.EnsureCreatedAsync();
|
||||
WriteLine($"Database created: {created}");
|
||||
|
||||
WriteLine("SQL script used to create database:");
|
||||
WriteLine(a.Database.GenerateCreateScript());
|
||||
|
||||
foreach (Student s in a.Students.Include(s => s.Courses))
|
||||
{
|
||||
WriteLine("{0} {1} attends the following {2} courses:",
|
||||
s.FirstName, s.LastName, s.Courses.Count);
|
||||
|
||||
foreach (Course c in s.Courses)
|
||||
{
|
||||
WriteLine($" {c.Title}");
|
||||
}
|
||||
}
|
||||
}
|
||||
9
vscode/Chapter10/CoursesAndStudents/Student.cs
Normal file
9
vscode/Chapter10/CoursesAndStudents/Student.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace Packt.Shared;
|
||||
|
||||
public class Student
|
||||
{
|
||||
public int StudentId { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public ICollection<Course>? Courses { get; set; }
|
||||
}
|
||||
29
vscode/Chapter10/WorkingWithEFCore/AutoGenModels/Category.cs
Normal file
29
vscode/Chapter10/WorkingWithEFCore/AutoGenModels/Category.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace WorkingWithEFCore.AutoGen
|
||||
{
|
||||
[Index("CategoryName", Name = "CategoryName")]
|
||||
public partial class Category
|
||||
{
|
||||
public Category()
|
||||
{
|
||||
Products = new HashSet<Product>();
|
||||
}
|
||||
|
||||
[Key]
|
||||
public long CategoryId { get; set; }
|
||||
[Column(TypeName = "nvarchar (15)")]
|
||||
public string CategoryName { get; set; } = null!;
|
||||
[Column(TypeName = "ntext")]
|
||||
public string? Description { get; set; }
|
||||
[Column(TypeName = "image")]
|
||||
public byte[]? Picture { get; set; }
|
||||
|
||||
[InverseProperty("Category")]
|
||||
public virtual ICollection<Product> Products { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace WorkingWithEFCore.AutoGen
|
||||
{
|
||||
public partial class Northwind : DbContext
|
||||
{
|
||||
public Northwind()
|
||||
{
|
||||
}
|
||||
|
||||
public Northwind(DbContextOptions<Northwind> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual DbSet<Category> Categories { get; set; } = null!;
|
||||
public virtual DbSet<Product> Products { get; set; } = null!;
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
{
|
||||
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
|
||||
optionsBuilder.UseSqlite("Filename=Northwind.db");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Category>(entity =>
|
||||
{
|
||||
entity.Property(e => e.CategoryId).ValueGeneratedNever();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Product>(entity =>
|
||||
{
|
||||
entity.Property(e => e.ProductId).ValueGeneratedNever();
|
||||
|
||||
entity.Property(e => e.Discontinued).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.ReorderLevel).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.UnitPrice).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.UnitsInStock).HasDefaultValueSql("0");
|
||||
|
||||
entity.Property(e => e.UnitsOnOrder).HasDefaultValueSql("0");
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
}
|
||||
|
||||
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
|
||||
}
|
||||
}
|
||||
41
vscode/Chapter10/WorkingWithEFCore/AutoGenModels/Product.cs
Normal file
41
vscode/Chapter10/WorkingWithEFCore/AutoGenModels/Product.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace WorkingWithEFCore.AutoGen
|
||||
{
|
||||
[Index("CategoryId", Name = "CategoriesProducts")]
|
||||
[Index("CategoryId", Name = "CategoryId")]
|
||||
[Index("ProductName", Name = "ProductName")]
|
||||
[Index("SupplierId", Name = "SupplierId")]
|
||||
[Index("SupplierId", Name = "SuppliersProducts")]
|
||||
public partial class Product
|
||||
{
|
||||
[Key]
|
||||
public long ProductId { get; set; }
|
||||
[Column(TypeName = "nvarchar (40)")]
|
||||
public string ProductName { get; set; } = null!;
|
||||
[Column(TypeName = "int")]
|
||||
public long? SupplierId { get; set; }
|
||||
[Column(TypeName = "int")]
|
||||
public long? CategoryId { get; set; }
|
||||
[Column(TypeName = "nvarchar (20)")]
|
||||
public string? QuantityPerUnit { get; set; }
|
||||
[Column(TypeName = "money")]
|
||||
public byte[]? UnitPrice { get; set; }
|
||||
[Column(TypeName = "smallint")]
|
||||
public long? UnitsInStock { get; set; }
|
||||
[Column(TypeName = "smallint")]
|
||||
public long? UnitsOnOrder { get; set; }
|
||||
[Column(TypeName = "smallint")]
|
||||
public long? ReorderLevel { get; set; }
|
||||
[Column(TypeName = "bit")]
|
||||
public byte[] Discontinued { get; set; } = null!;
|
||||
|
||||
[ForeignKey("CategoryId")]
|
||||
[InverseProperty("Products")]
|
||||
public virtual Category? Category { get; set; }
|
||||
}
|
||||
}
|
||||
24
vscode/Chapter10/WorkingWithEFCore/Category.cs
Normal file
24
vscode/Chapter10/WorkingWithEFCore/Category.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
57
vscode/Chapter10/WorkingWithEFCore/Northwind.cs
Normal file
57
vscode/Chapter10/WorkingWithEFCore/Northwind.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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);
|
||||
|
||||
optionsBuilder.LogTo(WriteLine, // Console
|
||||
new[] { RelationalEventId.CommandExecuting })
|
||||
.EnableSensitiveDataLogging();
|
||||
|
||||
optionsBuilder.UseLazyLoadingProxies();
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
// global filter to remove discontinued products
|
||||
modelBuilder.Entity<Product>()
|
||||
.HasQueryFilter(p => !p.Discontinued);
|
||||
}
|
||||
}
|
||||
BIN
vscode/Chapter10/WorkingWithEFCore/Northwind.db
Normal file
BIN
vscode/Chapter10/WorkingWithEFCore/Northwind.db
Normal file
Binary file not shown.
8722
vscode/Chapter10/WorkingWithEFCore/Northwind4SQLite.sql
Normal file
8722
vscode/Chapter10/WorkingWithEFCore/Northwind4SQLite.sql
Normal file
File diff suppressed because it is too large
Load diff
26
vscode/Chapter10/WorkingWithEFCore/Product.cs
Normal file
26
vscode/Chapter10/WorkingWithEFCore/Product.cs
Normal 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!;
|
||||
}
|
||||
28
vscode/Chapter10/WorkingWithEFCore/Program.Helpers.cs
Normal file
28
vscode/Chapter10/WorkingWithEFCore/Program.Helpers.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
partial class Program
|
||||
{
|
||||
static void SectionTitle(string title)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
ForegroundColor = ConsoleColor.Yellow;
|
||||
WriteLine("*");
|
||||
WriteLine($"* {title}");
|
||||
WriteLine("*");
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
|
||||
static void Fail(string message)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
ForegroundColor = ConsoleColor.Red;
|
||||
WriteLine($"Fail > {message}");
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
|
||||
static void Info(string message)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
ForegroundColor = ConsoleColor.Cyan;
|
||||
WriteLine($"Info > {message}");
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
}
|
||||
107
vscode/Chapter10/WorkingWithEFCore/Program.Modifications.cs
Normal file
107
vscode/Chapter10/WorkingWithEFCore/Program.Modifications.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using Microsoft.EntityFrameworkCore.ChangeTracking; // EntityEntry<T>
|
||||
using Microsoft.EntityFrameworkCore.Storage; // IDbContextTransaction
|
||||
using Packt.Shared;
|
||||
|
||||
partial class Program
|
||||
{
|
||||
static void ListProducts(int? productIdToHighlight = null)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
if ((db.Products is null) || (db.Products.Count() == 0))
|
||||
{
|
||||
Fail("There are no products.");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLine("| {0,-3} | {1,-35} | {2,8} | {3,5} | {4} |",
|
||||
"Id", "Product Name", "Cost", "Stock", "Disc.");
|
||||
|
||||
foreach (Product p in db.Products)
|
||||
{
|
||||
ConsoleColor previousColor = ForegroundColor;
|
||||
|
||||
if (productIdToHighlight == p.ProductId)
|
||||
{
|
||||
ForegroundColor = ConsoleColor.Green;
|
||||
}
|
||||
|
||||
WriteLine("| {0:000} | {1,-35} | {2,8:$#,##0.00} | {3,5} | {4} |",
|
||||
p.ProductId, p.ProductName, p.Cost, p.Stock, p.Discontinued);
|
||||
|
||||
ForegroundColor = previousColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static (int affected, int productId) AddProduct(
|
||||
int categoryId, string productName, decimal? price)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
Product p = new()
|
||||
{
|
||||
CategoryId = categoryId,
|
||||
ProductName = productName,
|
||||
Cost = price,
|
||||
Stock = 72
|
||||
};
|
||||
|
||||
// set product as added in change tracking
|
||||
EntityEntry<Product> entity = db.Products.Add(p);
|
||||
WriteLine($"State: {entity.State}, ProductId: {p.ProductId}");
|
||||
|
||||
// save tracked change to database
|
||||
int affected = db.SaveChanges();
|
||||
WriteLine($"State: {entity.State}, ProductId: {p.ProductId}");
|
||||
|
||||
return (affected, p.ProductId);
|
||||
}
|
||||
}
|
||||
|
||||
static (int affected, int productId) IncreaseProductPrice(
|
||||
string productNameStartsWith, decimal amount)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
// get first product whose name starts with name
|
||||
Product updateProduct = db.Products.First(
|
||||
p => p.ProductName.StartsWith(productNameStartsWith));
|
||||
|
||||
updateProduct.Cost += amount;
|
||||
|
||||
int affected = db.SaveChanges();
|
||||
|
||||
return (affected, updateProduct.ProductId);
|
||||
}
|
||||
}
|
||||
|
||||
static int DeleteProducts(string productNameStartsWith)
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
using (IDbContextTransaction t = db.Database.BeginTransaction())
|
||||
{
|
||||
WriteLine("Transaction isolation level: {0}",
|
||||
arg0: t.GetDbTransaction().IsolationLevel);
|
||||
|
||||
IQueryable<Product>? products = db.Products?.Where(
|
||||
p => p.ProductName.StartsWith(productNameStartsWith));
|
||||
|
||||
if (products is null)
|
||||
{
|
||||
WriteLine("No products found to delete.");
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
db.Products.RemoveRange(products);
|
||||
}
|
||||
|
||||
int affected = db.SaveChanges();
|
||||
t.Commit();
|
||||
return affected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
199
vscode/Chapter10/WorkingWithEFCore/Program.Queries.cs
Normal file
199
vscode/Chapter10/WorkingWithEFCore/Program.Queries.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
using Microsoft.EntityFrameworkCore; // Include extension method
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking; // CollectionEntry
|
||||
using Packt.Shared; // Northwind, Category, Product
|
||||
|
||||
partial class Program
|
||||
{
|
||||
static void QueryingCategories()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Categories and how many products they have:");
|
||||
|
||||
// a query to get all categories and their related products
|
||||
IQueryable<Category>? categories;
|
||||
// = db.Categories;
|
||||
// .Include(c => c.Products);
|
||||
|
||||
db.ChangeTracker.LazyLoadingEnabled = false;
|
||||
|
||||
Write("Enable eager loading? (Y/N): ");
|
||||
bool eagerLoading = (ReadKey(intercept: true).Key == ConsoleKey.Y);
|
||||
bool explicitLoading = false;
|
||||
WriteLine();
|
||||
|
||||
if (eagerLoading)
|
||||
{
|
||||
categories = db.Categories?.Include(c => c.Products);
|
||||
}
|
||||
else
|
||||
{
|
||||
categories = db.Categories;
|
||||
Write("Enable explicit loading? (Y/N): ");
|
||||
explicitLoading = (ReadKey(intercept: true).Key == ConsoleKey.Y);
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
if (categories is null)
|
||||
{
|
||||
Fail("No categories found.");
|
||||
return;
|
||||
}
|
||||
|
||||
// execute query and enumerate results
|
||||
foreach (Category c in categories)
|
||||
{
|
||||
if (explicitLoading)
|
||||
{
|
||||
Write($"Explicitly load products for {c.CategoryName}? (Y/N): ");
|
||||
ConsoleKeyInfo key = ReadKey(intercept: true);
|
||||
WriteLine();
|
||||
|
||||
if (key.Key == ConsoleKey.Y)
|
||||
{
|
||||
CollectionEntry<Category, Product> products =
|
||||
db.Entry(c).Collection(c2 => c2.Products);
|
||||
|
||||
if (!products.IsLoaded) products.Load();
|
||||
}
|
||||
}
|
||||
|
||||
WriteLine($"{c.CategoryName} has {c.Products.Count} products.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FilteredIncludes()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Products with a minimum number of units in stock.");
|
||||
|
||||
string? input;
|
||||
int stock;
|
||||
|
||||
do
|
||||
{
|
||||
Write("Enter a minimum for units in stock: ");
|
||||
input = ReadLine();
|
||||
} while (!int.TryParse(input, out stock));
|
||||
|
||||
IQueryable<Category>? categories = db.Categories?
|
||||
.Include(c => c.Products.Where(p => p.Stock >= stock));
|
||||
|
||||
if (categories is null)
|
||||
{
|
||||
Fail("No categories found.");
|
||||
return;
|
||||
}
|
||||
|
||||
Info($"ToQueryString: {categories.ToQueryString()}");
|
||||
|
||||
foreach (Category c in categories)
|
||||
{
|
||||
WriteLine($"{c.CategoryName} has {c.Products.Count} products with a minimum of {stock} units in stock.");
|
||||
foreach (Product p in c.Products)
|
||||
{
|
||||
WriteLine($" {p.ProductName} has {p.Stock} units in stock.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void QueryingProducts()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Products that cost more than a price, highest at top.");
|
||||
|
||||
string? input;
|
||||
decimal price;
|
||||
|
||||
do
|
||||
{
|
||||
Write("Enter a product price: ");
|
||||
input = ReadLine();
|
||||
} while (!decimal.TryParse(input, out price));
|
||||
|
||||
IQueryable<Product>? products = db.Products?
|
||||
.Where(product => product.Cost > price)
|
||||
.OrderByDescending(product => product.Cost);
|
||||
|
||||
if (products is null)
|
||||
{
|
||||
Fail("No products found.");
|
||||
return;
|
||||
}
|
||||
|
||||
Info($"ToQueryString: {products.ToQueryString()}");
|
||||
|
||||
foreach (Product p in products)
|
||||
{
|
||||
WriteLine(
|
||||
"{0}: {1} costs {2:$#,##0.00} and has {3} in stock.",
|
||||
p.ProductId, p.ProductName, p.Cost, p.Stock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void QueryingWithLike()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Pattern matching with LIKE.");
|
||||
|
||||
Write("Enter part of a product name: ");
|
||||
string? input = ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
Fail("You did not enter part of a product name.");
|
||||
return;
|
||||
}
|
||||
|
||||
IQueryable<Product>? products = db.Products?
|
||||
.Where(p => EF.Functions.Like(p.ProductName, $"%{input}%"));
|
||||
|
||||
if (products is null)
|
||||
{
|
||||
Fail("No products found.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Product p in products)
|
||||
{
|
||||
WriteLine("{0} has {1} units in stock. Discontinued? {2}",
|
||||
p.ProductName, p.Stock, p.Discontinued);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GetRandomProduct()
|
||||
{
|
||||
using (Northwind db = new())
|
||||
{
|
||||
SectionTitle("Get a random product.");
|
||||
|
||||
int? rowCount = db.Products?.Count();
|
||||
|
||||
if (rowCount == null)
|
||||
{
|
||||
Fail("Products table is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
Product? p = db.Products?.FirstOrDefault(
|
||||
p => p.ProductId == (int)(EF.Functions.Random() * rowCount));
|
||||
|
||||
if (p == null)
|
||||
{
|
||||
Fail("Product not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLine($"Random product: {p.ProductId} {p.ProductName}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
vscode/Chapter10/WorkingWithEFCore/Program.cs
Normal file
48
vscode/Chapter10/WorkingWithEFCore/Program.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using Packt.Shared;
|
||||
|
||||
/*
|
||||
Northwind db = new();
|
||||
WriteLine($"Provider: {db.Database.ProviderName}");
|
||||
*/
|
||||
|
||||
// QueryingCategories();
|
||||
|
||||
//FilteredIncludes();
|
||||
|
||||
//QueryingProducts();
|
||||
|
||||
//QueryingWithLike();
|
||||
|
||||
//GetRandomProduct();
|
||||
|
||||
var resultAdd = AddProduct(categoryId: 6,
|
||||
productName: "Bob's Burgers", price: 500M);
|
||||
|
||||
if (resultAdd.affected == 1)
|
||||
{
|
||||
WriteLine("Add product successful.");
|
||||
}
|
||||
|
||||
ListProducts(productIdToHighlight: resultAdd.productId);
|
||||
|
||||
var resultUpdate = IncreaseProductPrice(
|
||||
productNameStartsWith: "Bob", amount: 20M);
|
||||
|
||||
if (resultUpdate.affected == 1)
|
||||
{
|
||||
WriteLine("Increase product price successful.");
|
||||
}
|
||||
|
||||
ListProducts(productIdToHighlight: resultUpdate.productId);
|
||||
|
||||
WriteLine("About to delete all products whose name starts with Bob.");
|
||||
Write("Press Enter to continue: ");
|
||||
if (ReadKey(intercept: true).Key == ConsoleKey.Enter)
|
||||
{
|
||||
int deleted = DeleteProducts(productNameStartsWith: "Bob");
|
||||
WriteLine($"{deleted} product(s) were deleted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine("Delete was cancelled.");
|
||||
}
|
||||
26
vscode/Chapter10/WorkingWithEFCore/WorkingWithEFCore.csproj
Normal file
26
vscode/Chapter10/WorkingWithEFCore/WorkingWithEFCore.csproj
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<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-*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0-*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.0-*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Northwind.db">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in a new issue