Przejdź do głównej zawartości

Tworzenie modelu (klasy) w CloudFlow

Modele w CloudFlow reprezentują encje bazodanowe. Każda klasa musi dziedziczyć po CFEntity, co zapewnia automatyczne pola audytowe.

Szablon klasy

#nullable enable
namespace CloudFlow.Shared.Models.ClientDb
{
/// <summary>
/// Represents a [NAZWA_ENCJI] entity with [OPIS].
/// </summary>
[Table("[NAZWA_TABELI]", Schema = "public")]
public partial class [NAZWA_KLASY] : CFEntity
{
/// <summary>
/// [OPIS POLA]
/// </summary>
[Visible]
[EnableAI]
public string? Name { get; set; }

/// <summary>
/// [OPIS POLA]
/// </summary>
[MaxLength(100)]
public string? Code { get; set; }

/// <summary>
/// Foreign key to parent entity.
/// </summary>
public Guid? ParentId { get; set; }

/// <summary>
/// Navigation property to parent entity.
/// </summary>
[ForeignKey(nameof(ParentId))]
public virtual ParentEntity? Parent { get; set; }

/// <summary>
/// Collection of child entities.
/// </summary>
public virtual ICollection<ChildEntity> Children { get; set; } = [];
}
}

Dziedziczenie po CFEntity

Klasa bazowa CFEntity zapewnia następujące pola:

PoleTypOpis
IdGuidUnikalny identyfikator (PK)
CreatedBystring?Użytkownik tworzący
CreatedOnDateTime?Data utworzenia
UpdatedBystring?Użytkownik aktualizujący
UpdatedOnDateTime?Data aktualizacji
Descriptionstring?Opis encji
IsNewRecordboolCzy rekord jest nowy (nie mapowane)
EntityMessagesObservableCollection<Message>Komunikaty walidacji
Automatyczne audytowanie

Pola CreatedBy, CreatedOn, UpdatedBy, UpdatedOn są automatycznie wypełniane przez system podczas operacji zapisu.

Atrybuty walidacji i UI

AtrybutOpisPrzykład
[Required]Pole wymagane[Required] public string Name { get; set; }
[MaxLength(n)]Maksymalna długość[MaxLength(255)] public string? Email { get; set; }
[Visible]Widoczne w gridzie[Visible] public string? Code { get; set; }
[EnableAI]Dostępne dla AI fill[EnableAI] public string? Description { get; set; }
[NotMapped]Nie mapowane do DB[NotMapped] public string FullName => $"{FirstName} {LastName}";
[ForeignKey]Klucz obcy[ForeignKey(nameof(ParentId))]
[Table]Nazwa tabeli[Table("Products", Schema = "public")]

Pliki tłumaczeń (.resx)

Dla każdej klasy utwórz pliki zasobów obok pliku .cs:

Product.cs
Product.resx # Domyślne (angielskie)
Product.pl-PL.resx # Polskie tłumaczenia

Przykład zawartości Product.pl-PL.resx:

NameValue
NameNazwa produktu
CodeKod produktu
PriceCena
DescriptionOpis
Generowanie plików .resx

W Visual Studio możesz kliknąć prawym przyciskiem na klasę i wybrać "Add Resource File" aby wygenerować szablon.

Typy właściwości

Typy proste

public string? Name { get; set; }           // Tekst
public int Quantity { get; set; } // Liczba całkowita
public decimal Price { get; set; } // Kwota
public bool IsActive { get; set; } // Boolean
public DateTime? CreatedDate { get; set; } // Data i czas
public DateOnly? StartDate { get; set; } // Tylko data

Enumy

public ProductStatus Status { get; set; } = ProductStatus.Active;

public enum ProductStatus
{
[Display(Name = "Aktywny")]
Active = 0,

[Display(Name = "Nieaktywny")]
Inactive = 1,

[Display(Name = "Wycofany")]
Discontinued = 2
}
Display attribute

Zawsze używaj atrybutu [Display(Name = "...")] dla wartości enum - jest używany w dropdownach UI.

Relacje

// Many-to-One (klucz obcy)
public Guid? CategoryId { get; set; }

[ForeignKey(nameof(CategoryId))]
public virtual Category? Category { get; set; }

// One-to-Many (kolekcja)
public virtual ICollection<ProductVariant> Variants { get; set; } = [];

Typy złożone (Owned Types)

// W modelu
public CFAddress Address { get; set; } = new CFAddress();

// CFAddress jest typem owned - pola są w tej samej tabeli
public class CFAddress
{
public string? Street { get; set; }
public string? City { get; set; }
public string? PostalCode { get; set; }
}

Przykład kompletnej klasy

#nullable enable
using static CloudFlow.Shared.Models.EnumsLibrary;

namespace CloudFlow.Shared.Models.ClientDb
{
/// <summary>
/// Represents a product in the inventory system.
/// </summary>
[Table("Products", Schema = "public")]
public partial class Product : CFEntity
{
/// <summary>
/// The name of the product.
/// </summary>
[Required]
[Visible]
[EnableAI]
[MaxLength(255)]
public string Name { get; set; } = string.Empty;

/// <summary>
/// The unique product code (SKU).
/// </summary>
[Visible]
[MaxLength(50)]
public string? Code { get; set; }

/// <summary>
/// The unit price of the product.
/// </summary>
[Visible]
public decimal Price { get; set; }

/// <summary>
/// The current quantity in stock.
/// </summary>
public int Quantity { get; set; }

/// <summary>
/// The status of the product.
/// </summary>
public ProductStatus Status { get; set; } = ProductStatus.Active;

/// <summary>
/// Foreign key to the category.
/// </summary>
public Guid? CategoryId { get; set; }

/// <summary>
/// Navigation property to the category.
/// </summary>
[ForeignKey(nameof(CategoryId))]
public virtual Category? Category { get; set; }

/// <summary>
/// Collection of product variants.
/// </summary>
public virtual ICollection<ProductVariant> Variants { get; set; } = [];
}

/// <summary>
/// Product status enumeration.
/// </summary>
public enum ProductStatus
{
[Display(Name = "Aktywny")]
Active = 0,

[Display(Name = "Nieaktywny")]
Inactive = 1,

[Display(Name = "Wycofany")]
Discontinued = 2
}
}

Migracja bazy danych

Po utworzeniu modelu wygeneruj i zastosuj migrację:

cd CloudFlow/Server
dotnet ef migrations add AddProductsTable --context CompanyContext
dotnet ef database update --context CompanyContext
Backup przed migracją

Na środowisku produkcyjnym zawsze wykonaj backup bazy przed uruchomieniem migracji.

Best Practices

Nazewnictwo

ElementKonwencjaPrzykład
Klasa modeluPascalCase, liczba pojedynczaProduct, OrderItem
Tabela DBPascalCase, liczba mnogaProducts, OrderItems
WłaściwośćPascalCaseProductName, UnitPrice
Klucz obcy[Encja]IdCategoryId, CustomerId

Komentarze XML

Każda publiczna klasa i właściwość powinna mieć komentarz XML:

/// <summary>
/// Represents a product in the inventory system.
/// </summary>
[Table("Products", Schema = "public")]
public partial class Product : CFEntity
{
/// <summary>
/// The unique product code (SKU).
/// </summary>
public string? Code { get; set; }
}

Nullable reference types

Zawsze używaj #nullable enable i oznaczaj typy nullable:

#nullable enable
public string? OptionalField { get; set; }
public string RequiredField { get; set; } = string.Empty;

Inicjalizacja kolekcji

// ✅ Dobre - nowoczesna składnia
public virtual ICollection<Child> Children { get; set; } = [];

// ✅ Dobre - tradycyjna składnia
public virtual ICollection<Child> Children { get; set; } = new List<Child>();

// ❌ Złe - null reference exception
public virtual ICollection<Child> Children { get; set; }

Walidacja

Używaj atrybutów walidacji:

[Required(ErrorMessage = "Nazwa jest wymagana")]
[MaxLength(255, ErrorMessage = "Nazwa może mieć maksymalnie 255 znaków")]
public string Name { get; set; } = string.Empty;

[Range(0, double.MaxValue, ErrorMessage = "Cena musi być dodatnia")]
public decimal Price { get; set; }

[EmailAddress(ErrorMessage = "Nieprawidłowy format email")]
public string? Email { get; set; }

Następne kroki