Domena: Avtohiša
V tem primeru uporabljamo tri entitete:
1. Avto
Atributi: Znamka, Model, Letnik, Cena
2. Lastnik
Atributi: Ime, Priimek, Telefon
3. Prodaja (Vmesna)
Povezuje Avto in Lastnika + Datum, Znesek
1
Modeli in Baza (EF Core)
Navodila za izpit:
1. Namesti pakete:
2. Kopiraj spodnjo kodo v mapo
3. Zaženi v konzoli:
1. Namesti pakete:
Microsoft.EntityFrameworkCore.Sqlite in .Tools.2. Kopiraj spodnjo kodo v mapo
Models ali Data.3. Zaženi v konzoli:
Add-Migration Initial in nato Update-Database.
Data/AvtoContext.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
// 1. Entiteta: AVTO
public class Avto
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Znamka { get; set; } // npr. BMW
[Required]
public string Model { get; set; } // npr. X5
[Range(1900, 2100)] // Validacija letnika
public int Letnik { get; set; }
[Column(TypeName = "decimal(18,2)")] // Tip za denar
public decimal Cena { get; set; }
// Relacija: En avto je lahko prodan večkrat (npr. rabljen)
[JsonIgnore] // Prepreči cikel pri serializaciji
public List<Prodaja> Prodaje { get; set; }
}
// 2. Entiteta: LASTNIK (Kupec)
public class Lastnik
{
public int Id { get; set; }
[Required]
public string Ime { get; set; }
public string Priimek { get; set; }
[Phone] // Validacija formata telefona
public string Telefon { get; set; }
// Navigacija
[JsonIgnore]
public List<Prodaja> Prodaje { get; set; }
}
// 3. Entiteta: PRODAJA (Vmesna tabela M:N)
public class Prodaja
{
public int Id { get; set; }
public DateTime DatumProdaje { get; set; }
public decimal KoncnaCena { get; set; } // Lahko se razlikuje od cene avta (popust)
// Tuji ključi
public int AvtoId { get; set; }
public Avto Avto { get; set; }
public int LastnikId { get; set; }
public Lastnik Lastnik { get; set; }
}
// DB CONTEXT
public class AvtoContext : DbContext
{
public AvtoContext(DbContextOptions<AvtoContext> options) : base(options) { }
public DbSet<Avto> Avti { get; set; }
public DbSet<Lastnik> Lastniki { get; set; }
public DbSet<Prodaja> Prodaje { get; set; }
}
// P.S. V Program.cs dodaj:
// builder.Services.AddDbContext<AvtoContext>(options => options.UseSqlite("Data Source=avtohisa.db"));
2
Web API - Branje (GET)
Vključuje filtriranje po dveh parametrih (Znamka in Model) ter OpenAPI dokumentacijo.
Endpoints/AvtoEndpoints.cs
public static class AvtoEndpoints
{
public static void MapEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/avti").WithTags("Avtomobili");
// 1. GET VSI + FILTRIRANJE
group.MapGet("/",
[EndpointSummary("Išči avtomobile")]
[EndpointDescription("Vrne seznam avtomobilov. Omogoča filtriranje po znamki in modelu.")]
(AvtoContext db, [FromQuery] string? znamka, [FromQuery] string? model) =>
{
var query = db.Avti.AsQueryable();
if (!string.IsNullOrEmpty(znamka))
query = query.Where(a => a.Znamka.Contains(znamka));
if (!string.IsNullOrEmpty(model))
query = query.Where(a => a.Model.Contains(model));
return Results.Ok(query.ToList());
});
// 2. GET PO ID
group.MapGet("/{id}",
[EndpointSummary("Pridobi avto")]
[ProducesResponseType(typeof(Avto), 200)]
[ProducesResponseType(404)]
(AvtoContext db, int id) =>
{
var avto = db.Avti.Find(id);
return avto != null ? Results.Ok(avto) : Results.NotFound("Avto s tem ID ne obstaja.");
});
}
}
3
Web API - Dodajanje (POST)
Vključuje dodajanje enega avtomobila in Batch Insert (uvoz seznama avtomobilov).
Endpoints/AvtoPostEndpoints.cs
// 1. Dodajanje ENEGA avtomobila
app.MapPost("/api/avti",
[EndpointSummary("Nov avto")]
(AvtoContext db, Avto avto) =>
{
db.Avti.Add(avto);
db.SaveChanges();
// Vrne 201 Created in povezavo do novega vira
return Results.Created($"/api/avti/{avto.Id}", avto);
});
// 2. BATCH INSERT (Dodajanje seznama)
app.MapPost("/api/avti/uvoz",
[EndpointSummary("Uvoz seznama avtomobilov")]
[EndpointDescription("Prejme seznam avtomobilov in jih vse shrani v bazo.")]
(AvtoContext db, List<Avto> seznamAvtov) =>
{
// AddRange je ključen za dodajanje seznama
db.Avti.AddRange(seznamAvtov);
db.SaveChanges();
return Results.Created("/api/avti", $"{seznamAvtov.Count} avtomobilov uspešno uvoženih.");
});
4
Web API - Urejanje in Brisanje
Endpoints/AvtoCrudEndpoints.cs
// PUT: Posodobitev cene avtomobila
app.MapPut("/api/avti/{id}", (AvtoContext db, int id, Avto podatki) =>
{
var avto = db.Avti.Find(id);
if (avto == null) return Results.NotFound();
avto.Cena = podatki.Cena;
avto.Model = podatki.Model;
// ... posodobi ostalo ...
db.SaveChanges();
return Results.NoContent(); // 204 OK (brez vsebine)
});
// DELETE: Brisanje avtomobila
app.MapDelete("/api/avti/{id}", (AvtoContext db, int id) =>
{
var avto = db.Avti.Find(id);
if (avto == null) return Results.NotFound();
db.Avti.Remove(avto);
db.SaveChanges();
return Results.NoContent();
});
5
Transakcija (ACID)
Scenarij: Prodaja avtomobila novemu lastniku.
1. Ustvarimo novega lastnika v tabeli
2. Zabeležimo prodajo v tabeli
Če karkoli spodleti, ne želimo imeti v bazi lastnika brez nakupa, zato uporabimo Rollback.
1. Ustvarimo novega lastnika v tabeli
Lastniki.2. Zabeležimo prodajo v tabeli
Prodaje.Če karkoli spodleti, ne želimo imeti v bazi lastnika brez nakupa, zato uporabimo Rollback.
Endpoints/TransakcijaEndpoint.cs
app.MapPost("/api/prodaja/transakcija",
[EndpointSummary("Izvedi prodajo (Transakcija)")]
(AvtoContext db, int avtoId, string imeKupca, string priimekKupca) =>
{
// 1. ZAČETEK TRANSAKCIJE
using var transakcija = db.Database.BeginTransaction();
try
{
// Korak A: Preveri, če avto obstaja
var avto = db.Avti.Find(avtoId);
if (avto == null) throw new Exception("Avto ne obstaja!");
// Korak B: Ustvari kupca (Tabela Lastniki)
var kupec = new Lastnik { Ime = imeKupca, Priimek = priimekKupca, Telefon = "000-000" };
db.Lastniki.Add(kupec);
db.SaveChanges(); // Tukaj dobimo kupec.Id
// Korak C: Zabeleži prodajo (Tabela Prodaje)
var prodaja = new Prodaja
{
AvtoId = avto.Id,
LastnikId = kupec.Id, // Povežemo z novim kupcem
DatumProdaje = DateTime.Now,
KoncnaCena = avto.Cena
};
db.Prodaje.Add(prodaja);
db.SaveChanges();
// 2. POTRDITEV (Commit)
transakcija.Commit();
return Results.Ok($"Prodaja uspešna! Prodaja ID: {prodaja.Id}");
}
catch (Exception ex)
{
// 3. PREKLIC (Rollback) - Baza se vrne v prvotno stanje
transakcija.Rollback();
return Results.Problem($"Napaka pri prodaji: {ex.Message}");
}
});
6
gRPC - Proto Definicija
Datoteka avto.proto v mapi Protos.
Protos/avto.proto
syntax = "proto3";
option csharp_namespace = "AvtoGrpcService";
package avto;
service AvtoService {
rpc PridobiAvto (AvtoIdRequest) returns (AvtoResponse);
rpc UstvariAvto (UstvariAvtoRequest) returns (AvtoResponse);
rpc PridobiVse (Empty) returns (SeznamAvtovResponse);
rpc IzbrisiAvto (AvtoIdRequest) returns (StatusResponse);
}
message Empty {}
message AvtoIdRequest {
int32 id = 1;
}
message UstvariAvtoRequest {
string znamka = 1;
string model = 2;
int32 letnik = 3;
double cena = 4; // double za decimalna števila
}
message AvtoResponse {
int32 id = 1;
string znamka = 2;
string model = 3;
int32 letnik = 4;
double cena = 5;
}
message SeznamAvtovResponse {
repeated AvtoResponse avti = 1;
}
message StatusResponse {
bool uspeh = 1;
string sporocilo = 2;
}
7
gRPC - Implementacija
Services/AvtoService.cs
using Grpc.Core;
public class AvtoServiceImpl : AvtoService.AvtoServiceBase
{
// Simulacija baze
private static List<AvtoResponse> _baza = new();
private static int _id = 1;
public override Task<AvtoResponse> UstvariAvto(UstvariAvtoRequest request, ServerCallContext context)
{
var nov = new AvtoResponse
{
Id = _id++,
Znamka = request.Znamka,
Model = request.Model,
Letnik = request.Letnik,
Cena = request.Cena
};
_baza.Add(nov);
return Task.FromResult(nov);
}
public override Task<AvtoResponse> PridobiAvto(AvtoIdRequest request, ServerCallContext context)
{
var najden = _baza.FirstOrDefault(a => a.Id == request.Id);
if (najden == null)
throw new RpcException(new Status(StatusCode.NotFound, "Ni avta"));
return Task.FromResult(najden);
}
public override Task<SeznamAvtovResponse> PridobiVse(Empty request, ServerCallContext context)
{
var odziv = new SeznamAvtovResponse();
odziv.Avti.AddRange(_baza);
return Task.FromResult(odziv);
}
}
8
gRPC - Konzolni Klient
Client/Program.cs
using Grpc.Net.Client;
using AvtoGrpcService;
var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new AvtoService.AvtoServiceClient(channel);
Console.WriteLine("--- Avtohiša gRPC Demo ---");
// 1. Ustvari nove avtomobile
await NovAvto("BMW", "X5", 2022, 65000);
await NovAvto("Audi", "A6", 2023, 55000);
// 2. Pridobi vse
Console.WriteLine("\nSeznam vozil:");
var vsi = await client.PridobiVseAsync(new Empty());
foreach(var a in vsi.Avti)
{
Console.WriteLine($"#{a.Id} {a.Znamka} {a.Model} ({a.Cena} EUR)");
}
// 3. Pridobi po ID
try {
Console.WriteLine("\nIščem ID 1...");
var avto = await client.PridobiAvtoAsync(new AvtoIdRequest { Id = 1 });
Console.WriteLine($"Najden: {avto.Znamka} {avto.Model}");
} catch (Exception ex) {
Console.WriteLine("Napaka: " + ex.Message);
}
Console.ReadLine();
async Task NovAvto(string znamka, string model, int letnik, double cena)
{
var r = await client.UstvariAvtoAsync(new UstvariAvtoRequest
{
Znamka = znamka,
Model = model,
Letnik = letnik,
Cena = cena
});
Console.WriteLine($"Ustvarjen: {r.Znamka} {r.Model}");
}