Skip to content

Commit e953d13

Browse files
author
MPCoreDeveloper
committed
new version with EF provider fixs for GUID paths
1 parent 8a2b9e7 commit e953d13

73 files changed

Lines changed: 1978 additions & 239 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using SharpCoreDB.EntityFrameworkCore;
3+
using System.Text.Json;
4+
5+
Console.WriteLine("?? SharpCoreDB + EF Core - Full CRUD Demo (Companies & Vacancies)");
6+
Console.WriteLine("Using recommended reliable patterns for Guid relationships\n");
7+
8+
// Setup database (in-memory file for demo)
9+
var dbPath = $"./crud_demo_{Guid.NewGuid():N}.scdb";
10+
var connectionString = $"Data Source={dbPath};Password=DemoPassword123;Cache=Shared";
11+
12+
var options = new DbContextOptionsBuilder<CompanyVacancyContext>()
13+
.UseSharpCoreDB(connectionString)
14+
.Options;
15+
16+
using var context = new CompanyVacancyContext(options);
17+
await context.Database.EnsureCreatedAsync();
18+
19+
// =====================================================================
20+
// 1. LOAD SEED DATA
21+
// =====================================================================
22+
Console.WriteLine("?? Loading seed data from companies.vacancies.seed.json...");
23+
24+
var json = await File.ReadAllTextAsync("companies.vacancies.seed.json");
25+
var seedData = JsonSerializer.Deserialize<SeedDataRoot>(json, new JsonSerializerOptions
26+
{
27+
PropertyNameCaseInsensitive = true
28+
})!;
29+
30+
Console.WriteLine($" Loaded {seedData.Companies.Count} companies from seed file.\n");
31+
32+
// =====================================================================
33+
// 2. CREATE (Seed the database)
34+
// =====================================================================
35+
Console.WriteLine("?? CREATE - Seeding companies and vacancies...");
36+
37+
foreach (var seed in seedData.Companies)
38+
{
39+
var company = new Company
40+
{
41+
Id = Guid.NewGuid(),
42+
Name = seed.Name,
43+
Address = seed.Address
44+
};
45+
46+
foreach (var v in seed.Vacancies)
47+
{
48+
company.Vacancies.Add(new Vacancy
49+
{
50+
Id = Guid.NewGuid(),
51+
Title = v.Title,
52+
Description = v.Description,
53+
IsActive = v.IsActive,
54+
CompanyId = company.Id
55+
});
56+
}
57+
58+
context.Companies.Add(company);
59+
}
60+
61+
await context.SaveChangesAsync();
62+
Console.WriteLine(" Seeded successfully.\n");
63+
64+
// =====================================================================
65+
// 3. READ - Using recommended reliable pattern
66+
// =====================================================================
67+
Console.WriteLine("?? READ - Getting companies with active vacancies (recommended pattern)...");
68+
69+
var activeCompanies = await GetActiveWithVacanciesAsync(context);
70+
71+
Console.WriteLine($" Found {activeCompanies.Count} companies with at least one active vacancy:");
72+
foreach (var c in activeCompanies)
73+
{
74+
Console.WriteLine($" - {c.Name} ({c.Vacancies.Count(v => v.IsActive)} active vacancies)");
75+
}
76+
Console.WriteLine();
77+
78+
// =====================================================================
79+
// 4. UPDATE
80+
// =====================================================================
81+
Console.WriteLine("?? UPDATE - Changing a vacancy...");
82+
83+
var delta = await context.Companies
84+
.AsNoTracking()
85+
.FirstAsync(c => c.Name == "Delta Logistics");
86+
87+
var backendDev = await context.Vacancies.FirstAsync(v => v.Title == "Backend Developer" && v.CompanyId == delta.Id);
88+
89+
backendDev.Title = "Senior Backend Developer";
90+
backendDev.IsActive = false;
91+
92+
await context.SaveChangesAsync();
93+
94+
Console.WriteLine(" Updated 'Backend Developer' → 'Senior Backend Developer' (IsActive = false)\n");
95+
96+
// Re-read using recommended pattern
97+
var afterUpdate = await GetActiveWithVacanciesAsync(context);
98+
var deltaAfter = afterUpdate.First(c => c.Name == "Delta Logistics");
99+
Console.WriteLine($" Delta Logistics now has {deltaAfter.Vacancies.Count(v => v.IsActive)} active vacancies.\n");
100+
101+
// =====================================================================
102+
// 5. CREATE NEW
103+
// =====================================================================
104+
Console.WriteLine("?? CREATE - Adding new company + vacancy...");
105+
106+
var newCompany = new Company
107+
{
108+
Id = Guid.NewGuid(),
109+
Name = "Future Systems",
110+
Address = "Innovation Park 42, Amsterdam"
111+
};
112+
newCompany.Vacancies.Add(new Vacancy
113+
{
114+
Id = Guid.NewGuid(),
115+
Title = "AI Engineer",
116+
IsActive = true,
117+
CompanyId = newCompany.Id
118+
});
119+
120+
context.Companies.Add(newCompany);
121+
await context.SaveChangesAsync();
122+
123+
Console.WriteLine(" Added 'Future Systems' with AI Engineer vacancy.\n");
124+
125+
// =====================================================================
126+
// 6. DELETE
127+
// =====================================================================
128+
Console.WriteLine("?? DELETE - Removing a vacancy...");
129+
130+
var toDelete = await context.Vacancies
131+
.FirstAsync(v => v.Title == "Project Coordinator");
132+
133+
context.Vacancies.Remove(toDelete);
134+
await context.SaveChangesAsync();
135+
136+
Console.WriteLine(" Removed 'Project Coordinator' vacancy.\n");
137+
138+
// Final read
139+
var final = await GetActiveWithVacanciesAsync(context);
140+
Console.WriteLine($" Final result: {final.Count} companies with active vacancies.");
141+
142+
// Cleanup
143+
try { File.Delete(dbPath); } catch { }
144+
145+
Console.WriteLine("\n? Full CRUD demo completed successfully!");
146+
Console.WriteLine(" (Using recommended reliable patterns for Guid-based relationships)");
147+
148+
// =====================================================================
149+
// Recommended reliable helper (same pattern as CompanyVacancyRepository)
150+
// =====================================================================
151+
static async Task<List<Company>> GetActiveWithVacanciesAsync(CompanyVacancyContext dbContext)
152+
{
153+
var companies = await dbContext.Companies.AsNoTracking().OrderBy(x => x.Name).ToListAsync();
154+
var vacancies = await dbContext.Vacancies.AsNoTracking().ToListAsync();
155+
156+
var byCompany = vacancies.GroupBy(v => v.CompanyId).ToDictionary(g => g.Key, g => g.ToList());
157+
158+
foreach (var c in companies)
159+
{
160+
c.Vacancies = byCompany.TryGetValue(c.Id, out var list) ? list : [];
161+
}
162+
163+
return companies.Where(x => x.Vacancies.Any(v => v.IsActive)).ToList();
164+
}
165+
166+
// =====================================================================
167+
// Entity models (minimal for demo)
168+
// =====================================================================
169+
public class Company
170+
{
171+
public Guid Id { get; set; }
172+
public string Name { get; set; } = string.Empty;
173+
public string Address { get; set; } = string.Empty;
174+
public ICollection<Vacancy> Vacancies { get; set; } = [];
175+
}
176+
177+
public class Vacancy
178+
{
179+
public Guid Id { get; set; }
180+
public string Title { get; set; } = string.Empty;
181+
public string Description { get; set; } = string.Empty;
182+
public bool IsActive { get; set; }
183+
public Guid CompanyId { get; set; }
184+
}
185+
186+
public class CompanyVacancyContext(DbContextOptions<CompanyVacancyContext> options) : DbContext(options)
187+
{
188+
public DbSet<Company> Companies => Set<Company>();
189+
public DbSet<Vacancy> Vacancies => Set<Vacancy>();
190+
191+
protected override void OnModelCreating(ModelBuilder modelBuilder)
192+
{
193+
modelBuilder.Entity<Company>(e =>
194+
{
195+
e.ToTable("Companies");
196+
e.HasKey(x => x.Id);
197+
e.Property(x => x.Name).IsRequired().HasMaxLength(200);
198+
e.HasMany(x => x.Vacancies).WithOne().HasForeignKey(v => v.CompanyId);
199+
});
200+
201+
modelBuilder.Entity<Vacancy>(e =>
202+
{
203+
e.ToTable("Vacancies");
204+
e.HasKey(x => x.Id);
205+
e.Property(x => x.Title).IsRequired().HasMaxLength(200);
206+
});
207+
}
208+
}
209+
210+
// Seed DTOs
211+
public class SeedDataRoot { public List<SeedCompany> Companies { get; set; } = []; }
212+
public class SeedCompany { public string Name { get; set; } = ""; public string Address { get; set; } = ""; public List<SeedVacancy> Vacancies { get; set; } = []; }
213+
public class SeedVacancy { public string Title { get; set; } = ""; public string Description { get; set; } = ""; public bool IsActive { get; set; } }
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# SharpCoreDB EF Core Full CRUD Demo
2+
3+
This is a **runnable console application** that demonstrates a complete end-to-end CRUD workflow using:
4+
5+
- SharpCoreDB.EntityFrameworkCore provider
6+
- Guid primary keys and foreign keys
7+
- The **recommended reliable patterns** for reading relationships (instead of relying on `Include` + server-side navigation filters, which have current limitations)
8+
9+
## What it does
10+
11+
1. Loads the real dataset from `companies.vacancies.seed.json` (Delta Logistics, Nordic Retail, TechNova + their vacancies)
12+
2. **Create** – Seeds the entire dataset
13+
3. **Read** – Retrieves companies with active vacancies using the recommended two-query pattern
14+
4. **Update** – Modifies a vacancy (title + status)
15+
5. **Create (extra)** – Adds a brand new company with a vacancy
16+
6. **Delete** – Removes a specific vacancy
17+
7. Final verification using the reliable read pattern
18+
19+
## How to run
20+
21+
```bash
22+
cd Examples/SharpCoreDB.EFCoreCrudDemo
23+
dotnet run
24+
```
25+
26+
## Key Takeaway
27+
28+
This demo shows the **current best practice** for working with Guid-based relationships in SharpCoreDB.EntityFrameworkCore:
29+
30+
- Use simple queries + manual navigation wiring for reads (see `GetActiveWithVacanciesAsync`)
31+
- This pattern is stable and recommended until deeper provider improvements (custom `IModificationCommandBatch`) are completed in a future release.
32+
33+
## Roadmap
34+
35+
A more "magical" EF experience (full `Include` + complex navigation queries for Guids on writes) is planned via Option B (custom batch implementation) in a later version.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace>SharpCoreDB.EFCoreCrudDemo</RootNamespace>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\SharpCoreDB.EntityFrameworkCore\SharpCoreDB.EntityFrameworkCore.csproj" />
13+
</ItemGroup>
14+
15+
<!-- System.Text.Json is included in .NET 10, no explicit reference needed -->
16+
17+
<ItemGroup>
18+
<None Update="companies.vacancies.seed.json">
19+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
20+
</None>
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"companies": [
3+
{
4+
"name": "Delta Logistics",
5+
"address": "Stationsplein 1, Rotterdam",
6+
"vacancies": [
7+
{
8+
"title": "Backend Developer",
9+
"description": "Build and maintain robust REST APIs.",
10+
"isActive": true
11+
},
12+
{
13+
"title": "Project Coordinator",
14+
"description": "Coordinate delivery timelines and communication.",
15+
"isActive": false
16+
},
17+
{
18+
"title": "DevOps Engineer",
19+
"description": "Implement CI/CD pipelines and manage cloud infrastructure.",
20+
"isActive": true
21+
},
22+
{
23+
"title": "Frontend Developer",
24+
"description": "Create responsive and user-friendly web interfaces.",
25+
"isActive": true
26+
}
27+
]
28+
},
29+
{
30+
"name": "Nordic Retail",
31+
"address": "Ringlaan 24, Gent",
32+
"vacancies": [
33+
{
34+
"title": "Data Analyst",
35+
"description": "Analyze sales and customer behavior data.",
36+
"isActive": true
37+
},
38+
{
39+
"title": "Marketing Specialist",
40+
"description": "Develop and execute marketing campaigns.",
41+
"isActive": true
42+
},
43+
{
44+
"title": "Store Manager",
45+
"description": "Oversee daily operations of the retail store.",
46+
"isActive": true
47+
}
48+
]
49+
},
50+
{
51+
"name": "TechNova",
52+
"address": "Innovatieweg 10, Eindhoven",
53+
"vacancies": [
54+
{
55+
"title": "AI Researcher",
56+
"description": "Conduct research on cutting-edge AI technologies.",
57+
"isActive": true
58+
},
59+
{
60+
"title": "Software Tester",
61+
"description": "Ensure software quality through rigorous testing.",
62+
"isActive": true
63+
},
64+
{
65+
"title": "Product Manager",
66+
"description": "Define product vision and roadmap.",
67+
"isActive": true
68+
}
69+
]
70+
}
71+
]
72+
}
Binary file not shown.

0 commit comments

Comments
 (0)