diff --git a/02_ProjectOrientedSessions/Session01/Session 01 Actual.md b/02_ProjectOrientedSessions/Session01/Session 01 Actual.md new file mode 100644 index 0000000..c0d20ba --- /dev/null +++ b/02_ProjectOrientedSessions/Session01/Session 01 Actual.md @@ -0,0 +1,756 @@ +# 1. Create the develop branch +# 2. Entities +## Create the feature/domain-entities branch +## Create the IEntity interface and Entity class + +## IEntity +> Location: Domain Project > Framework > Interfaces > IEntity.cs +```C# +public interface IEntity +{ + public TKey Id { get; protected set; } +} +``` +## Entity +> Location: Domain Project > Framework > Base > Entity.cs +```C# +public class Entity : IEntity +{ + public TKey Id{ get; protected set; } +} +``` + + +## Why should we do this? (Chat GPT): +> this approach is **valid and commonly used** in **Domain-Driven Design (DDD)** and **Clean Architecture**. It provides **consistency**, **reusability**, and **common functionality** across all entities. +## Create the entities + +## Create the entities in Domain > Aggregates > (RelatedFolder) +## They all should inherit Entity + +## **Choosing the right datatype for integer values, specially IDs (Chat GPT):** + +### **1️⃣ Integer Data Types (int, short, long) in C# and SQL Server** + +| **C# Type** | **SQL Server Type** | **Size** | **Range** | +| ----------- | ------------------- | -------- | ------------------------------------------------------- | +| `byte` | `TINYINT` | 1 byte | 0 to 255 | +| `short` | `SMALLINT` | 2 bytes | -32,768 to 32,767 | +| `int` | `INT` | 4 bytes | -2,147,483,648 to 2,147,483,647 | +| `long` | `BIGINT` | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | + +❗ **Important Notes:** + +- The **ranges are the same in C# and SQL Server** because both use the same underlying storage. +- `SMALLINT` and `TINYINT` **save space**, but be careful about hitting the limit. +- `BIGINT` is needed only if you expect **billions** of records. +--- + +### **2️⃣ What to Use for User ID, Ticket ID, Gender ID? (Some examples)** + +| **Field** | **Recommended C# Type** | **SQL Server Type** | **Why?** | +| ----------- | ----------------------- | ----------------------- | ----------------------------------------------------------- | +| `UserId` | `int` or `long` | `INT` or `BIGINT` | `INT` is usually enough unless expecting billions of users. | +| `TicketId` | `int` or `long` | `INT` or `BIGINT` | Use `BIGINT` if expecting massive ticket volumes. | +| `GenderId` | `byte` or `short` | `TINYINT` or `SMALLINT` | Gender options are limited, so `TINYINT` is sufficient. | +| `CompanyId` | `int` | `INT` | Companies are limited, `INT` is fine. | +| `VehicleId` | `int` | `INT` | Use `INT`, as vehicle count is manageable. | +| `Price` | `decimal(18,2)` | `DECIMAL(18,2)` | Avoid `float`/`double` due to rounding issues. | + +--- + +### **3️⃣ Should I Use GUIDs for User IDs or Ticket IDs?** + +- **Use `GUID` (`UNIQUEIDENTIFIER`) for IDs only if:** + - Data is distributed across multiple databases. + - Security is critical (e.g., preventing sequential guessing of IDs). +- Otherwise, **stick with `int` or `long`** for performance. + +📌 **Example in C# (EF Core Model):** + +```csharp +public class Ticket +{ + public int TicketId { get; set; } // Primary key + public int UserId { get; set; } // Foreign key + public decimal Price { get; set; } // Use decimal for money + public DateTime PurchaseDate { get; set; } +} +``` + +📌 **Fluent API (SQL Mapping)** + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.Entity() + .Property(t => t.Price) + .HasColumnType("DECIMAL(18,2)"); +} +``` + + + +## **`required` keyword (Chat GPT) : + +C# **IntelliSense** suggests adding `required` to string properties because of **nullable reference types (NRT)** introduced in **C# 8.0+**. + +### **1️⃣ What Does `required` Do?** + +- `required` **forces initialization** of the property when creating an object. +- It is **not a data annotation** (like `[Required]` in EF Core), but a **C# keyword** that affects **compile-time checks**. + +📌 **Example Without `required`** + +```csharp +public class User +{ + public string Name { get; set; } // Warning: "Non-nullable property 'Name' is uninitialized" +} +``` + +🔴 **Problem**: The compiler warns that `Name` is not initialized. +✅ **Fix**: Add `required` or initialize the property. + +📌 **Example With `required`** + +```csharp +public class User +{ + public required string Name { get; set; } // No warning +} +``` + +✅ **Effect**: You **must** provide `Name` when creating a `User` object. + +```csharp +var user = new User { Name = "Mehrdad" }; // ✅ Works +var invalidUser = new User(); // ❌ Compilation Error: Name is required +``` + + + +## **A note about strings in C# (Chat GPT) :** +**`string` is nullable in C#**, but in **nullable reference types (C# 8+), `string` is treated as non-nullable unless explicitly marked `string?`**. + +- **`string`** → Default behavior (non-nullable by default in nullable context). +- **`string?`** → Explicitly nullable. + + +## Add Navigation Properties +## Create `virtual` navigation properties +### **Why virtual? (Chat GPT):** + +- If you mark a navigation property as `virtual`, EF Core **creates a proxy class** at runtime that overrides the property and loads related data **only when accessed**. +- This is called **Lazy Loading**, meaning data is not fetched until needed. +- If you don’t mark it as `virtual`, you **must** load relationships using `.Include()` (Eager Loading). + +### **What Type Should Navigation Properties Be?** + +| **Scenario** | **Recommended Type** | **Why?** | +| --------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------- | +| **Single reference** (e.g., `Ticket → Transportation`) | `virtual Transportation` | Represents a **one-to-one** or **many-to-one** relationship. | +| **Collection of related entities** (e.g., `Transportation → Tickets`) | `virtual ICollection` | Best for **one-to-many** relationships, supports lazy loading. | +| **Alternative for collections** | `virtual List` | Works the same, but **EF prefers `ICollection`**. | +| **Using `IEnumerable`** | ❌ **Avoid** | EF **does not recognize** `IEnumerable` for navigation properties. | +### **One Scenario to look after if using lazy loading:** +#### **What is the N+1 Query Problem?** + +The **N+1 query problem** happens when EF Core **makes too many separate database queries** instead of loading data efficiently. + +##### **Example Scenario** + +Let’s say you have **100 tickets**, and each ticket has a related **Transportation** entity. + +You run this code: + +```c# +var tickets = context.Tickets.ToList(); // Loads all tickets foreach (var ticket in tickets) +{ + Console.WriteLine(ticket.Transportation.Name); // Lazy loads Transportation for each ticket +} +``` + +##### **What Happens?** + +1. **1 Query:** EF Core first loads all `Tickets`. +2. **N Queries:** Then, for each **Ticket**, EF Core makes a separate query to fetch `Transportation` (so 100 additional queries). +3. **Total Queries:** **1 + 100 = 101 queries!** 🚨 **Bad performance!** + +--- + + +## Add `Microsoft.EntityFrameworkCore.Proxies` to Infrastructure Project + +## Create a PR and merge the current branch with develop +# 6. Configurations +## Read the documentation: +https://learn.microsoft.com/en-us/ef/core/modeling/ + +## Create the feature/entity-configurations branch based on develop + +## **1. Where Should You Place Configuration Files?** + +✅ **Best Practice:** Place all configuration files in the **Infrastructure** layer. +📂 Suggested Folder: `Infrastructure/Configurations` + +Create configuration classes with this format: `[Entity]Configutaion.cs` +The class should implement the `IEntityTypeConfiguration<[Entity]>` + + +### **Reason:** + +- The **Domain layer** should be **clean** (only entities, no database-related logic). +- The **Infrastructure layer** handles **database interactions**, so configurations belong here. + +--- + +## **2. How to Define Keys (Primary Keys & Identity)** + +You **don’t** need to explicitly define the **primary key (PK)** if you follow EF Core conventions (`Id` or `EntityNameId`). However, if you want to be explicit: + +```csharp +public class TicketConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(t => t.Id); // Explicitly defining PK (optional) + + builder.Property(t => t.Id) + .ValueGeneratedOnAdd(); // Sets Identity (auto-increment) + } +} +``` + +> 🛑 **NOTE:** If you’re using a GUID as the ID, you might need `.ValueGeneratedNever()` instead. + +--- + +## **3. How to Define Foreign Keys?** + +Use `HasOne()` and `WithMany()` for **one-to-many** relationships. + +```csharp +public class TicketConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(t => t.Id); + + // Foreign Key - Ticket to Transportation + builder.HasOne(t => t.Transportation) + .WithMany(tr => tr.Tickets) + .HasForeignKey(t => t.TransportationId) + .OnDelete(DeleteBehavior.Restrict); // Optional: No cascade delete + } +} +``` + +--- + +## **4. How to Introduce Navigation Properties with Different Names?** + +If your navigation property **doesn’t match** the entity name, you should explicitly specify it using `HasOne()` and `WithMany()`. + +### **Example: Ticket has a Buyer (which is an Account)** + +```csharp +builder.HasOne(t => t.Buyer) // Navigation property (Ticket → Account) + .WithMany(a => a.TicketsBought) // Corresponding collection in Account + .HasForeignKey(t => t.BuyerId); +``` + +> **Tip:** If your navigation property names don't match table names, always define them explicitly in the Fluent API. + +--- + +## **5. How to Configure Column Types? (nvarchar, date, etc.)** + +You can **manually specify column types** using `.HasColumnType()`. + +### **All Strings Should Be `nvarchar` with Specific Lengths** + +```csharp +builder.Property(t => t.TicketNumber) + .IsRequired() + .HasMaxLength(20) // Limits nvarchar length + .HasColumnType("nvarchar(20)"); +``` + +### **Store Some DateTime Fields as SQL `DATE` Instead of `DATETIME2`** + +```csharp +builder.Property(t => t.PurchaseDate) + .HasColumnType("date"); // Instead of default "datetime2" +``` + +> 🚀 **Best Practice:** Always set **string lengths** to avoid `nvarchar(MAX)`, which hurts performance. + +--- + +## **6. How to Define Constraints? (Not Null, Length, etc.)** + +Use `.IsRequired()` for **NOT NULL** and `.HasMaxLength()` for length constraints. + +### **Example: Ticket Number Must Be Unique & Required** + +```csharp +builder.Property(t => t.TicketNumber) + .IsRequired() // NOT NULL + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + +builder.HasIndex(t => t.TicketNumber) + .IsUnique(); // Unique constraint +``` + +--- + +## **Final Configuration File Example (TicketConfiguration.cs)** + +Here’s a **complete** example of a configuration file: + +```csharp +public class TicketConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(t => t.Id); // Primary Key + builder.Property(t => t.Id).ValueGeneratedOnAdd(); // Auto-increment + + // Foreign Key Relationships + builder.HasOne(t => t.Transportation) + .WithMany(tr => tr.Tickets) + .HasForeignKey(t => t.TransportationId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(t => t.Buyer) + .WithMany(a => a.TicketsBought) + .HasForeignKey(t => t.BuyerId); + + // Column Types & Constraints + builder.Property(t => t.TicketNumber) + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + builder.HasIndex(t => t.TicketNumber) + .IsUnique(); // Unique constraint + + builder.Property(t => t.PurchaseDate) + .HasColumnType("date"); // Store as DATE instead of DATETIME2 + } +} +``` + +--- + +## **Summary & Best Practices** + +✅ **Store Configuration Files in:** `Infrastructure/Configurations` +✅ **Define Foreign Keys:** Use `HasOne()` and `WithMany()` +✅ **Explicitly Define Navigation Properties** if the names differ +✅ **Column Types:** Use `.HasColumnType()` for `nvarchar`, `date`, etc. +✅ **Constraints:** Use `.IsRequired()`, `.HasMaxLength()`, `.IsUnique()` + +--- + +Now you’re all set to configure your entities properly! 🚀 Let me know if you have more questions! 😊 + +## Some special cases + +### **1. Join Tables with Multiple IDs** + +In many-to-many relationships, a join table is created to link two entities. This join table typically contains foreign keys referencing the primary keys of the two entities involved in the relationship. + +#### **Example of a Join Table** + +Suppose we have two entities, `Student` and `Course`, and we want to create a many-to-many relationship between them. We'll create a join table called `StudentCourses`. + +#### **Entities** + +```csharp +public class Student : Entity +{ + public required string Name { get; set; } + public virtual ICollection StudentCourses { get; set; } +} + +public class Course : Entity +{ + public required string Title { get; set; } + public virtual ICollection StudentCourses { get; set; } +} + +public class StudentCourse +{ + public long StudentId { get; set; } + public virtual Student Student { get; set; } + + public long CourseId { get; set; } + public virtual Course Course { get; set; } +} +``` + +#### **Configuration for Join Table** + +You would configure the join table using the Fluent API: + +```csharp +public class StudentCourseConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // Composite Primary Key + builder.HasKey(sc => new { sc.StudentId, sc.CourseId }); + + // Foreign Key Relationships + builder.HasOne(sc => sc.Student) + .WithMany(s => s.StudentCourses) + .HasForeignKey(sc => sc.StudentId); + + builder.HasOne(sc => sc.Course) + .WithMany(c => c.StudentCourses) + .HasForeignKey(sc => sc.CourseId); + } +} +``` + +#### **OnModelCreating Method in DbContext** + +In your `DbContext`, you would include: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.ApplyConfiguration(new StudentCourseConfiguration()); +} +``` + +#### **Key Points for Join Tables** + +- **Composite Primary Key**: The join table uses a composite key made up of both foreign keys. +- **Navigation Properties**: This enables navigation from `Student` to `Course` and vice versa. + +--- + +### **2. Using GUIDs That Should Be Auto-Generated** + +GUIDs (Globally Unique Identifiers) can be used as primary keys in your entities. In EF Core, you can configure them to auto-generate when a new entity is created. + +#### **Example Entity Using GUID** + +```csharp +public class SomeEntity +{ + public Guid Id { get; set; } = Guid.NewGuid(); // Auto-generate GUID + public string Name { get; set; } +} +``` + +#### **Configuration for GUID** + +When configuring an entity with a GUID as the primary key, you don’t need a specific setup in the configuration, but you can enforce that the `Id` is generated on addition. + +```csharp +builder.Property(e => e.Id) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("NEWSEQUENTIALID()"); // Optionally use NEWID() for random GUID +``` + +#### **How It Works** + +- **`Guid.NewGuid()`** generates a new GUID when a new entity instance is created. +- **Database**: If you use `NEWSEQUENTIALID()` in SQL Server, it generates sequential GUIDs, which can improve indexing performance. + +#### **Example Configuration in DbContext** + +Here's how you might define an entity with GUIDs in your `DbContext`: + +```csharp +public class ApplicationDbContext : DbContext +{ + public DbSet SomeEntities { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.HasKey(e => e.Id); + builder.Property(e => e.Id) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("NEWSEQUENTIALID()"); + }); + } +} +``` + +--- + + +## Create a PR and merge the current branch with develop +# Summary +### 📌 **Implementing Database and EF Core in Clean Architecture** + +In **Clean Architecture**, everything should be **modular**, **loosely coupled**, and **separated into layers**. The database-related concerns (entities, repositories, unit of work, configurations, migrations, etc.) belong **primarily** to the **Infrastructure** and **Domain layers**. + +--- + +## 🏛 **Layer Breakdown for Database Implementation** + +|**Layer**|**Responsibility**|**Examples**| +|---|---|---| +|**Domain**|Business rules & entity models|Entities (POCOs), Value Objects, Interfaces| +|**Application**|Business logic & use cases|Services, DTOs, CQRS Handlers| +|**Infrastructure**|Data access, repositories, database configurations|EF Core, Repository, Unit of Work, Configurations| +|**Presentation**|UI/API layer, controllers, endpoints|ASP.NET Core Controllers, Razor Pages, Blazor| + +--- + +## 🛠 **1️⃣ Defining the Domain Layer (Entities and Interfaces)** + +The **Domain Layer** should contain **only business logic and entity definitions**—it should not reference Entity Framework or infrastructure concerns. + +### 📌 **Entities (Domain/Entities)** + +```csharp +namespace Domain.Entities +{ + public class Ticket + { + public int Id { get; set; } + public string CustomerName { get; set; } = string.Empty; + public DateTime PurchaseDate { get; set; } + public decimal Price { get; set; } + + // Navigation Property + public int CompanyId { get; set; } + public virtual Company Company { get; set; } = null!; + } +} +``` + +```csharp +namespace Domain.Entities +{ + public class Company + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + + // Navigation Property + public virtual ICollection Tickets { get; set; } = new List(); + } +} +``` + +### 📌 **Repository Interfaces (Domain/Repositories)** + +```csharp +namespace Domain.Repositories +{ + public interface IRepository where T : class + { + Task GetByIdAsync(int id); + Task> GetAllAsync(); + Task AddAsync(T entity); + void Remove(T entity); + } +} +``` + +```csharp +namespace Domain.Repositories +{ + public interface IUnitOfWork + { + Task CompleteAsync(); + } +} +``` + +--- + +## 🏗 **2️⃣ Implementing Infrastructure Layer (EF Core, Repositories, Configurations, Migrations)** + +### 📌 **Database Context (Infrastructure/Persistence/AppDbContext.cs)** + +```csharp +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using System.Reflection; + +namespace Infrastructure.Persistence +{ + public class AppDbContext : DbContext + { + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Tickets { get; set; } + public DbSet Companies { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + base.OnModelCreating(modelBuilder); + } + } +} +``` + +### 📌 **Entity Configurations (Infrastructure/Persistence/Configurations)** + +EF Core Fluent API is best kept in separate configuration classes. + +```csharp +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Persistence.Configurations +{ + public class TicketConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(t => t.Id); + builder.Property(t => t.CustomerName) + .IsRequired() + .HasMaxLength(100); + builder.Property(t => t.Price) + .HasColumnType("decimal(18,2)"); + + builder.HasOne(t => t.Company) + .WithMany(c => c.Tickets) + .HasForeignKey(t => t.CompanyId); + } + } +} +``` + +--- + +### 📌 **Repository Pattern (Infrastructure/Persistence/Repositories)** + +```csharp +using Domain.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace Infrastructure.Persistence.Repositories +{ + public class Repository : IRepository where T : class + { + protected readonly AppDbContext _context; + protected readonly DbSet _dbSet; + + public Repository(AppDbContext context) + { + _context = context; + _dbSet = context.Set(); + } + + public async Task GetByIdAsync(int id) => await _dbSet.FindAsync(id); + public async Task> GetAllAsync() => await _dbSet.ToListAsync(); + public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity); + public void Remove(T entity) => _dbSet.Remove(entity); + } +} +``` + +### 📌 **Unit of Work Implementation** + +```csharp +using Domain.Repositories; +using System.Threading.Tasks; + +namespace Infrastructure.Persistence +{ + public class UnitOfWork : IUnitOfWork + { + private readonly AppDbContext _context; + + public UnitOfWork(AppDbContext context) + { + _context = context; + } + + public async Task CompleteAsync() + { + return await _context.SaveChangesAsync(); + } + } +} +``` + +--- + +## 🌍 **3️⃣ Configuring the Database in ASP.NET Core** + +### 📌 **Connection String (appsettings.json)** + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=YOUR_SERVER;Database=YourDb;Trusted_Connection=True;MultipleActiveResultSets=true;" + } +} +``` + +### 📌 **Registering EF Core in `Program.cs`** + +```csharp +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + +// Register repositories and Unit of Work +builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); +builder.Services.AddScoped(); + +var app = builder.Build(); +app.Run(); +``` + +--- + +## 🚀 **4️⃣ Migrations and Database Setup** + +### ✅ **Creating Migrations** + +```bash +dotnet ef migrations add InitialCreate --project Infrastructure --startup-project Presentation +``` + +### ✅ **Applying Migrations** + +```bash +dotnet ef database update --project Infrastructure --startup-project Presentation +``` + +--- + +## 🎯 **Summary** + +|**Component**|**Layer**|**Purpose**| +|---|---|---| +|**Entities**|Domain|Business models| +|**Repository Interfaces**|Domain|Data access abstraction| +|**AppDbContext & Configurations**|Infrastructure|EF Core setup & Fluent API| +|**Repositories & UoW**|Infrastructure|Data persistence implementation| +|**Controllers & Services**|Presentation & Application|API logic| +|**Connection String**|AppSettings.json|Database configuration| + +--- + +## 🏆 **Final Thoughts** + +✅ **This follows Clean Architecture principles** +✅ **Keeps domain logic independent of EF Core** +✅ **Repositories and Unit of Work separate infrastructure concerns** +✅ **DB Context and migrations handled in the right place** + +Would you like to add CQRS or MediatR to this setup? 🚀 \ No newline at end of file diff --git a/02_ProjectOrientedSessions/docs/AlibabaERD.pdf b/02_ProjectOrientedSessions/docs/AlibabaERD.pdf index eb3c7ea..d078991 100644 Binary files a/02_ProjectOrientedSessions/docs/AlibabaERD.pdf and b/02_ProjectOrientedSessions/docs/AlibabaERD.pdf differ diff --git a/02_ProjectOrientedSessions/docs/AlibabaERD.vsdx b/02_ProjectOrientedSessions/docs/AlibabaERD.vsdx index b0ef42a..1b889f8 100644 Binary files a/02_ProjectOrientedSessions/docs/AlibabaERD.vsdx and b/02_ProjectOrientedSessions/docs/AlibabaERD.vsdx differ diff --git a/02_ProjectOrientedSessions/docs/~$$AlibabaERD.~vsdx b/02_ProjectOrientedSessions/docs/~$$AlibabaERD.~vsdx new file mode 100644 index 0000000..47b6a2a Binary files /dev/null and b/02_ProjectOrientedSessions/docs/~$$AlibabaERD.~vsdx differ