Serwis do komunikacji z bazą danych a customowe obiekty

0

Cześć.

Mam pytanie obnośnie tworzenia serwisów do obsługi zapisu i odczytu danych z kontekstu bazy danych. Do tej pory zapis i odczyt danych robiłem w kontrolerach api, jednak czasem logika była na tyle rozbudowana że kod w kontrolerze był ogromny i mało czytelny. Wydzieliłem całą logikę do serwisów obsługujących komunikację z bazą danych, ale tu się pojawia pewien problem. Nie zawsze potrzebuję zwrócić w akcji kontrolera cały obiekt jaki pozyskuję z bazy danych, czasem potrzebuję zwrócić do klienta obiekt customowy zawierający częściowe dane z dwóch tabel. Bezpośrednio w kontrolerze mogłem zwrócić customowy obiekt wygenerowany w select, gdzie wybierałem tylko określone właściwości które miały polecieć do klienta.

Co w przypadku serwisu, mogę zwrócić dowolny obiekt, ale wcześniej musi zostać on zadeklarowany. A więc czy tworzyć dla takich customowych obiektów customowe klasy opisujące kształt obiektu ? Dla przykładu dwie klasy opisujące dane w tabeli

public class Area
{
    public int id { get; set; }
    public string name { get; set; }
    public string description { get; set; }
    public string manager { get; set; }
    public string location { get; set; }
    public virtual List<Person> Persols { get; set; }
}

public class Person
{
    public int id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
    public int areaId { get; set; }
    public Area Area {get; set; }
}


class CustoomPerson
{
    public int id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
    public string areaName {get; set; } //<<właściwość name z obiektu Area
}

W tym przypadku CustoomPerson mogę podać jako typ zwracany z metody serwisu.

public class Servive: IService
{
    public CustoomPerson GetPerson(string name)
    {
        //logika pobierająca dane z bazy danych
    }
}

Tylko pytanie, czy to jest dobre podejście.

2

Myślę, że nawet całkiem normalne.

0

Jeszcze myślałem zwracać z serwisu obiekty jakie otrzymuję z kontekstu bazy danych i w akcji kontrolera tworzyć lokalnie customowy obiekt przekazywany do klienta HTTP.
Tylko nie wiem która metoda będzie lepsza.

1

Najlepiej robić takie operacje jak najbliżej źródła danych, czyli bazy.
Jeśli masz jakiegoś ORMa, to możesz jego o nie poprosić.

Tak na marginesie - to nie są customowe obiekty, a DTO będące projekcją danych z bazy.

0

Ja to robię tak:

public class Area
{
    public int id { get; set; }
    public string name { get; set; }
    public string description { get; set; }
    public string manager { get; set; }
    public string location { get; set; }
    public virtual List<Person> Persols { get; set; }
}

public class Person
{
    public int id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
    public int areaId { get; set; }
    public Area Area {get; set; }
}


public class PersonDto // Łatwo to zmapować
{
    public int id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }
    public int areaId { get; set; }
}
public class GetAllPeopleDto // Mniejsza ilość danych do wyświetlenia w tabeli
{
    public PersonDto Person { get; set; }
    public string AreaName { get; set; }
}
public class GetPersonDto // Wszystkie potrzebne dane do wyświetlenia szczegółów
{
    public PersonDto Person { get; set; }
    public string AreaName { get; set; }
    public string AreaDescription { get; set; }
    public string AreaManager { get; set; }
    public string AreaLocation { get; set; }
}

public async Task<List<GetAllPeopleDto>> GetAllPeople(GetAllPeopleInput input);
public async Task<GetPersonDto> GetPerson(int id);

Jak pobierasz coś z bazy to proponuję zrobić nastepująco:

public class FilterInput
{
    public string FilterText { get; set; }
    public int Skip { get; set; }
    public int Take { get; set; }
}
public class GetAllPeopleInput : FilterInput
{
    // Tutaj możesz dodać inne filtry dla danej metody
}

public class PeopleService
{
    public PeopleService(){
    
    }

    private IQueryable<Person> Filter(GetAllPeopleInput input)
    {
        return _appDbContext.People
            .Include(x => x.Area)
            .Where(x => string.IsNullOrEmpty(input.FirstName) ? true : x.FirstName.Contains(input.FilterText))
            .Skip(input.Skip)
            .Take(input.Take)
    }
    public async Task<List<GetAllPeopleDto>> GetAllPeople(GetAllPeopleInput input)
    {
        var dbData = Filter(input);
        List<GetAllPeopleDto> getAllPeopleDtos = new List<GetAllPeopleDto>();
        foreach(var person in dbData){
            getAllPeopleDtos.Add(new GetAllPeopleDto(){
                Person = _mapper.Map(person),
                AreaName = person.Area.Name
            })
        }
        return getAllPeopleDtos;
    }
}
1

@gswidwa1: tylko w ten sposób pobierasz z bazy wszystkie dane, a dopiero po stronie aplikacji wybierasz te potrzebne. To nie jest optymalne podejście.

0

Ale na przykład dla 100 rekordów czy to ma jakieś znaczenie? Milisekunda dłużej, bo nie pobieram wszystkich danych tylko odpowiednią ilość po filtrach

0

Dla 100 rekordów żadne. Ale jak na forum pytasz o rozwiązanie to zazwyczaj dostajesz odpowiedź "jak powinno się to zrobić zgodnie ze sztuką".

2
somekind napisał(a):

@gswidwa1: tylko w ten sposób pobierasz z bazy wszystkie dane, a dopiero po stronie aplikacji wybierasz te potrzebne. To nie jest optymalne podejście.

Wystarczyłoby użyć ProjectTo<T> z Automappera zamiast robić jakieś ręczne mapowanie w foreach i byłoby git. Praktycznie tak sam case jest opisany w dokumentacji
https://docs.automapper.org/en/stable/Queryable-Extensions.html

gswidwa1 napisał(a):

Ale na przykład dla 100 rekordów czy to ma jakieś znaczenie? Milisekunda dłużej, bo nie pobieram wszystkich danych tylko odpowiednią ilość po filtrach

Tu nie chodzi o czas, ale o sens. Jeśli żadnym kosztem (a w zasadzie zyskiem, bo oszczędzamy sobie pisania) można coś zrobić lepiej to jedynym argumentem przeciwko jest niewiedza.

0

@Saalin: Nie wiem czy dobrze że odnawiam wątek, ale tutaj na przykład pobieram tylko te dane z bazy które mnie interesują. I wygląda to raczej dobrze. Co sądzisz?

public async Task<PagedResultDto<GetProductMovementHistoryForViewDto>> GetAllMovementHistories(GetAllProductMovementHistoriesInput input)
        {

            var filteredProductMovementHistories = _productMovementHistoriesRepository.GetAll()
                        .Include(e => e.ProductFk)
                        .Include(e => e.VehicleFk)
                        .Include(e => e.UserFk)
                        .Include(e => e.BuildingFk)
                        .Include(e => e.ClientFk)
                        .Include(e => e.StationaryObjectFk)
                        .Include(e => e.MobileObjectFk)
                        .Where(x => !input.ProductId.HasValue ? true : x.ProductId == input.ProductId);
                        
            var pagedAndFilteredProductMovementHistories = filteredProductMovementHistories
                .OrderBy(input.Sorting ?? "creatingTime desc")
                .PageBy(input);

            var productMovementHistories = from o in pagedAndFilteredProductMovementHistories
                                           join o1 in _vehicleRepository.GetAll() on o.VehicleId equals o1.Id into j1
                                           from s1 in j1.DefaultIfEmpty()

                                           join o2 in _usersRepository.GetAll() on o.UserId equals o2.Id into j2
                                           from s2 in j2.DefaultIfEmpty()

                                           join o3 in _buildingRepository.GetAll() on o.BuildingId equals o3.Id into j3
                                           from s3 in j3.DefaultIfEmpty()

                                           join o4 in _clientsRepository.GetAll() on o.ClientId equals o4.Id into j4
                                           from s4 in j4.DefaultIfEmpty()

                                           join o5 in _stationaryObjectsRepository.GetAll() on o.StationaryObjectId equals o5.Id into j5
                                           from s5 in j5.DefaultIfEmpty()

                                           join o6 in _mobileObjectsRepository.GetAll() on o.MobileObjectId equals o6.Id into j6
                                           from s6 in j6.DefaultIfEmpty()

                                           join o7 in _productsRepository.GetAll().Include(x => x.ProductDefinitionFk) on o.ProductId equals o7.Id into j7
                                           from s7 in j7.DefaultIfEmpty()

                                           join o8 in _usersRepository.GetAll() on o.SourceUserId equals o8.Id into j8
                                           from s8 in j8.DefaultIfEmpty()

                                           select new
                                           {
                                               o.CreatingTime,
                                               o.HistoryType,
                                               o.UserCreated,
                                               o.MobileObjectId,
                                               o.StationaryObjectId,
                                               o.BuildingId,
                                               o.VehicleId,
                                               o.UserId,
                                               o.ClientId,
                                               o.SourceBuildingId,
                                               o.SourceVehicleId,
                                               o.SourceUserId,
                                               o.SourceClientId,
                                               o.SourceStationaryObjectId,
                                               o.SourceMobileObjectId,
                                               Id = o.Id,
                                               o.ProductId,
                                               VehicleRegistrationNumber = s1 == null || s1.RegistrationNumber == null ? "" : s1.RegistrationNumber.ToString(),
                                               UserName = s2 == null || s2.Name == null ? "" : s2.Name.ToString(),
                                               BuildingName = s3 == null || s3.Name == null ? "" : s3.Name.ToString(),
                                               ClientName = s4 == null || s4.Name == null ? "" : s4.Name.ToString(),
                                               StationaryObjectName = s5 == null || s5.Name == null ? "" : s5.Name.ToString(),
                                               MobileObjectRegistrationNumber = s6 == null || s6.RegistrationNumber == null ? "" : s6.RegistrationNumber.ToString(),
                                               ProductName = s7 == null || s7.ProductDefinitionFk == null ? null : s7.ProductDefinitionFk.Name,
                                               SourceUserName = s8 == null || s8.Name == null ? "" : s8.Name.ToString()
                                           };

            var totalCount = await filteredProductMovementHistories.CountAsync();

            var dbList = await productMovementHistories.ToListAsync();
            var results = new List<GetProductMovementHistoryForViewDto>();

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