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:
| Pole | Typ | Opis |
|---|---|---|
Id | Guid | Unikalny identyfikator (PK) |
CreatedBy | string? | Użytkownik tworzący |
CreatedOn | DateTime? | Data utworzenia |
UpdatedBy | string? | Użytkownik aktualizujący |
UpdatedOn | DateTime? | Data aktualizacji |
Description | string? | Opis encji |
IsNewRecord | bool | Czy rekord jest nowy (nie mapowane) |
EntityMessages | ObservableCollection<Message> | Komunikaty walidacji |
Pola CreatedBy, CreatedOn, UpdatedBy, UpdatedOn są automatycznie wypełniane przez system podczas operacji zapisu.
Atrybuty walidacji i UI
| Atrybut | Opis | Przykł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:
| Name | Value |
|---|---|
| Name | Nazwa produktu |
| Code | Kod produktu |
| Price | Cena |
| Description | Opis |
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
}
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
Na środowisku produkcyjnym zawsze wykonaj backup bazy przed uruchomieniem migracji.
Best Practices
Nazewnictwo
| Element | Konwencja | Przykład |
|---|---|---|
| Klasa modelu | PascalCase, liczba pojedyncza | Product, OrderItem |
| Tabela DB | PascalCase, liczba mnoga | Products, OrderItems |
| Właściwość | PascalCase | ProductName, UnitPrice |
| Klucz obcy | [Encja]Id | CategoryId, 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
- Tworzenie formularza - Jak stworzyć formularz edycji dla modelu
- Tworzenie strony - Jak stworzyć stronę z listą rekordów