Wzorzec Repozytorium - jak wyświetlić kategorie w dropdownlistfor?

0

Mam klasy i do nich interfejsy

[Table("Product")]
    public class Product
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
        public int ProductId { get; set; }

        [Required(ErrorMessage = "Product Name is required.")]
        [DisplayName("Product Name")]
        [MaxLength(50)]
        public string PorductName { get; set; }

        public int CategoryId { get; set; }  

        public virtual Category Category { get; set; }
    }

    public class EFProductRepository : IProductRepository
    {
        private EFContext context = new EFContext();

        public IEnumerable<Product> GetAllProducts
        {
            get { return context.Products; }        
        }       

        public void ProductCreate(Product p)
        {
            context.Products.Add(p);
            context.SaveChanges();
        }
    }

oraz

[Table("Category")]
    public class Category
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
        public int CategoryId { get; set; }

        [Required(ErrorMessage = "Category is required.")]
        [DisplayName("Category Description")]
        [MaxLength(50)]
        public string CategoryDescription { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }

    public class EFCategoryRepository : ICategoryRepository
    {
        private EFContext context = new EFContext();

        public IEnumerable<Category> Categories
        {
            get { return context.Categories; }
        }
    
    }

Controller

public class ProductController : Controller
    {

        private IProductRepository repository;

        public ProductController(IProductRepository repo)
        {
            repository = repo;        
        }
       
        public ActionResult ProductList()
        {
            ProductListViewModel plvm = new ProductListViewModel
            {
                Products = repository.GetAllProducts                
            };

            return View(plvm);
        }

        [HttpGet]
        public ActionResult ProductCreate()
        {
            return View();        
        }

        [HttpPost]
        public ActionResult ProductCreate(Product product)
        {
            if (ModelState.IsValid)
            {
                TempData["Save"] = "ok";
              //  return View(product);
            }

            return View(product);       
        }
    } 

Widok

@model WebDev.Models.Product
(...)
@using (Html.BeginForm("ProductCreate", "Product", FormMethod.Post))
        { 
            <p>
                @Html.LabelFor(model => model.PorductName)
                @Html.TextBoxFor(model => model.PorductName)
                @Html.ValidationMessageFor(model => model.PorductName)
            </p>     
            
            <p>
                @Html.LabelFor(model => model.Category.CategoryDescription)
                @Html.DropDownListFor(model => model.CategoryId, new SelectList(@Model.Category, "CategoryId", "CategoryDescription"), "Choose Category", null)
            </p>   
            
            <input type="submit" value="Save" />
        }

Jak zgodnie ze sztuką wzorca projektowego wyświetlić wszystkie kategorie z encji "Categories" w kontrolce widoku DropDownListFor aby potem móc utworzyć w tabeli nowy rekord. Używając wyłącznie modeli widoku, bez ViewBagów itp.

1

Repozytorium nie ma żadnego związku z GUI, z żadnymi dropdownami, z żadnymi ViewBagami.

Twój problem polega na tym, że próbujesz używać w GUI obiektu reprezentującego strukturę danych w bazie. Sprawy by nie było, gdybyś nie łączył tych dwóch oddzielnych światów i po prostu utworzył klasę ViewModel, który będzie zawierał dane o Produkcie, czyli ProductName oraz CategoryId oraz dodatkowe dane potrzebne do edycji, czyli w Twoim przypadku listę wszystkich kategorii.

PS Dlaczego masz właściwość, która ma w nazwie Get? Przecież jeśli coś jest wyrażeniem z czasownikiem w trybie rozkazującym, to musi być metodą. No i raczej CreateProduct niż ProductCreate. No i raczej ta metoda powinna nazywać się Add, bo ona dodaje produkt do listy, a nie go tworzy.

PPS Tak ogólniej: http://commitandrun.pl/2016/05/11/Repozytorium_najbardziej_niepotrzebny_wzorzec_projektowy/

0

Dzięki @somekind. Tak, chciałem użyć ViewModel np. jak poniżej

public class ProductViewModel
    {
        public Product Product { get; set; }

        // to (1)
        public IEnumerable<Category> Categories { get; set; }
        // lub (2)
        public IEnumerable<Category> Categories
        {
            get
            {                
                return new EFContext().Categories;            
            }           
        }
    }

tylko, że (2) to łamie tzw. zależności, gdzie zależność klas miedzy sobą nie powinna występować lub występować jak najrzadziej. Innym sposobem, aby pominąć tą zależność klas to zastosowanie (1) i w konstruktorze kontrolera zastosować dwa repozytoria IProduct i ICategory - pytanie czy to zgodne? Czytałem, że tak, że kontroler może zawierać kilka repozytoriów.

Nazewnictwo metod i właściwości trochę małoważna. Ale dzięki za info. Też dziękuję za link, trochę zrozumiałem, trochę nie. Bardziej mi zależy na rozwiązaniu na powyższe.

1
szymon7500 napisał(a):

Dzięki @somekind. Tak, chciałem użyć ViewModel np. jak poniżej

public class ProductViewModel
    {
        public Product Product { get; set; }
        // to (1)
        public IEnumerable<Category> Categories { get; set; }
        // lub (2)
        public IEnumerable<Category> Categories
        {
            get
            {                
                return new EFContext().Categories;            
            }           
        }
    }

To nie jest ViewModel, bo:

  1. Jest tam jakiś EFContext, a ViewModel to ma być proste DTO, które tylko przechowuje dane i nie wykonuje żadnych operacji.
  2. ViewModel nie powinien mieć żadnych powiązań z PersistenceModelem (u Ciebie klasy Product, Category). ViewModel może zawierać właściwości typów "prostych" oraz inne ViewModele.

Taka klasa powinna wyglądać tak:

public class ProductVewModel
    {
        public int ProductId { get; set; }
 
        [Required(ErrorMessage = "Product Name is required.")]
        [DisplayName("Product Name")]
        [MaxLength(50)]
        public string PorductName { get; set; }
 
        public int CategoryId { get; set; }  
 
        public IList<CategoryViewModel> Categories { get; set; }
    }

w konstruktorze kontrolera zastosować dwa repozytoria IProduct i ICategory - pytanie czy to zgodne? Czytałem, że tak, że kontroler może zawierać kilka repozytoriów.

Kontroler nie może zawierać żadnego repozytorium. Repozytorium to logika biznesowa, kontroler to warstwa prezentacji. Wstrzykiwanie repozytoriów do kontrolera, to tak, jakby w samochodzie położyć silnik na siedzeniu pasażera.

Ale Twoje repozytoria to nie są żadne repozytoria, bo nie piszesz aplikacji zgodnie z Domain Driven Design. To po prostu Data Access Object, który nazwałeś repozytorium, bo pewno tak przeczytałeś w tutorialu. (99% tutoriali kłamie.) Te Twoje obiekty to tak naprawdę wrappery na klasy Entity Frameworka. Możesz ich kilka sobie wstrzyknąć do kontrolera, tylko czy one cokolwiek Ci dają? Skoro już chcesz pisać prostą, jednowarstwową aplikację, to nie prościej po prostu wstrzyknąć jeden DbContext?

Nazewnictwo metod i właściwości trochę małoważna.

Wręcz przeciwnie. To jest ważniejsze niż języki i technologie, w których piszesz, niż biblioteki, których używasz, niż wzorce, które stosujesz. Kod pisze się po to, aby inni programiści mogli go przeczytać bez mruczenia pod nosem: "o co tu chodzi?" albo "co za debil to pisał?". Konfundujące nazwy i niechlujstwo czynią kod nieczytelnym.

0

Dzięki wielkie za odpowiedź.

Przykład wziąłem z książki Adama Freeman-a, z małą przeróbką jeśli chodzi o kategorie. Chciałem, aby kategorie były w bazie, w oddzielnej tabeli i podczas dodawania produktu można byłoby je wybrać za pomocą DropDownList i zapisać jako id w tabeli Product. Zrobiłem podobnie jak było to zrobione z Product-em, ponieważ chciałem zadziałać niejako na wielu repozytoriach. Mam dwa repozytoria, jeden kontroler, jak poniżej

(...)
        private IProductRepository repository;
        private ICategoryRepository cat;

        public ProductController(IProductRepository repo, ICategoryRepository categories)
        {
            repository = repo;
            cat = categories;
        }
(...)

Coś na wzór: http://sampathloku.blogspot.com/2012/10/how-to-use-viewmodel-with-aspnet-mvc.html

ViewModel skopałem, na szybko robione i chciałem mieć szybki efekt na potrzeby odpowiedzi.

0

Reasumując: czy istnieje możliwość nauczenia się wzorców ze stron internetowych lub z książek? Czy to daremny trud?

3

Teorii tak. Tylko trzeba patrzeć na sensowne strony i sensowne książki, a nie jakieś hinduskie blogi, CodeProject czy tutoriale Entity Frameworka, bo to nie jest miejsce na naukę architektury.

http://gorodinski.com/
http://blog.sapiensworks.com/
https://lostechies.com/
http://martinfowler.com

I najważniejsze, nie ograniczać się do jednego miejsca tylko każdą swoją wątpliwość sprawdzać w kilku.

0

Podobnie jak twórca tematu jestem początkujący i korzystam z książki Freemana o ASP.NET MVC. Im bardziej się w nią zagłębiam i im więcej czytam o wzorcu repozytorium i MVC tym mniej rozumiem z tego co "autor miał na myśli".

somekind - napisałeś: "Kontroler nie może zawierać żadnego repozytorium. Repozytorium to logika biznesowa, kontroler to warstwa prezentacji. Wstrzykiwanie repozytoriów do kontrolera, to tak, jakby w samochodzie położyć silnik na siedzeniu pasażera."

Bazując na przykładach z książki Freemana również mam wstrzyknięte repozytorium do kontrolera w swoim projekcie. Jeśli jest to uchybienie, to jak powinno wyglądać powiązanie kontrolera z repozytorium, aby za jego pośrednictwem pobierał dane z bazy?

 private ITicketRepository ticketRepository;

        public TicketController(ITicketRepository ticketRepository)
        {
            this.ticketRepository = ticketRepository;
        }
0
MMSz napisał(a):

Jeśli jest to uchybienie, to jak powinno wyglądać powiązanie kontrolera z repozytorium, aby za jego pośrednictwem pobierał dane z bazy?

Wcale nie powinny być powiązane. Kontroler powinien odwoływać się do jakiegoś serwisu wystawiającego API biznesowe aplikacji, ten serwis ma pod sobą serwisy domenowe, encje, repozytoria i całe to DDD. Zaś sam serwis (z którego kontrole korzysta) może zwracać jakieś DTO albo nawet gotowe ViewModele.

A jak nie chcesz mieć tak skomplikowanego projektu z DDD, to po prostu nie używaj repozytoriów, tylko wstrzyknij sobie kontekst ORMa do kontrolera. Będziesz miał w aplikacji dokładnie tyle samo warstw, co tym biedarepozytorium Freemana.

0

Projekt nad którym pracuję ma dla mnie charakter typowo edukacyjny. Myślę, że na moim pułapie wiedzy podejście DDD, które sugerujesz to jeszcze za wysoka półka - w końcu newbie. :) Może się mylę - może brak mi czegoś co nazywam wzorcem wzorca. ;)

Zastanawiam się czy w ogóle początkujący jak ja powinni tak głęboko wnikać w architekturę. Freeman używa "biedarepozytorium" w swoim szkoleniowym przykładzie. Może właśnie zaimplementowanie takiego biedarepozytorium ma być wstępem, który ułatwi w niedalekiej przyszłości stworzenie repozytorium z prawdziwego zdarzenia? Chociaż rozumiem, że doświadczony programista powie, że to nauka złych nawyków.

To, że w ogóle to całe repozytorium jest "jakimś" ogólnie znanym wzorcem projektowym dowiedziałem się chyba dwa tygodnie po zaimplementowaniu pierwszych biedarepozytoriów w swoim projekcie :). Potem przeczytałem dziesiątkę postów i artykułów o tym, że repozytorium jest samo w sobie złe, a jeżeli ktoś stwierdzał, że złe nie jest, to w ślad za tym szła jednak opinia, że w przynajmniej 99% przypadków wzorzec jest niepoprawnie implementowany/rozumiany (m.in. na smutnym blogu o programistycznej rzeczywistości). Ja jestem "wannabe programmer" i pewnie, że chciałbym, żeby architektura mojej aplikacji była cycuś-glancuś, ale ja jeszcze nie łapię co gdzie umieszczać w projekcie MVC... Po kolei.

No właśnie. Załóżmy, że pójdę ścieżką nr 2 i będę wstrzykiwał kontekst ORM-a do kontrolera. Wypada jeden trybik z maszyny - biedarepozytoria. Zabrałem się za EF w podejściu Code First, napisałem odpowiednie klasy, stworzona została struktura bazy danych. Na razie jest anemia w modelu. Wcześniej zdawało mi się, że metody do operowania na obiektach biznesowych mają znajdować się w tych pseudorepozytoriach. Jeśli nie to gdzie? Jak uniknąć logiki biznesowej w kontrolerze? Czy klasy POCO powinny zawierać również metody? Wiem, że z perspektywy programisty ze stażem pytania mogą wydawać się śmieszne, ale mam wrażenie, że tutoriale i książki, z których korzystałem w ogóle nie nauczyły mnie odpowiadać na takie pytania...

2
MMSz napisał(a):

Zastanawiam się czy w ogóle początkujący jak ja powinni tak głęboko wnikać w architekturę. Freeman używa "biedarepozytorium" w swoim szkoleniowym przykładzie. Może właśnie zaimplementowanie takiego biedarepozytorium ma być wstępem, który ułatwi w niedalekiej przyszłości stworzenie repozytorium z prawdziwego zdarzenia?

Rozumiem upraszczanie czegoś dla początkujących, rozumiem upraszczanie na potrzeby prostych aplikacji, nie rozumiem błędnego nazewnictwa uczącego, że ogórek jest arbuzem.

Chociaż rozumiem, że doświadczony programista powie, że to nauka złych nawyków.

Niestety nie - oni też uczyli się z książek i tutoriali powielających błędy, więc sami je powielają.

To, że w ogóle to całe repozytorium jest "jakimś" ogólnie znanym wzorcem projektowym dowiedziałem się chyba dwa tygodnie po zaimplementowaniu pierwszych biedarepozytoriów w swoim projekcie :).

Ja bym raczej powiedział, że ta nazwa jest powszechnie używana, ale sam wzorzec ogólnie nieznany. :)

Wcześniej zdawało mi się, że metody do operowania na obiektach biznesowych mają znajdować się w tych pseudorepozytoriach.

Taki jest właśnie efekt pokazywania początkującym pseudorepozytoriów, które są używane przez kontrolery, zamiast od razu pokazać jakiś sensowny kawałek kodu.

Jeśli nie to gdzie? Jak uniknąć logiki biznesowej w kontrolerze?

Przenieść ją do innej klasy. Np. jakiegoś serwisu, z którego korzysta kontroler (a ten serwis niech operuje na anemicznym modelu i kontekście ORMa). Albo do obiektów (encji) biznesowych - niech np. faktura sama ma metodę dodającą do niej nową pozycję. (Tylko z takich encji też nie powinno korzystać się bezpośrednio w kontrolerach.)

Czy klasy POCO powinny zawierać również metody?

Wtedy już nie będą POCO. :)

Wiem, że z perspektywy programisty ze stażem pytania mogą wydawać się śmieszne, ale mam wrażenie, że tutoriale i książki, z których korzystałem w ogóle nie nauczyły mnie odpowiadać na takie pytania...

E tam, nie ma nic śmiesznego w konkretnych pytaniach na sensowne tematy.

0
[somekind napisał(a)]

ViewModel nie powinien mieć żadnych powiązań z PersistenceModelem (u Ciebie klasy Product, Category). ViewModel może zawierać właściwości typów "prostych" oraz inne ViewModele.

Właściwie to dlaczego tak jest? Pomijam już fakt, że w wielu tutorialach i u tego nieszczęsnego Freemana również powiązanie ViewModelu z persistence modelem jest normą. Załóżmy, że potrzebuję uzyskać efekt jak w przykładzie przedstawiownym przez @szymon7500. Chcę wszystkich danych o produkcie i listę kategorii do wyświetlenia w formularzu edycji. Intuicja mówi:

public class ProductEditViewModel {
public Product Product { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}

Dla mnie byłoby to niesamowite uproszczenie sprawy, a mapowanie odbyło by się chwila moment. Nie twierdzę oczywiście, że tak powinno być. Pytam jedynie, dlaczego tak nie powinno być? W czym tkwi haczyk, czyli o czym jeszcze nie wiem? :)

1

Na moje oko powyższe rozwiązanie wygląda nienaturalnie. Pierwsza właściwość może powodować problemy z filtrami i gdy trzeba będzie dodać dodatkowe właściwości, to będzie brzydko wyglądało, np. w celu edycji użytkownika trzeba podać hasło i potwierdzenie hasła, ale bazę danych interesuje tylko jedno hasło. Druga właściwość według mnie nie powinna wymuszać używania w widoku SelectList, widok powinien sam sobie z tym poradzić. Ja użyłbym IEnumerable<Category>, chociaż domyślam się, że typ kolekcji też powinien być jakimś DTO. Też chętnie usłyszę poprawną odpowiedź.

0

Też myślałem o użyciu IEnumerable<Category>.

0
MMSz napisał(a):

Właściwie to dlaczego tak jest?

Bo klasa powinna mieć tylko jeden powód do zmiany (SRP), a taka uniwersalna klasa, która się zapisuje do bazy danych, wyświetla w interfejsie, parzy kawę i stepuje, ma zdecydowanie więcej powodów do zmian niż powinna.
Druga rzecz to separation of concerns, obiekty z jednej warstwy nie powinny wyciekać do innych warstw. Co za tym idzie, nie trzeba np. referencjonować ORMa w warstwie GUI.

Chcę wszystkich danych o produkcie

Serio? Potrzebujesz jego id, daty ostatniej edycji albo nazwiska pracownika, który go utworzył?

Pytam jedynie, dlaczego tak nie powinno być? W czym tkwi haczyk, czyli o czym jeszcze nie wiem? :)

To napisz kod klasy Product, tak aby:

  1. Zawierał Id, nazwę, opis, numer produktu, kategorię, podkategorię, producenta, cenę, masę oraz wymiary zewnętrzne.
  2. Na widoku listy produktów ma się wyświetlać numer oraz nazwy: produktu, kategorii, podkategorii i producenta
  3. W widoku tworzenia wszystko poza Id ma być edytowalne.
  4. W widoku edycji dla zwykłego pracownika wszystko poza id oraz numerem produktu i ceną ma być edytowalne.
  5. W widoku edycji dla kierownika możliwa ma być także edycja numeru produktu oraz ceny.
  6. W widoku podglądu produktu dla klienta widoczna była cena dostawy. (W normalnym viewmodelu byłaby to po prostu właściwość, obliczona w serwisie na podstawie danych pobranych z API firmy kurierskiej na podstawie wymiarów i masy.)
Burmistrz napisał(a):

Ja użyłbym IEnumerable<Category>, chociaż domyślam się, że typ kolekcji też powinien być jakimś DTO.

Właściwie nie widzę powodu tworzenia specyficznego typu kolekcji w takim przypadku.

0

Czy nie warto tworzyć prostych repozytoriów* nawet tylko dla samego ułatwienia pisania testów jednostkowych? Nie podoba mi się mockowanie Entity Framework z tutorialu https://www.asp.net/web-api/overview/testing-and-debugging/mocking-entity-framework-when-unit-testing-aspnet-web-api-2#testcontext. Wydaje m się, że łatwiej by było mockować repozytorium, niż tworzyć taki skomplikowany sztuczny obiekt. Jest na to jakieś łatwiejsze i sensowne rozwiązanie?

*Proste repozytoria, czyli takie, które zawierają metody:

  • Get(lista filtrów, wybór sortowania, podział na strony),
  • GetById(),
  • Insert(),
  • Update(),
  • Delete().
0

@Burmistrz, tylko po co to nazywać Repozytorium, a nie np. Dao?

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