.NET 5 i EntityFramework - podstawy

0

Cześć,

w końcu postanowiłem przestać być dinozaurem i tworzę testową aplikację wykorzystującą EntityFramework z podejściem CodeFirst.
Chciałbym żebyście doradzili mi czy dobrze do tego podchodzę.

Chciałbym stworzyć strukturę modeli dla czegoś typu magazyn. na ten moment zakładam następującą sytuację:

  1. Istnieją różne kategorie przedmiotów (enum)
  2. Na podstawie kategorii wczytywana jest lista właściwości przedmiotu (np. inne pola ma do uzupełnienia kategoria A, a inne kategoria B (chociaż niektóre mogą się powtarzać).
  3. Każda właściwość przedmiotu ma dostępne tłumaczenia na różne języki (np. "tytuł" => "title" => "titel" (pl, en, de).

Stworzyłem więc następujące modele:

Definicje:

public class ItemDetailDefinition
	{
		public int Id { get; set; }
		
		[Required]
		public string Name { get; set; }

		[Required]
		public System.Type ValueType { get; set; }

		public ICollection<ItemDetailTranslate> Translations { get; set; }
	}
public class ItemDetailTranslate
	{
		public int Id { get; set; }

		[Required]
		public string Translate { get; set; }

		[Required]
		public string Languages { get; set; }

		public ItemDetailDefinition ItemDetailDefinition { get; set; }
	}

Model mający połączyć definicję właściwości z daną kategorią:

public class ItemDetailDefinitionCategoryType
	{
		public int Id { get; set; }

		[Required]
		public bool IsRequired { get; set; }

		[Required]
		public DepositoryType ItemType { get; set; }

		[Required]
		public ItemDetailDefinition DetailDefinition { get; set; }
	}

zakładam, że w tabelce ItemDetailDefinitionCategoryType będzie wiele wierszy z np. kategorią(ItemType) A i w każdej będzie id z ItemDetailDefinition. W ten sposób będę mógł wyciągnąć wszystkie pola z danej kategorii i zbudować później z tego jakiś widok.

Pytanie tylko jak to połączyć podczas dodawania danego przedmiotu. Przykładowo user wybiera sobie kategorię A, wyświetlają mu sie pola do uzupełnienia, uzupełnia je, zapisuje, i co dalej? Mam wstępnie stworzony model Item:

public class Item
	{
		public int Id { get; set; }

		public DateTime? AddedDate { get; set; }

		[Required]
		public DepositoryType Type { get; set; }

		[Required]
		public ApplicationUser AddedBy { get; set; }
		
	}

Czy teraz powinienem mieć jeszcze model (i tabelę), który będzie miał w sobie Id itemu, Id Definicji pola i wpisaną wartość?

Coś typu:

	public class ItemValues
	{
		public int Id { get; set; }
		public Item Item { get; set; }
		public ItemDetailDefinition ItemDetailDefinition { get; set; }
		public string Value { get; set; }
	}

Widzę tutaj taki problem, że Value to może być dowolny typ, więc muszę użyć stringa.

Czy w ogóle takie podejście jest dobre, czy robi się to inaczej?
Ilość i rodzaj pól może się zmieniać.

2

Zacząłeś z grubej rury. Przede wszystkim pytanie, czy kategorie w enum są ok. Jeśli dojdzie jakaś kategoria, będzie to wymagało zmian w kodzie, a to jest już złe. Zatem kategoria powinna mieć własną tabelę:

class Category
{
  public int Id {get; set;}
  public string Name {get;set;}
}

Ale, jeśli chcesz mieć to przetłumaczalne, to tłumaczenia też powinny być w innej tabeli, np:

class TranslatedCategoryName
{
  public int Id {get;set;}
  public string LangCode {get;set;}
  public string Value {get;set;}
}

Z tego wychodzi, że klasa Category zamiast prostego Name powinna mieć raczej:

class Category
{
  public int Id {get; set;}
  public TranslatedCategoryName Name {get;set;}
}

Potem, jeśli chcesz mieć w różnych kategoriach różne parametry, to ja bym podszedł chyba do zadania tak, że w osobnych tabelach byłyby osobne parametry. To generalnie nie jest łatwe i proste zadanie. Trzeba wiele rzeczy przemyśleć, potem pewnie pozmieniać w trakcie itd. Zacznij od czegoś prostszego. Zrób coś w rodzaju systemu zamówień. Będzie dużo łatwiej zacząć i naprawdę dużo się nauczysz.

0
Juhas napisał(a):

Zacząłeś z grubej rury.

To nie jest tak, że nie wiem nic. Po prostu EF jest dla mnie nowością ;)

Przede wszystkim pytanie, czy kategorie w enum są ok. Jeśli dojdzie jakaś kategoria, będzie to wymagało zmian w kodzie, a to jest już złe. Zatem kategoria powinna mieć własną tabelę:

Dobre pytanie i dobry pomysł ;) Ja po prostu bardziej się skupiłem w moich rozmyślaniach na samym problemie przechowywania informacji o parametrach, ale jak najbardziej się zgadzam.

Potem, jeśli chcesz mieć w różnych kategoriach różne parametry, to ja bym podszedł chyba do zadania tak, że w osobnych tabelach byłyby osobne parametry.

Możesz rozwinąć tą myśl: "że w osobnych tabelach byłyby osobne parametry."?

Zacznij od czegoś prostszego. Zrób coś w rodzaju systemu zamówień. Będzie dużo łatwiej zacząć i naprawdę dużo się nauczysz.

Spróbuje jednak zostać przy tym. Jak rzeczywiście nie pójdzie to naklepie system zamówień :P

0
[Required]
public System.Type ValueType { get; set; }

dlaczego to jest System.Type?

0

@WeiXiao: Chwilowo, żebym pamiętał, że tam ma być typ i wymyślił jakego typu propertiesa użyć ;)

Taki moj brzydki zwyczaj, ktorego musze sie oduczyc.

1
kobi55 napisał(a):
Juhas napisał(a):

Zacząłeś z grubej rury.

To nie jest tak, że nie wiem nic. Po prostu EF jest dla mnie nowością ;)

No właśnie o to chodzi. Później będzie dużo kombinowania z mapowaniem. Dlatego zacząłbym prościej.

Potem, jeśli chcesz mieć w różnych kategoriach różne parametry, to ja bym podszedł chyba do zadania tak, że w osobnych tabelach byłyby osobne parametry.

Możesz rozwinąć tą myśl: "że w osobnych tabelach byłyby osobne parametry."?

To tylko pierwsza myśl, więc nie wiem, czy dobra. Miałem taki "przebłysk". Potraktuj to jako pobieżnie zarysowane tabelki:

CarCategoryParams (ID, CategoryId, Year, Milage, Color)
ComputerCategoryParams (ID, CategoryId, Hdd, Cpu, Model)
TVCategoryParams(ID, CategoryId, ScreenSize, OS)

Categories(ID, Name)

Item(ID, CategoryId, Name, Producent, IsNew)

Nie traktuj pola CategoryId w tabelach parametrów jako redundancji. Nazwy tabel zawsze mogą być zupełnie inne :)
Wydaje mi się, że w tą stronę bym poszedł.

2
kobi55 napisał(a):

w końcu postanowiłem przestać być dinozaurem i tworzę testową aplikację wykorzystującą EntityFramework z podejściem CodeFirst.

No to gratulacje, zostałeś mamutem. ;)

Czy w ogóle takie podejście jest dobre, czy robi się to inaczej?
Ilość i rodzaj pól może się zmieniać.

Ogólnie to, co chcesz osiągnąć nazywa się EAV (entity attribute value) model.

class Category
{
    int Id;  // PK
    MultiString Name;
}

class Attribute
{
    int Id;  // PK
    int CategoryId; // FK
    MultiString Name;
    Type ValueType; // tu chyba enum trzeba zrobić, bo normalny Type się do bazy raczej nie zapisze, no chyba, że jako string, ale to i tak nie ma sensu.
}

class Entity
{
    int Id;
    MultiString Name;
}

class Value
{
    int EntityId;  // PK FK
    int AttributeId;  // PK FK
    string RawValue;
}

To tak w najprostszej wersji. Ogólnie musisz tutaj sam dbać o parsowanie RawValue przy odczycie i serializowanie przy zapisie. Możesz to sobie ułatwić komplikując model w ten sposób:

abstract class Value
{
    int EntityId;  // PK FK
    int AttributeId;  // PK FK
}

class MultiStringValue : Value
{
    MultiString Value;
}

class IntValue : Value
{
    int Value;
}

class DateTimeValue : Value
{
    DateTime Value;
}

// itd.

Tu dochodzi kolejny dylemat, czy mapować to jako TPH (wszystkie klasy w jednej tabeli), TPC (każda klasa w oddzielnej tabeli) czy TPT (oddzielna tabele na klasę bazową i na dziedziczące).

No, a MultiString to po prostu typ na wielojęzyczne napisy, wewnętrznie to słownik Dictionary<string, string>, no i pewnie parę metod oraz konstruktor.
Może mieć po prostu metodę podającą tłumaczenie dla danego kodu języka, a można mu wstrzykiwać aktualny język systemu/aplikacji, aby sam zwrócił wartość dla użytkownika.

Użyłem pól, bo nie chce mi się get i set pisać wszędzie, ale to oczywiście powinny być właściwości.

No i generalnie zastanów się nad tym podejściem. Jest ono często uznawane za antywzorzec, bo jest trudne w implementacji, dość niewydajne i utrudnia raportowanie.

0

To tak w najprostszej wersji. Ogólnie musisz tutaj sam dbać o parsowanie RawValue przy odczycie i serializowanie przy zapisie. Możesz to sobie ułatwić komplikując model w ten sposób:

Tak, tego się spodziewałem :)

No i generalnie zastanów się nad tym podejściem. Jest ono często uznawane za antywzorzec, bo jest trudne w implementacji, dość niewydajne i utrudnia raportowanie.

No właśnie wydaje mi się to zagmatwane. Tylko jak inaczej zrobić to co chcę osiągnąć? Czyli możliwość dodawania w locie nowych kolumn do kategorii.

1

Dodawania nowych kolumn do kategorii się nie da zrobić inaczej. Pytanie, czy to w ogóle jest potrzebne? Czy nie można mieć np. jednej kolumny opisującej wszystkie atrybuty danego produktu?

0
somekind napisał(a):

Pytanie, czy to w ogóle jest potrzebne? Czy nie można mieć np. jednej kolumny opisującej wszystkie atrybuty danego produktu?

Jednej kolumny opisującej wszystkie atrybuty? W sensie trzymać to w jakimś xml/jsonie?

1

Albo nawet czystym tekstem. Zależy do czego to jest potrzebne.

0
somekind napisał(a):

Albo nawet czystym tekstem. Zależy do czego to jest potrzebne.

Tylko czy wtedy da się skorzystać z możliwości np. filtrowania. Teoretycznie mając np. cenę jako normalną kolumnę mogę sobie wyciągnąć wszystkie itemy tańsze niż:

context.Items.Where(x => x.Price < 500).ToList();

jak to zrobić mając taką daną w strukturze xml? EF pozwoli mi to jakoś ogarnąć?

0

Ale cenę to chyba ma każdy produkt, więc jaki w ogóle byłby sens umieszczać ją w dynamicznych atrybutach?

0

@somekind: Po prostu był to pierwszy atrybut, o którym pomyślałem ;) Oczywiście chodzi mi o coś czego nie ma każdy. Lepszym przykładem będzie tutaj data przydatności:

context.Items.Where(x => x.ExpiredDate < DateTime.Now).ToList();

Edit:
Czy jeżeli w modelu Item zrobie sobie jakieś properties NotMapped z listą atrybutów i ich wartości to mogę w trakcie wyciąganai tych danych z db z tego skorzystać?

1

Nie wiem jak działa NotMapped, ale dziwne gdyby jego użycie sprawiało, że coś można z bazy pobrać. No, ale to w końcu EF.

Mnie tu bardziej od dostosowanie architektury do wymagań chodzi, a nie o używanie konkretnego narzędzia.
Gdyby np. te atrybuty miały służyć tylko do opisu, to po prostu wsadzałbym je jako jeden tekstowy opis.
Ale jeśli ma być filtrowanie, no to już sprawa się komplikuje, no bo można:

  • zrobić EAV jak pokazałem wyżej, wówczas wadą jest dużo tabel, dość skomplikowane zapytania z wieloma złączeniami
  • wsadzić XML/JSON z listą atrybutów do kolumny, wówczas struktura bazy jest prosta, ale potrzebne jest wyszukiwanie w XML. Bazy to zazwyczaj potrafią, ale ORM niekoniecznie obsługuje.

Które z tych rozwiązań jest lepsze? Nie mam pojęcia. Moim zdaniem ten akurat aspekt to raczej bazodanowy problem, więc może zajrzy tu jakiś ekspert od baz danych typu @Panczo @wloochacz czy @Marcin.Miga i się wypowie?

2

@somekind:

wsadzić XML/JSON z listą atrybutów do kolumny, wówczas struktura bazy jest prosta, ale potrzebne jest wyszukiwanie w XML. Bazy to zazwyczaj potrafią, ale ORM niekoniecznie obsługuje.

JSON Mapping

1 użytkowników online, w tym zalogowanych: 0, gości: 1