Skip to content

MrHallKSC/DatabaseExampleWPF

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Library Management System - Database Exemplar for A-Level NEA Projects

Overview

This exemplar WPF application demonstrates fundamental database programming concepts required for AQA 7517 A-Level Computer Science NEA projects. It illustrates how to create a database-driven application using WPF, C#, and SQLite, showing key techniques that students should understand when developing their own projects.

Important: This exemplar is designed to teach concepts, not to be used as a starting point for your NEA project. Your NEA must be your own original work, addressing a problem you have identified and designed yourself. You WILL get zero marks if you copy from this project.

Library Management System Screenshot

Note on Authorship: This entire project, including all code, documentation, and this README, was generated by Claude AI (Anthropic) as an educational resource for A-Level Computer Science teachers and students. It is intended to demonstrate database concepts and techniques, not to serve as a template for student NEA projects.

Additional Documentation

This project includes comprehensive documentation to help you understand the concepts:

Document Description
SQL-Explained.md Complete guide to SQL and SQLite - covers connecting to databases, CRUD operations, JOIN queries, parameterised queries, NULL handling, and common mistakes to avoid. Includes DB Browser for SQLite setup instructions.
WPF-Guide.md Introduction to WPF (Windows Presentation Foundation) - covers XAML vs code-behind, event-driven programming, data binding, common controls, layout with Grid/StackPanel, and building your first window.
NEA-Guide.md Step-by-step guide for planning and developing your own AQA 7517 NEA database project - covers analysis, design, prototyping, development, and testing stages. Uses the Library System as a worked example throughout.

Project Structure

Core Components

DatabaseExampleWPF/
│
├── Models/                              # Entity and View Model Classes
│   ├── Book.cs                          # Book entity with validation
│   ├── Author.cs                        # Author entity
│   ├── Member.cs                        # Member entity with email validation
│   ├── Loan.cs                          # Loan entity with nullable ReturnDate
│   └── LoanWithDetails.cs               # View model for multi-table JOINs
│
├── Database/                            # Data Access Layer
│   └── DatabaseHelper.cs                # All database operations (static methods)
│
├── Windows/                             # User Interface (WPF)
│   ├── MainWindow.xaml/.xaml.cs        # Navigation hub
│   ├── BooksWindow.xaml/.xaml.cs       # Book management + many-to-many demo
│   ├── AuthorsWindow.xaml/.xaml.cs     # Author management
│   ├── MembersWindow.xaml/.xaml.cs     # Member management
│   ├── LoansWindow.xaml/.xaml.cs       # Loan management + foreign keys
│   └── SearchLoansWindow.xaml/.xaml.cs # Advanced search with JOINs
│
└── LibraryDatabase.db                   # SQLite database (created at runtime)

Entity Classes Explained

Book.cs

  • Properties: BookID (PK), Title, ISBN, YearPublished
  • Collection: List<Author> Authors - populated from JOIN queries
  • Validation: Title required, year between 1000 and current year + 1
  • Key Methods: IsValid(), GetValidationErrors(), ToString()

Author.cs

  • Properties: AuthorID (PK), FirstName, LastName
  • Computed Property: FullName (FirstName + LastName)
  • Collection: List<Book> Books - populated from JOIN queries
  • Validation: Both names required

Member.cs

  • Properties: MemberID (PK), FirstName, LastName, Email, MemberType
  • Computed Property: FullName
  • Collection: List<Loan> Loans - populated from JOIN queries
  • Validation: Email format validation using regex, MemberType must be "Student", "Teacher", or "Staff"
  • Static Method: GetMemberTypes() - returns valid member type list

Loan.cs

  • Properties: LoanID (PK), BookID (FK), MemberID (FK), LoanDate, DueDate, ReturnDate (nullable)
  • Computed Properties:
    • IsReturned - checks if ReturnDate has value
    • IsOverdue - checks if not returned and past due date
    • DaysUntilDue - calculates days remaining
  • Demonstrates: Nullable DateTime (DateTime?), business logic in properties

LoanWithDetails.cs (View Model)

  • Purpose: Flattened data from 3-table JOIN (Loans + Books + Members)
  • Loan Data: LoanID, BookID, MemberID, dates
  • Book Data: BookTitle, BookISBN
  • Member Data: MemberFirstName, MemberLastName, MemberEmail, MemberType
  • Computed Properties: MemberFullName, Status, StatusMessage, IsOverdue, DaysUntilDue
  • Key Concept: Separates database structure from display needs

DatabaseHelper.cs Structure

Static class containing all database operations:

Database Creation

  • CreateTables() - Creates all 5 tables with constraints
  • DatabaseExists() - Checks if .db file exists
  • TableExists(tableName) - Checks specific table

Book CRUD

  • InsertBook(book) → returns BookID
  • UpdateBook(book) → returns bool
  • DeleteBook(bookId) → returns bool
  • GetAllBooks() → returns List<Book>
  • GetBookById(bookId) → returns Book or null

Author CRUD

  • InsertAuthor(author) → returns AuthorID
  • UpdateAuthor(author) → returns bool
  • DeleteAuthor(authorId) → returns bool
  • GetAllAuthors() → returns List<Author>
  • GetAuthorById(authorId) → returns Author or null

Member CRUD

  • InsertMember(member) → returns MemberID
  • UpdateMember(member) → returns bool
  • DeleteMember(memberId) → returns bool
  • GetAllMembers() → returns List<Member>
  • GetMemberById(memberId) → returns Member or null

Loan CRUD

  • InsertLoan(loan) → returns LoanID
  • UpdateLoan(loan) → returns bool
  • ReturnBook(loanId) → sets ReturnDate to today
  • DeleteLoan(loanId) → returns bool
  • GetAllLoans() → returns List<Loan> (basic)
  • GetAllLoansWithDetails() → returns List<LoanWithDetails> (with JOINs)
  • SearchLoansWithDetails(searchTerm) → searches with LIKE operator
  • GetLoansForMember(memberId) → member's loan history
  • GetActiveLoans() → unreturned loans only

Many-to-Many Operations

  • AddBookAuthor(bookId, authorId) → creates relationship
  • RemoveBookAuthor(bookId, authorId) → deletes relationship
  • GetAuthorsForBook(bookId) → returns List<Author>
  • GetBooksForAuthor(authorId) → returns List<Book>

Sample Data

  • PopulateSampleData() → creates 10 of each entity with realistic data

Design Rationale and Architecture

Why This Design?

This project demonstrates a three-tier architecture pattern commonly used in professional database applications:

┌─────────────────────────────────────┐
│     Presentation Layer (UI)          │  ← WPF Windows (.xaml/.xaml.cs)
│  - MainWindow, BooksWindow, etc.     │     • Displays data to user
│  - Handles user interaction          │     • Captures user input
│  - Data binding to grids/forms       │     • Event handlers for buttons
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│     Business Logic Layer             │  ← Model Classes (.cs)
│  - Book, Author, Member, Loan        │     • Represents entities
│  - Validation logic (IsValid())      │     • Encapsulates business rules
│  - Computed properties               │     • Data validation
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│     Data Access Layer                │  ← DatabaseHelper.cs
│  - All SQL queries                   │     • Parameterised queries
│  - Database connections              │     • CRUD operations
│  - Transaction management            │     • JOIN queries
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│     Database (SQLite)                │  ← LibraryDatabase.db
│  - 5 tables with relationships       │     • Books, Authors, BookAuthors
│  - Constraints and foreign keys      │     • Members, Loans
└─────────────────────────────────────┘

Key Design Decisions

1. Static DatabaseHelper Class (Why?)

Decision: All database methods are static in one class rather than instance-based or scattered.

Rationale:

  • Simplicity: No need to create DatabaseHelper objects
  • Single Point of Access: All database operations in one place
  • Stateless: Each method call is independent
  • Easy to Call: DatabaseHelper.GetAllBooks() from anywhere
  • Consistent Patterns: All methods follow same structure

Alternative Considered: Instance-based repository pattern with interfaces Why Not Chosen: Too complex for A-Level exemplar, hides concepts

2. Separate Entity and View Model Classes (Why?)

Decision: Created LoanWithDetails separate from Loan class.

Rationale:

  • Single Responsibility: Loan represents database structure, LoanWithDetails represents display needs
  • Clarity: Makes JOIN queries explicit and understandable
  • Flexibility: Can create multiple view models from same entities
  • Performance: One JOIN query vs multiple SELECT queries
  • Maintainability: Changes to display don't affect entity classes

Example:

// Entity - matches database table
public class Loan
{
    public int BookID { get; set; }      // Just the foreign key
    public int MemberID { get; set; }    // Just the foreign key
}

// View Model - optimised for display
public class LoanWithDetails
{
    public int BookID { get; set; }
    public string BookTitle { get; set; }      // From JOIN
    public string MemberFullName { get; set; } // From JOIN
    public string Status { get; set; }         // Computed
}

3. Collection Properties in Entity Classes (Why?)

Decision: Book has List<Author> Authors, Author has List<Book> Books, Member has List<Loan> Loans.

Rationale:

  • Demonstrates Relationships: Makes one-to-many and many-to-many visible in code
  • Navigation: Can access book.Authors after loading
  • Educational: Shows how objects relate beyond foreign keys
  • Not Stored: These are populated by JOIN queries, not database fields
  • Initialised in Constructor: Prevents null reference errors

Important Note: These collections are NOT automatically loaded - they must be populated explicitly:

Book book = DatabaseHelper.GetBookById(5);
book.Authors = DatabaseHelper.GetAuthorsForBook(book.BookID);

4. Computed Properties for Business Logic (Why?)

Decision: Properties like IsOverdue, FullName, Status are calculated, not stored.

Rationale:

  • Dynamic: Values change based on current date without database updates
  • No Redundancy: Don't store data that can be derived
  • Always Accurate: Calculated fresh each time
  • Encapsulation: Business logic stays in the model class

Examples:

// In Loan.cs
public bool IsOverdue
{
    get
    {
        if (IsReturned) return false;
        return DateTime.Today > DueDate;
    }
}

// In Member.cs
public string FullName
{
    get { return $"{FirstName} {LastName}"; }
}

5. Parameterised Queries Throughout (Why?)

Decision: Every SQL query uses parameters (@ParameterName) rather than string concatenation.

Rationale:

  • Security: Prevents SQL injection attacks (critical!)
  • Reliability: Handles special characters (apostrophes, quotes) correctly
  • Best Practice: Industry standard, AQA specification requirement
  • Type Safety: Parameters are typed, reducing errors

Example:

// VULNERABLE - never do this
string sql = $"SELECT * FROM Books WHERE Title = '{title}'";

// SECURE - always do this
string sql = "SELECT * FROM Books WHERE Title = @Title";
cmd.Parameters.AddWithValue("@Title", title);

6. Multiple Windows Instead of Single Window with Tabs (Why?)

Decision: Separate window class for each major function (Books, Authors, Members, Loans, Search).

Rationale:

  • Modularity: Each window is self-contained and independent
  • Easier Development: Can work on one window without affecting others
  • Clear Purpose: Each window has focused, single responsibility
  • User Flexibility: Can open multiple windows side-by-side
  • Code Organisation: Related functionality grouped in one file

Alternatives Considered:

  • TabControl in single window - less flexible, complex XAML
  • UserControls swapped in content area - more complex navigation logic

7. Validation at Multiple Levels (Why?)

Decision: Validate in UI, Model, and Database.

Rationale:

  • Defence in Depth: Multiple layers catch different types of errors
  • User Experience: UI validation gives immediate feedback
  • Data Integrity: Model validation enforces business rules
  • Last Resort: Database constraints prevent corrupt data

Three-Level Pattern:

// 1. UI Validation (immediate feedback)
if (string.IsNullOrWhiteSpace(txtTitle.Text))
{
    MessageBox.Show("Title is required");
    return;
}

// 2. Model Validation (business rules)
Book book = new Book { Title = txtTitle.Text };
if (!book.IsValid())
{
    MessageBox.Show(book.GetValidationErrors());
    return;
}

// 3. Database Validation (last line of defence)
CREATE TABLE Books (
    Title TEXT NOT NULL,  -- Database enforces this
    ...
)

Database Design Structure

Entity Relationship Diagram (ERD)

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   Authors   │         │  BookAuthors │         │    Books    │
├─────────────┤         ├──────────────┤         ├─────────────┤
│ AuthorID PK │◄────────┤ AuthorID FK  │         │ BookID PK   │
│ FirstName   │         │ BookID FK    │────────►│ Title       │
│ LastName    │         └──────────────┘         │ ISBN        │
└─────────────┘         (Junction Table)         │ YearPub     │
                                                  └──────┬──────┘
                                                         │
                                                         │ 1:M
                                                         │
┌─────────────┐                                  ┌──────▼──────┐
│   Members   │                                  │    Loans    │
├─────────────┤                                  ├─────────────┤
│ MemberID PK │◄─────────────────────────────────┤ LoanID PK   │
│ FirstName   │ 1:M                              │ BookID FK   │
│ LastName    │                                  │ MemberID FK │
│ Email       │                                  │ LoanDate    │
│ MemberType  │                                  │ DueDate     │
└─────────────┘                                  │ ReturnDate? │
                                                 └─────────────┘

Legend: PK = Primary Key, FK = Foreign Key, ? = Nullable

Table Relationships Explained

One-to-Many Relationships

  • Member → Loans: One member can have many loans (over time)
  • Book → Loans: One book can be loaned multiple times (not simultaneously)

Many-to-Many Relationship

  • Books ↔ Authors:
    • One book can have multiple authors (e.g., "Good Omens" by Pratchett and Gaiman)
    • One author can write multiple books
    • Implemented via BookAuthors junction table

Database Schema

Books Table

CREATE TABLE Books (
    BookID INTEGER PRIMARY KEY AUTOINCREMENT,
    Title TEXT NOT NULL,
    ISBN TEXT,
    YearPublished INTEGER NOT NULL
)

Authors Table

CREATE TABLE Authors (
    AuthorID INTEGER PRIMARY KEY AUTOINCREMENT,
    FirstName TEXT NOT NULL,
    LastName TEXT NOT NULL
)

BookAuthors Junction Table

CREATE TABLE BookAuthors (
    BookID INTEGER NOT NULL,
    AuthorID INTEGER NOT NULL,
    PRIMARY KEY (BookID, AuthorID),           -- Composite primary key
    FOREIGN KEY (BookID) REFERENCES Books(BookID) ON DELETE CASCADE,
    FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID) ON DELETE CASCADE
)

Purpose: Implements many-to-many relationship. Composite PK prevents duplicate pairings.

Members Table

CREATE TABLE Members (
    MemberID INTEGER PRIMARY KEY AUTOINCREMENT,
    FirstName TEXT NOT NULL,
    LastName TEXT NOT NULL,
    Email TEXT NOT NULL,
    MemberType TEXT NOT NULL               -- Student, Teacher, or Staff
)

Loans Table

CREATE TABLE Loans (
    LoanID INTEGER PRIMARY KEY AUTOINCREMENT,
    BookID INTEGER NOT NULL,
    MemberID INTEGER NOT NULL,
    LoanDate TEXT NOT NULL,                -- ISO 8601 format: YYYY-MM-DD
    DueDate TEXT NOT NULL,
    ReturnDate TEXT,                       -- Nullable - NULL if not returned
    FOREIGN KEY (BookID) REFERENCES Books(BookID),
    FOREIGN KEY (MemberID) REFERENCES Members(MemberID)
)

Normalisation Applied

First Normal Form (1NF): All fields contain atomic (single) values

✓ Good: ISBN = "978-0547928227"
✗ Bad: Authors = "Pratchett, Gaiman"  (multiple values in one field)

Second Normal Form (2NF): No partial dependencies

✓ BookAuthors has composite key (BookID, AuthorID)
✓ No attributes depend on only part of the key

Third Normal Form (3NF): No transitive dependencies

✓ MemberType stored directly in Members (not derived)
✓ Status in LoanWithDetails is computed, not stored

Understanding AQA 7517 Technical Skills

Skills from Group B (Table 1, Section 4.14.3.4.1)

Data Structures:

  • Records/Classes: Five entity classes with properties and methods
  • Simple OOP: Encapsulation, constructors, validation methods
  • Lists/Collections: List<T> for managing collections

Algorithms:

  • User-defined algorithms: IsValid(), IsOverdue logic, email validation
  • File handling: SQLite database file operations
  • Linear search: Finding records via SQL WHERE clauses

Database:

  • Relational design: Normalised tables with relationships
  • SQL queries: SELECT, INSERT, UPDATE, DELETE
  • JOINs: INNER JOIN across multiple tables

Skills from Group A (Table 1, Section 4.14.3.4.1)

Data Structures:

  • Complex relational models: 5 interrelated tables
  • Many-to-many relationships: BookAuthors junction table
  • View models: LoanWithDetails for flattened JOIN data

Algorithms:

  • Complex queries: 3-table JOINs
  • Parameterised queries: SQL injection prevention
  • Search algorithms: LIKE operator with wildcards

Core Database Concepts Explained

1. Parameterised Queries (SQL Injection Prevention)

The Critical Security Rule

❌ NEVER do this (vulnerable to SQL injection):

string sql = "SELECT * FROM Books WHERE Title = '" + userInput + "'";

If user enters: ' OR '1'='1 → returns all books! If user enters: '; DROP TABLE Books; -- → deletes your table!

✅ ALWAYS do this (secure):

string sql = "SELECT * FROM Books WHERE Title = @Title";
cmd.Parameters.AddWithValue("@Title", userInput);

How it works: Parameters are escaped and type-checked automatically. SQL engine treats them as pure data, never as executable code.

In this project: Every single SQL query uses parameters. Check DatabaseHelper.cs - you'll see @ParameterName throughout.

2. Many-to-Many Relationships via Junction Tables

The Problem: A book can have multiple authors, and an author can write multiple books. You cannot represent this with foreign keys alone.

The Solution: Create a junction table (also called: link table, join table, associative entity).

How it works:

  1. Without Junction Table (WRONG):
Books table:
BookID | Title          | AuthorIDs
1      | Good Omens     | "8,9"    ← Multiple values! Violates 1NF!
  1. With Junction Table (CORRECT):
Books:
BookID | Title
9      | Good Omens

Authors:
AuthorID | Name
8        | Terry Pratchett
9        | Neil Gaiman

BookAuthors:
BookID | AuthorID
9      | 8           ← Pratchett wrote Good Omens
9      | 9           ← Gaiman wrote Good Omens

In code:

// Link Good Omens to both authors
DatabaseHelper.AddBookAuthor(bookId: 9, authorId: 8);  // Pratchett
DatabaseHelper.AddBookAuthor(bookId: 9, authorId: 9);  // Gaiman

// Retrieve authors for a book
List<Author> authors = DatabaseHelper.GetAuthorsForBook(bookId: 9);
// Returns: [Terry Pratchett, Neil Gaiman]

3. Foreign Keys and Referential Integrity

Purpose: Ensure data consistency across related tables.

Example:

CREATE TABLE Loans (
    BookID INTEGER NOT NULL,
    FOREIGN KEY (BookID) REFERENCES Books(BookID)
)

This prevents:

  • Creating a loan for BookID = 999 when no such book exists
  • Orphaned records (loans pointing to deleted books)

CASCADE DELETE:

FOREIGN KEY (BookID) REFERENCES Books(BookID) ON DELETE CASCADE

Effect: Deleting a book automatically deletes all its BookAuthors entries.

4. Nullable Types in C#

The Problem: Some fields can be empty/unknown. In Loans, ReturnDate is empty until the book is returned.

The Solution: Nullable value types using ?:

public DateTime? ReturnDate { get; set; }  // Note the ?

Working with nullable types:

// Check if it has a value
if (loan.ReturnDate.HasValue)
{
    DateTime date = loan.ReturnDate.Value;  // Extract the value
    Console.WriteLine($"Returned on {date:dd/MM/yyyy}");
}
else
{
    Console.WriteLine("Not yet returned");
}

In database: NULL is used:

if (loan.ReturnDate.HasValue)
    cmd.Parameters.AddWithValue("@ReturnDate", loan.ReturnDate.Value.ToString("yyyy-MM-dd"));
else
    cmd.Parameters.AddWithValue("@ReturnDate", DBNull.Value);

5. JOIN Queries

Purpose: Combine data from multiple related tables in one query.

Without JOIN (inefficient):

// 3 separate queries for each loan
List<Loan> loans = GetAllLoans();
foreach (Loan loan in loans)
{
    Book book = GetBookById(loan.BookID);       // 1 query per loan
    Member member = GetMemberById(loan.MemberID); // 1 query per loan
}
// Total: 1 + (n × 2) queries for n loans

With JOIN (efficient):

SELECT 
    l.LoanID, l.LoanDate, l.DueDate,
    b.Title AS BookTitle,
    m.FirstName, m.LastName
FROM Loans l
INNER JOIN Books b ON l.BookID = b.BookID
INNER JOIN Members m ON l.MemberID = m.MemberID

Total: 1 query regardless of number of loans!

INNER JOIN: Only returns rows where there's a match in both tables.

In this project: GetAllLoansWithDetails() uses 3-table JOIN to populate LoanWithDetails objects.

6. CRUD Operations

Create, Read, Update, Delete - the four fundamental database operations.

Every entity in this project has complete CRUD:

// CREATE
int newId = DatabaseHelper.InsertBook(book);

// READ
List<Book> all = DatabaseHelper.GetAllBooks();
Book one = DatabaseHelper.GetBookById(5);

// UPDATE
book.Title = "New Title";
DatabaseHelper.UpdateBook(book);

// DELETE
DatabaseHelper.DeleteBook(bookId);

Pattern:

  • Insert returns new ID (or -1 for failure)
  • Update/Delete return bool (success/failure)
  • Get methods return objects or collections

7. Validation Patterns

Three-Layer Validation:

Layer 1: UI (Immediate Feedback)

// In BtnSave_Click
if (string.IsNullOrWhiteSpace(txtTitle.Text))
{
    MessageBox.Show("Title is required");
    txtTitle.Focus();  // Take user to the problem
    return;
}

Layer 2: Model (Business Rules)

// In Book.cs
public bool IsValid()
{
    if (string.IsNullOrWhiteSpace(Title)) return false;
    if (YearPublished < 1000 || YearPublished > DateTime.Now.Year + 1) return false;
    return true;
}

Layer 3: Database (Final Enforcement)

CREATE TABLE Books (
    Title TEXT NOT NULL,           -- Database enforces NOT NULL
    YearPublished INTEGER NOT NULL
)

Why all three?

  • UI: Fast feedback, good UX
  • Model: Reusable logic, consistent rules
  • Database: Absolute guarantee, protects against bugs

WPF (Windows Presentation Foundation) Concepts

What is WPF?

WPF is Microsoft's framework for building Windows desktop applications. It separates:

  • XAML (.xaml files): UI layout and design (XML-based)
  • C# (.xaml.cs files): Application logic and event handlers

Key Principle: Separation of Concerns - UI design is separate from behaviour.

Core WPF Principles

1. XAML vs Code-Behind

XAML (Declarative UI):

<Button x:Name="btnSave" 
        Content="Save Book"
        Click="BtnSave_Click"
        Width="100" 
        Height="30"
        Background="Green"/>

Code-Behind (Event Logic):

private void BtnSave_Click(object sender, RoutedEventArgs e)
{
    // What happens when button is clicked
    SaveBook();
}

Benefits:

  • Designers can modify XAML without touching code
  • Developers can change logic without breaking layout
  • Clear separation between "what it looks like" and "what it does"

2. Event-Driven Programming

WPF applications don't run linearly from start to finish. Instead:

  1. Application starts → displays UI
  2. Waits for user action (idle)
  3. User clicks button → event fires
  4. Event handler executes
  5. Returns to waiting

Common Events in This Project:

// Button clicked
private void BtnSave_Click(object sender, RoutedEventArgs e) { }

// Selection changed in DataGrid
private void DgBooks_SelectionChanged(object sender, SelectionChangedEventArgs e) { }

// CheckBox checked/unchecked
private void ChkReturned_Checked(object sender, RoutedEventArgs e) { }

// Window closing
private void Window_Closing(object sender, CancelEventArgs e) { }

Parameters:

  • sender: The control that raised the event (e.g., which button was clicked)
  • e: Event arguments with additional information

3. Data Binding

Without Data Binding (manual, tedious):

foreach (Book book in books)
{
    DataGridRow row = new DataGridRow();
    // ... create cells manually
    // ... set cell values
    dgBooks.Rows.Add(row);
}
// Must repeat for every update!

With Data Binding (automatic, elegant):

// Set once
dgBooks.ItemsSource = books;

// DataGrid automatically displays all book data!

In XAML, specify which properties to display:

<DataGrid ItemsSource="{Binding}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Title" Binding="{Binding Title}"/>
        <DataGridTextColumn Header="Year" Binding="{Binding YearPublished}"/>
    </DataGrid.Columns>
</DataGrid>

Key Concept: Binding connects UI element to object property. Changes sync automatically.

4. Master-Detail Pattern

Pattern: Selecting item in list shows related details/data.

Implementation in BooksWindow:

private void DgBooks_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (dgBooks.SelectedItem != null)
    {
        Book book = (Book)dgBooks.SelectedItem;
        
        // Populate form with book data
        txtTitle.Text = book.Title;
        txtISBN.Text = book.ISBN;
        txtYear.Text = book.YearPublished.ToString();
        
        // Show related authors
        List<Author> authors = DatabaseHelper.GetAuthorsForBook(book.BookID);
        lstAuthors.ItemsSource = authors;
    }
}

User Experience:

  1. User clicks book in DataGrid
  2. SelectionChanged event fires
  3. Form populates with book details
  4. Related authors list populates
  5. User can now edit or view relationships

Multiple Windows Architecture

Why Multiple Windows?

This Project's Approach: Dedicated window for each major function.

Alternatives Considered:

Approach Pros Cons Why Not Chosen
Single Window + Tabs All in one place Complex XAML, harder navigation Less modular
Single Window + UserControls Flexible swapping Complex state management Over-engineered for exemplar
Multiple Windows Focused, modular, flexible More memory if many open Best for teaching concepts

Benefits of Multiple Windows:

  • Each window has single, clear purpose
  • Users can open multiple windows side-by-side (compare data)
  • Development is modular - work on one window without affecting others
  • Code organisation - related functionality grouped in one file
  • Easy to add new windows without touching existing code

Window Creation and Lifecycle

Creating a Window:

private void BtnManageBooks_Click(object sender, RoutedEventArgs e)
{
    // 1. Create new instance
    BooksWindow booksWindow = new BooksWindow();
    
    // 2. Set owner (optional but recommended)
    booksWindow.Owner = this;
    
    // 3. Show the window
    booksWindow.Show();  // Non-modal - both windows usable
}

Modal vs Non-Modal:

Non-Modal (Show()):

window.Show();  // User can interact with both windows
  • Used in this project
  • Good for: Reference windows, tools, multi-tasking

Modal (ShowDialog()):

bool? result = window.ShowDialog();  // Blocks until window closes
if (result == true)
{
    // User clicked OK
}
  • User must close window before returning to parent
  • Returns DialogResult
  • Good for: Confirmations, required input, login screens

Window Owner Relationship:

childWindow.Owner = this;

Effects:

  • Child appears on top of parent
  • Minimising parent minimises children
  • Closing parent closes children
  • Visual relationship clear to user

WPF Layout Containers

Containers arrange child controls. This project uses:

Grid

Most versatile - defines rows and columns:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>     <!-- Size to content -->
        <RowDefinition Height="*"/>        <!-- Take remaining space -->
        <RowDefinition Height="50"/>       <!-- Fixed 50 pixels -->
    </Grid.RowDefinitions>
    
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="2*"/>     <!-- 2 parts of space -->
        <ColumnDefinition Width="1*"/>     <!-- 1 part of space -->
    </Grid.ColumnDefinitions>
    
    <TextBlock Grid.Row="0" Grid.Column="0" Text="Header"/>
    <DataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>

Sizing Options:

  • Auto: Size to content
  • *: Proportional (takes remaining space)
  • 100: Fixed pixels
  • 2*: Takes twice as much space as 1*

StackPanel

Stacks children vertically or horizontally:

<StackPanel Orientation="Vertical" Margin="10">
    <TextBlock Text="Book Title:"/>
    <TextBox x:Name="txtTitle"/>
    <TextBlock Text="ISBN:"/>
    <TextBox x:Name="txtISBN"/>
    <Button Content="Save" Click="BtnSave_Click"/>
</StackPanel>

Used for: Form fields, button groups, vertical/horizontal lists

Border

Adds visual grouping:

<Border Background="White" 
        BorderBrush="Gray" 
        BorderThickness="1"
        CornerRadius="5"
        Padding="10">
    <StackPanel>
        <!-- Content -->
    </StackPanel>
</Border>

Used for: Sections, cards, visual separation

UniformGrid

Equal-sized cells without row/column definitions:

<UniformGrid Rows="2" Columns="2">
    <Button Content="Books"/>
    <Button Content="Authors"/>
    <Button Content="Members"/>
    <Button Content="Loans"/>
</UniformGrid>

Used for: Main menu buttons, uniform grids

Key WPF Controls

DataGrid

Displays tabular data:

<DataGrid x:Name="dgBooks"
          ItemsSource="{Binding}"
          AutoGenerateColumns="False"
          SelectionMode="Single"
          IsReadOnly="True"
          SelectionChanged="DgBooks_SelectionChanged">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Title" 
                           Binding="{Binding Title}" 
                           Width="*"/>
        <DataGridTextColumn Header="Year" 
                           Binding="{Binding YearPublished}" 
                           Width="100"/>
    </DataGrid.Columns>
</DataGrid>

Key Properties:

  • ItemsSource: Collection to display (set in code-behind)
  • AutoGenerateColumns="False": We define columns manually
  • SelectionMode: Single, Multiple, Extended
  • IsReadOnly="True": Prevents direct editing in grid
  • Width="*": Takes remaining space

ComboBox (Dropdown)

For selecting from a list - crucial for foreign keys:

<ComboBox x:Name="cboMember"
          DisplayMemberPath="FullName"
          SelectedValuePath="MemberID"/>
// Load data
cboMember.ItemsSource = DatabaseHelper.GetAllMembers();

// Get selected value
int selectedMemberId = (int)cboMember.SelectedValue;  // Gets MemberID

Key Properties:

  • ItemsSource: Collection of objects
  • DisplayMemberPath: Which property to show (e.g., "FullName")
  • SelectedValuePath: Which property to use as value (e.g., "MemberID")

Pattern for Foreign Keys:

// Show member name, but store member ID
cboMember.DisplayMemberPath = "FullName";
cboMember.SelectedValuePath = "MemberID";
cboMember.ItemsSource = members;

// When saving
loan.MemberID = (int)cboMember.SelectedValue;

ListBox

Displays list of items:

<ListBox x:Name="lstAuthors" ItemsSource="{Binding}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding FullName}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

ItemTemplate defines how each item displays.

DatePicker

Calendar control for dates:

<DatePicker x:Name="dpLoanDate" 
            SelectedDate="{Binding}"/>
// Set date
dpLoanDate.SelectedDate = DateTime.Today;

// Get date
DateTime date = dpLoanDate.SelectedDate ?? DateTime.Today;

Why DatePicker?

  • Automatic date validation
  • Calendar popup
  • Prevents invalid dates (e.g., Feb 30)
  • Better UX than TextBox

TextBox

Text input:

<TextBox x:Name="txtTitle" 
         Width="200"/>
// Get value
string title = txtTitle.Text;

// Set value
txtTitle.Text = "Default Title";

// Clear
txtTitle.Clear();

// Focus
txtTitle.Focus();

Button

Triggers actions:

<Button Content="Save Book"
        Click="BtnSave_Click"
        Width="100"
        Height="30"
        Background="Green"
        Foreground="White"/>

Styling Properties:

  • Background: Button colour
  • Foreground: Text colour
  • FontWeight="Bold": Text weight
  • FontSize="14": Text size
  • Cursor="Hand": Mouse cursor type

Form Patterns

Add vs Edit Pattern

Same form for both operations:

// Class-level variable
private Book selectedBook = null;

// Add new book
private void BtnNew_Click(object sender, RoutedEventArgs e)
{
    selectedBook = null;  // Indicates "adding"
    ClearForm();
}

// Edit selected book
private void DgBooks_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    selectedBook = (Book)dgBooks.SelectedItem;
    if (selectedBook != null)
    {
        PopulateForm(selectedBook);
    }
}

// Save button - works for both
private void BtnSave_Click(object sender, RoutedEventArgs e)
{
    Book book = ValidateAndCreateBook();
    
    if (selectedBook == null)
    {
        // Adding new
        int newId = DatabaseHelper.InsertBook(book);
    }
    else
    {
        // Editing existing
        book.BookID = selectedBook.BookID;
        DatabaseHelper.UpdateBook(book);
    }
}

Benefits:

  • Less code duplication
  • Single validation logic
  • Consistent user experience
  • Easier to maintain

Object-Oriented Programming Concepts

1. Encapsulation

Principle: Bundle data and methods that operate on that data together.

In this project:

public class Book
{
    // Data
    public string Title { get; set; }
    public int YearPublished { get; set; }
    
    // Methods that operate on this data
    public bool IsValid()
    {
        if (string.IsNullOrWhiteSpace(Title)) return false;
        if (YearPublished < 1000) return false;
        return true;
    }
    
    public string GetValidationErrors()
    {
        // Returns detailed error message
    }
}

Benefits:

  • Book knows how to validate itself
  • Validation logic not scattered through code
  • Easy to test
  • Changes to validation rules in one place

2. Separation of Concerns

Principle: Different responsibilities in different places.

In this project:

Model Classes (Book, Author, Member, Loan)
├─ Represent data
├─ Validate data
└─ Business rules (IsOverdue, FullName)

DatabaseHelper
├─ All SQL queries
├─ Database connections
└─ Data conversion (SQL ↔ C# objects)

Window Classes (BooksWindow, etc.)
├─ Display data
├─ Capture user input
└─ Call DatabaseHelper methods

Example:

// ✗ BAD - mixing concerns
private void BtnSave_Click(...)
{
    string sql = "INSERT INTO Books VALUES (@Title)";  // SQL in UI!
    // ... database code
}

// ✓ GOOD - separated concerns
private void BtnSave_Click(...)
{
    Book book = new Book { Title = txtTitle.Text };
    DatabaseHelper.InsertBook(book);  // UI just calls data layer
}

3. Data Access Layer Pattern

Principle: Centralise all database operations.

Implementation: Static DatabaseHelper class with all CRUD methods.

Benefits:

  • Single Location: All SQL in one file
  • Reusability: Any window can call same methods
  • Consistency: Same error handling everywhere
  • Testability: Can test database operations independently
  • Maintainability: Change SQL without touching UI code

Pattern:

// UI never writes SQL
List<Book> books = DatabaseHelper.GetAllBooks();

// DatabaseHelper handles SQL, connections, errors
public static List<Book> GetAllBooks()
{
    try
    {
        using (SqliteConnection conn = new SqliteConnection(connectionString))
        {
            // SQL here
            // Connection management here
            // Error handling here
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
        return new List<Book>();  // Return empty, don't crash
    }
}

Error Handling Patterns

1. Try-Catch for Database Operations

Every database method wrapped in try-catch:

public static List<Book> GetAllBooks()
{
    try
    {
        // Database operations
    }
    catch (Exception ex)
    {
        // Log error
        System.Diagnostics.Debug.WriteLine($"Error: {ex.Message}");
        
        // Return safe default
        return new List<Book>();
    }
}

Why?

  • Database operations can fail (locked files, connection issues, constraint violations)
  • User shouldn't see technical errors
  • Application shouldn't crash

2. Using Statement (Resource Management)

ALWAYS use using for database connections:

using (SqliteConnection conn = new SqliteConnection(connectionString))
{
    conn.Open();
    // ... use connection
}  // Automatically closed and disposed here, even if error occurs!

What using does:

  1. Creates resource
  2. Executes code block
  3. Calls Dispose() automatically (closes connection)
  4. Works even if exception occurs

Without using (BAD):

SqliteConnection conn = new SqliteConnection(connectionString);
conn.Open();
// ... if error here, connection never closed!
conn.Close();  // Might not execute

3. Validation Before Database Operations

Three-stage validation:

// Stage 1: UI quick checks
if (string.IsNullOrWhiteSpace(txtTitle.Text))
{
    MessageBox.Show("Title required");
    return;
}

// Stage 2: Create object and validate
Book book = new Book { Title = txtTitle.Text };
if (!book.IsValid())
{
    MessageBox.Show(book.GetValidationErrors());
    return;
}

// Stage 3: Try database operation
try
{
    int id = DatabaseHelper.InsertBook(book);
    if (id > 0)
        MessageBox.Show("Book saved successfully");
}
catch (Exception ex)
{
    MessageBox.Show("Error saving book");
}

Search and Filter Patterns

1. SQL LIKE for Partial Matching

string sql = @"SELECT * FROM Books WHERE Title LIKE @SearchTerm";
cmd.Parameters.AddWithValue("@SearchTerm", $"%{searchText}%");

Wildcards:

  • % matches any characters (0 or more)
  • searchText becomes %searchText%
  • Finds "searchText" anywhere in field

Examples:

  • %Potter% matches "Harry Potter", "Potter's Guide"
  • Jane% matches "Jane Austen", "Janet Smith"
  • %Smith matches "John Smith", "Goldsmith"

2. Client-Side Filtering (In-Memory)

When data already loaded:

List<LoanWithDetails> allLoans = DatabaseHelper.GetAllLoansWithDetails();

// Filter in memory using LINQ
var overdueLoans = allLoans.Where(loan => loan.IsOverdue).ToList();
var activeLoans = allLoans.Where(loan => !loan.IsReturned).ToList();
var studentLoans = allLoans.Where(loan => loan.MemberType == "Student").ToList();

When to use:

  • Small datasets (< 10,000 records)
  • Multiple filters on same data
  • Avoids repeated database queries
  • Faster for subsequent filters

3. Server-Side Filtering (SQL WHERE)

Query only needed data:

SELECT * FROM Loans 
WHERE ReturnDate IS NULL           -- Not returned
  AND DueDate < date('now')        -- Past due date

When to use:

  • Large datasets
  • Single filter operation
  • Reduces memory usage
  • Only selected data transferred

Coding Style (AQA Table 2)

Basic Characteristics ✓

  • Meaningful names: GetAuthorsForBook, IsOverdue, connectionString
  • Comprehensive comments: Every method has XML summary explaining purpose, parameters, returns
  • Consistent style: PascalCase for methods, camelCase for parameters, indentation consistent

Good Characteristics ✓

  • Modular code: Separate classes for models, database, UI
  • Appropriate variables: Local variables used, minimal class-level state
  • Constants: connectionString as field
  • Self-documenting: Names clearly indicate purpose
  • Consistent formatting: Same patterns throughout

Excellent Characteristics ✓

  • Cohesive methods: Each does one thing well (InsertBook, UpdateBook, not InsertOrUpdateBook)
  • Well-designed parameters: Methods take what they need, return meaningful values
  • Defensive programming: Validation at multiple levels, null checks
  • Error handling: Try-catch throughout database operations
  • Clear separation: Data, logic, UI in separate layers

Running the Project

Prerequisites

  1. Visual Studio 2019+ (Community Edition is free)
  2. .NET Desktop Development workload installed
  3. NuGet Package Manager (included with Visual Studio)

Installation Steps

  1. Open Solution
   File → Open → Project/Solution
   Select DatabaseExampleWPF.sln
  1. Install SQLite Package (CRITICAL!)
   Right-click project → Manage NuGet Packages
   Browse → Search "Microsoft.Data.Sqlite"
   Install "Microsoft.Data.Sqlite" by Microsoft

OR via Package Manager Console:

   Tools → NuGet Package Manager → Package Manager Console
   Type: Install-Package Microsoft.Data.Sqlite
   Press Enter
  1. Build Project
   Build → Build Solution (Ctrl+Shift+B)
   Check Output window for errors
  1. Run Application
   Press F5 or click Start button
   Main window appears
  1. Initialize Database
   Click "Create Database & Tables"
   Click "Add Sample Data" (recommended)
  1. Explore Features
    • Open each management window
    • Add, edit, delete records
    • Create book-author relationships
    • Search loans with filters

Using DB Browser for SQLite

  1. Download: https://sqlitebrowser.org/
  2. Open database: LibraryDatabase.db from bin/Debug folder
  3. View tables: Browse Data tab
  4. Execute SQL: Execute SQL tab
  5. Understand structure: Database Structure tab

Example queries to try:

-- All books with authors
SELECT b.Title, a.FirstName, a.LastName
FROM Books b
INNER JOIN BookAuthors ba ON b.BookID = ba.BookID
INNER JOIN Authors a ON ba.AuthorID = a.AuthorID;

-- Overdue loans
SELECT b.Title, m.FirstName, m.LastName, l.DueDate
FROM Loans l
INNER JOIN Books b ON l.BookID = b.BookID
INNER JOIN Members m ON l.MemberID = m.MemberID
WHERE l.ReturnDate IS NULL AND l.DueDate < date('now');

AQA 7517 Specification Alignment

Section 4.1: Fundamentals of Programming

  • Variables, constants, data types
  • String handling (string.IsNullOrWhiteSpace, $"{FirstName} {LastName}")
  • Boolean operations (IsOverdue, IsReturned)
  • Exception handling (try-catch throughout)
  • OOP concepts (classes, properties, methods, encapsulation)

Section 4.2: Fundamentals of Data Structures

  • Records/classes with properties
  • Lists and collections (List<Book>, List<Author>)
  • Nullable types (DateTime?)

Section 4.3: Fundamentals of Algorithms

  • Searching (SQL WHERE, LIKE operator)
  • Validation algorithms (IsValid, IsValidEmail with regex)
  • Date calculations (DaysUntilDue)

Section 4.5: Fundamentals of Data Representation

  • Character encoding (TEXT fields in SQLite)
  • Date representation (ISO 8601 format: YYYY-MM-DD)
  • Email format validation (regex pattern matching)

Section 4.12: Fundamentals of Databases

  • Relational databases: 5 related tables
  • Normalisation: 1NF, 2NF, 3NF demonstrated
  • SQL: SELECT, INSERT, UPDATE, DELETE
  • JOIN operations: INNER JOIN across 3 tables
  • Primary keys: AUTOINCREMENT
  • Foreign keys: Referential integrity with CASCADE
  • Entity relationships: One-to-many, many-to-many

Section 4.13: Systematic Problem Solving

  • Analysis: Problem decomposition into entities
  • Design: ERD, database schema, class structure
  • Implementation: Complete working solution
  • Testing: Sample data with varied scenarios
  • Evaluation: Comments explaining decisions

Section 4.14: Non-exam Assessment

Table 1 - Technical Skills:

  • Group B: Records, simple OOP, file handling (database), linear search
  • Group A: Complex data structures (many-to-many), advanced queries (JOINs), parameterised queries

Table 2 - Coding Style:

  • Basic: Meaningful names, extensive comments, consistent style
  • Good: Well-designed UI, strong modularisation, appropriate variables
  • Excellent: Cohesive methods, defensive programming, clear separation of concerns

Key Takeaways for Your NEA

1. Always Use Parameterised Queries

// NEVER
string sql = $"SELECT * FROM Users WHERE Name = '{name}'";

// ALWAYS
string sql = "SELECT * FROM Users WHERE Name = @Name";
cmd.Parameters.AddWithValue("@Name", name);

2. Validate at Multiple Levels

  • UI: Immediate feedback
  • Model: Business rules
  • Database: Final enforcement

3. Separate Concerns

  • Models: Data and validation
  • DatabaseHelper: SQL and database operations
  • Windows: UI and user interaction

4. Use using for Connections

using (SqliteConnection conn = new SqliteConnection(connectionString))
{
    // Connection automatically closed
}

5. Handle Errors Gracefully

try
{
    DatabaseHelper.DeleteBook(id);
}
catch (Exception ex)
{
    MessageBox.Show("Cannot delete book with active loans");
}

6. Document Your Decisions

In NEA documentation, explain:

  • Why this database structure?
  • Why these data types?
  • How does design prevent invalid data?
  • What alternatives were considered?

Understanding vs Copying

✓ What to Learn

  • How to design normalised databases
  • How to use foreign keys and relationships
  • How to write parameterised queries
  • How to implement CRUD operations
  • How to structure database applications
  • How to manage multiple windows in WPF
  • How to separate concerns (UI, logic, data)

✗ What NOT to Do

  • Don't copy this code into your NEA
  • Don't use this as a template
  • Don't create a library system unless it's YOUR original idea
  • Don't submit code you don't fully understand

Your NEA Must Be:

  1. A problem YOU identified through investigation
  2. Designed BY YOU based on requirements YOU gathered
  3. Implemented using concepts in YOUR OWN WAY
  4. Demonstrating YOUR problem-solving skills
  5. YOUR original work with YOUR analysis and evaluation

Remember: Examiners look for evidence of YOUR thinking, YOUR decisions, YOUR problem-solving.

Further Resources

Official Documentation

Database Design

  • Normalisation principles (1NF, 2NF, 3NF)
  • Entity Relationship Diagrams (ERD)
  • Primary and foreign key design

Security

  • OWASP SQL Injection Prevention
  • Parameterised queries best practices

Common Pitfalls

  1. SQL Injection: Never concatenate strings for SQL
  2. Connection Leaks: Always use using statements
  3. Poor Error Handling: Wrap database operations in try-catch
  4. Denormalised Data: Follow normalisation principles
  5. Missing Validation: Validate at UI, model, and database levels
  6. Tight Coupling: Keep SQL separate from UI code

Practice Exercises

Beginner

  1. Add "Publisher" field to Books table
  2. Add ISBN validation (13 digits)
  3. Add phone number to Members

Intermediate

  1. Create Reservations feature
  2. Add Genre table (many-to-many with Books)
  3. Implement late fees calculation

Advanced

  1. Add user authentication
  2. Create audit log (track changes)
  3. Multi-criteria search (AND/OR logic)
  4. Export to CSV

Challenge

  1. Barcode scanning
  2. Statistics dashboard
  3. Email notifications for overdue books
  4. Recommendation system

About This Resource

Created by: Claude AI (Anthropic)
Purpose: Educational exemplar for AQA 7517 A-Level Computer Science NEA
Date: November 2024
Framework: WPF with .NET Framework 4.7.2+
Database: SQLite with Microsoft.Data.Sqlite
Licence: Provided for educational purposes

Acknowledgements

  • AQA for A-Level Computer Science specification
  • Microsoft for WPF, C#, and SQLite libraries
  • SQLite team for the database engine
  • DB Browser for SQLite team

Final Words

This exemplar demonstrates professional database application development. The concepts are fundamental to any database-driven project.

However, your NEA must be:

  • Your own idea - addressing a real problem you've identified
  • Your own design - based on your analysis and planning
  • Your own code - implementing concepts in your own way
  • Your own work - demonstrating your problem-solving ability

Use this to learn concepts, not to copy solutions.

Apply these principles to your own project, and create something you can be proud of - that demonstrates your true capabilities.


For Teachers: Use this to demonstrate concepts in lessons, but students must not use it as an NEA starting point.

For Students: Learn from this, understand the concepts, then create your own original work. Your NEA should reflect YOUR problem-solving skills.

About

Exemplar C# WPF Library database application that connects to sqlite database.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages