Vodnik po korakih

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: 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 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}");
}