niedziela, 13 października 2019

LINQ Aggregate

Wielu z Was zapewne nie spotyka często użycia funkcji Aggregate w kodzie. Nie ma w tym nic dziwnego, przeważnie używamy funkcji Select, Where, Sum, Average, Any, All, Count, First, Single itd. Aggregate jest gdzieś na samym końcu a nie rzadko w ogóle nie występuje w słowniku funkcji LINQ.

Warto poświęcić kilka minut i poznać jej możliwości. Definicja funkcji:


public static TResult Aggregate<TSource,TAccumulate,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, 
TAccumulate seed, 
Func<TAccumulate,TSource,TAccumulate> func, 
Func<TAccumulate,TResult> resultSelector);



Wygląda skomplikowanie ale tak na prawdę nie ma tutaj nic nadzwyczajnego. Poszczególne parametry:
source - kolekcja wejściowa na której wykonujemy operacje
seed - wartość początkowa, można to porównać do zmiennej "sum", która za zadanie ma poinformować nas o sumie elementów np. tablicy. Przed sumowaniem ustawiamy wartość takiej zmiennej na 0. Tutaj mamy do czynienia z tym samym mechanizmem.
func - funkcja wywoływana dla każdego elementu kolekcji wejściowej. Posiada dwa argumenty wejściowe - zmienną akumulacyjną (czyli przykładowo zmienna sum) oraz element z kolekcji wejściowej.
resultSelector - pozwala dodatkowo zmodyfikować/przetransformować ostateczny rezultat

Zobaczmy na prosty przykład. Chcemy policzyć sumę liczb parzystych które otrzymujemy jako dane wejściowe w postaci tablicy. Dodatkowo każdą sumowaną liczbę podwoimy (wyobraźmy sobie, że w rzeczywistości może to być znacznie bardziej skomplikowany warunek. Przykładowa implementacja:


        private static long SumDoubleEvenNumbers(IEnumerable<int> numbers)
        {
            return numbers
                .Where(n => n % 2 == 0)
                .Select(n => n + n)
                .Sum(x => (long)x);
        }


Nic nadzwyczajnego. Zobaczmy jakby wyglądał kod z zastosowaną funkcją Aggregate:


        private static long SumDoubleEvenNumbers_Aggregate(IEnumerable<int> numbers)
        {
            return numbers
                .Aggregate(0L, (coutner, number) => coutner += number % 2 == 0 ? (number + number) : 0);
        }



Kod może wydawać się początkowo bardziej skomplikowany, ale de fakto nie ma tutaj nic trudnego. Na początku ustawiamy początkową wartość naszego akumulatora (zmiennej przechowującej wynik) na 0L co powoduje zastosowanie typu long. Następna część funkcji podstawia pod zmienną sumy (counter) kolejne liczby parzyste.

Zastanówmy się czy powyższy kod jest tylko zmianą kosmetyczną. Aby to sprawdzić dokonajmy pomiaru ile trwa wykonanie jednej i drugiej wersji programu:


        static void Main(string[] args)
        {
            var numbers = Enumerable.Range(1, 1000000);
            long sum;
            
            var stopwatch = Stopwatch.StartNew();
            sum = SumDoubleEvenNumbers(numbers);
            stopwatch.Stop();
            Console.WriteLine($"Sum: {sum}, ElapsedMiliseconds: {stopwatch.ElapsedMilliseconds}");
            Console.WriteLine();

            stopwatch = Stopwatch.StartNew();
            sum = SumDoubleEvenNumbers_Aggregate(numbers);
            stopwatch.Stop();
            Console.WriteLine($"Sum: {sum}, ElapsedMiliseconds: {stopwatch.ElapsedMilliseconds}");
            Console.WriteLine();
        }

        private static long SumDoubleEvenNumbers(IEnumerable<int> numbers)
        {
            return numbers
                .Where(n => n % 2 == 0)
                .Select(n => n + n)
                .Sum(x => (long)x);
        }

        private static long SumDoubleEvenNumbers_Aggregate(IEnumerable<int> numbers)
        {
            return numbers
                .Aggregate(0L, (coutner, number) => coutner += number % 2 == 0 ? (number + number) : 0);
        }



Wyniki:



W przypadku Aggregate mamy ponad dwukrotny wzrost wydajności. Dzieje się tak dlatego, że w przypadku poprzedniego kodu musiały zostać utworzone dodatkowe obiekty dla funkcji Where, Select. W przypadku Aggregate wszystko zostało scalone do jednej funkcji.

sobota, 12 października 2019

C# 8.0 zmiany w interfejsie (domyślne metody, pola, modyfikatory dostępu)

Microsoft wydał niedawno C# 8.0 wraz z .NET Core 3.0. Nowości nie brakuje, większość była znana już przed premierą. Ten wpis chciałbym poświęcić domyślnej implementacji metody interfejsu. Jak każdy wie - interfejs do wersji C# 8.0 mógł posiadać tylko sygnatury metod, właściwości (properties), zdarzeń (events) lub indeksatorów (indexer).
Od wersji 8.0 interfejs może posiadać  także posiadać domyślną implementację metody jak i kilka innych nowości.

Zacznijmy od domyślnej implementacji metody. Dla programistów Javy nie jest to nowość - domyślna implementacja metody istnieje od wersji 8 Javy. Można by powiedzieć, że C# także to umożliwia od wersji 8.0 (przypadek :) ? ). Spójrzmy na przykładowy interfejs:


    public interface IFile
    {
        void SaveFile(string filePath, string fileContent);
    }


Powyższy interfejs zgodnie z nazwą powinien pozwalać operować na plikach. Obecnie posiada jedną metodę - SaveFile która pozwala zapisać plik. Jak wiadomo na plikach można wykonać znacznie więcej operacji. Od klienta przychodzi nowe zlecenie - proszę udostępnić operację odczytu pliku. Nic prostszego - stwierdzamy, że nie ma sensu męczyć osoby implementujące nasz interfejs i dodajemy do niego nową metodę z domyślną implementacją. W taki oto sposób powstaje kod:


    public interface IFile
    {
        void SaveFile(string filePath, string fileContent);
        string ReadFile(string filePath)
        {
            return File.ReadAllText(filePath);
        }
    }

    public class SystemFile : IFile
    {
        public void SaveFile(string filePath, string fileContent)
        {
            File.WriteAllText(filePath, fileContent);
        }
    }

Użycie:

            IFile file = new SystemFile();
            file.ReadFile("pathToFile");

Jeżeli używamy słowa kluczowego var podczas deklarowania typu obiektu należy pamiętać o tym, że typ SystemFile nie zawiera implementacji metody ReadFile. Mamy dwa wyjścia: jawnie zadeklarować typ jak powyżej, bądź rzutować tworzony obiekt na interfejs.

Wydawałoby się, że domyślna implementacja jest wręcz rewelacyjną wiadomością dla programistów C#. Już nie musimy martwić się, że dodając nową operację do interfejsu nasi klienci otrzymają błąd kompilacji. Ale czy tak na pewno jest?

Powyższe rozumowanie należy uzupełnić o potencjalne problemy wynikające z stosowania tego rozwiązania. Aby to pokazać dodam drugą implementację naszego interfejsu:


    public class AwsFile : IFile
    {
        public void SaveFile(string filePath, string fileContent)
        {
            var s3Client = new AmazonS3Client(RegionEndpoint.EUCentral1);
            var fileTransferUtility = new TransferUtility(s3Client);
            fileTransferUtility.Upload(filePath, "bucketName");
        }
    }

Jak widać sprawa się komplikuje. Klasa AwsFile pozwala zapisać plik w AWS.S3. Trudno sobie wyobrazić, że implementacja domyślna działająca na systemie plików pozwoli odczytać dane także w S3. Nie jest to możliwe - użytkownik prawdopodobnie otrzyma błąd. Należy więc przemyśleć czy każda domyślna implementacja ma faktycznie sens i jest na tyle uniwersalna aby ją udostępnić naszym klientom. Zauważyć można, że aż do wersji 8 nie posiadaliśmy domyślnej implementacji i doskonale sobie bez niej radziliśmy. Warto więc przemyśleć kiedy ma to sens a kiedy nie.

Oprócz powyższej zmiany interfejsy obecnie pozwalają na deklarowanie pól, statycznych sygnatur elementów interfejsu jak i stosowanie dowolnego modyfikatora dostępu (tak, tak już na rekrutacji nie trzeba bać się pytania "jaki modyfikator posiadają elementy interfejsu" :)).


    public interface IFile
    {
        void SaveFile(string filePath, string fileContent);
        string ReadFile(string filePath)
        {
            return File.ReadAllText(filePath);
        }

        private static void DoSomething()
        {

        }

        protected static void DoSomething2()
        {

        }

        private static string defaultPath;

        public static void SetDefaultPath(string path)
        {
            defaultPath = path;
        }
    }


Jak widać sporo zmian. Podsumowując od wersji 8.0 C# umożliwia następujące nowości dla interfejsów:

  • domyślna implementacja metod
  • możliwość deklaracji pól
  • statyczne elementy
  • dodawanie modyfikatorów dostępu

piątek, 11 października 2019

SQL - zwracanie wielu wyników w pojedynczym wierszu

Od dłuższego czasu nie publikowałem nowych wpisów na blogu, czas to zmienić :)

Podczas ostatniego zadania potrzebowałem wyselekcjonować wyniki w jednym wierszu. Najłatwiej będzie mi wytłumaczyć zagadnienie na przykładzie:


Powyżej wycinek bazy danych z jakim na pewno nie jeden z Was spotkał się podczas codziennej pracy. Chciałbym wyselekcjonować id-ki przedmiotów dla każdego zamówienia. Nic trudnego:


select o.OrderID, si.StockItemID
from Sales.Orders o JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID
 JOIN Warehouse.StockItems si ON ol.StockItemID = si.StockItemID
order by o.OrderID;





Rezultat jak widać powyżej łatwy do przewidzenia - lista zamówień wraz z pozycjami zamówienia. Co jeżeli chcielibyśmy teraz przykładowo scalić wyniki do jednego wiersza?

2 - 10, 50
4 - 130, 260, 50
itd.

Dla większości baz danych istnieją gotowe funkcje które pozwalają w prosty sposób scalać wyniki do pojedynczego wiersza. MS SQL posiada przykładowo funkcję STRING_AGG która w najprostszej postaci przyjmuje tylko dwa argumenty: wyrażenie do agregacji oraz separator wyników.


select o.OrderID, STRING_AGG(si.StockItemID, ', ') as 'Ordered items Ids'
from Sales.Orders o JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID
 JOIN Warehouse.StockItems si ON ol.StockItemID = si.StockItemID
group by o.OrderID
order by o.OrderID;

Wynik:


 



 Możemy sobie łatwo wyobrazić, że chcielibyśmy w tym miejscu posiadać np. kody produktów, nazwy zakupionych dóbr. Co więcej otrzymane rezultaty możemy posortować. Przykład powyżej pokazuje, że agregowane dane nie uwzględniają kolejności Id zakupionych przedmiotów. Łatwo można to zmienić następującą konstrukcją:


select o.OrderID, STRING_AGG(si.StockItemID, ', ') WITHIN GROUP (ORDER BY si.StockItemID)  as 'Ordered items Ids'
from Sales.Orders o JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID
 JOIN Warehouse.StockItems si ON ol.StockItemID = si.StockItemID
group by o.OrderID
order by o.OrderID;


Rezultat:


 


Oczywiście podobne funkcje istnieją w innych silnikach bazodanowych, dla przykładu w Oracle - LISTAGG:


SELECT o.OrderID, LISTAGG(si.StockItemID, ', ') WITHIN GROUP (ORDER BY si.StockItemID) "Ordered items Ids"
from Sales.Orders o JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID
 JOIN Warehouse.StockItems si ON ol.StockItemID = si.StockItemID
group by o.OrderID
order by o.OrderID;

poniedziałek, 3 marca 2014

3 egzaminy za 1

Microsoft jakiś czas temu udostępnił bardzo fajną promocję: płacąc za 1 egzamin możemy zdawać aż 3. Czasu nie pozostało wiele - promocja kończy się końcem maja 2014.
Jeżeli ktoś jest zainteresowany więcej szczegółów można znaleźć na stronie http://borntolearn.mslearn.net/btl/b/veronica/archive/2013/11/01/t-and-c-for-developer-exam-offer-2013.aspx#fbid=P7LajA7aSYu
Warto skorzystać zwłaszcza w takiej cenie :)

piątek, 28 lutego 2014

Autoryzacja w MVC 5 - ASP.NET Identity

Kiedyś pisałem o MVC 2 później 3, 4 a teraz przyszedł czas na nowości dotyczące 5 wersji frameworka. Wraz z MVC wypuszczono także nową bibliotekę pozwalającą autoryzować użytkowników - Microsoft Identity. Ma ona za zadanie zastąpić przestarzały MembershipProvider i nie końca udany SimpleMembership.
Co tym razem przygotował Microsoft? Możliwości ASP.NET Identity:
  • łatwe dodawanie dodatkowych informacji do profilu użytkownika
  • domyślnie użycie Entity Framework jako ORM
  • kontrola zapisu (możliwość wyboru czy dane mają zostać zapisane w SQL Azure, MS SQL, NOSQL itp)
  • wsparcie dla TDD dzięki dużo lepszej architekturze frameworka
  • wsparcie dla ról w aplikacji
  • system nadawania praw do poszczególnych aspektów aplikacji
  • zewnętrzna autoryzacja (Facebook, Twitter itp)
Jak widać całkiem sporo możliwości. Szczególnie ostania opcja jest przydatna w obecnych czasach. Dodatkowo po stworzeniu projektu wszystko co jest potrzebne jest już dla nas stworzone i jedyne co musimy zrobić aby korzystać z np. Twittera jako sposobu autoryzacji to podanie danych do konta naszej aplikacji.
Schemat bazy ASP.NET Identity przedstawia się następująco:


Dużo przyjemniejszy niż w przypadku starego MembershipProvidera. Dodawanie nowych kolumn i informacji o użytkowniku też nie sprawia problemów:
Wystarczy zlokalizować klasę IdentityUser i dodać w niej nowe pole:


Code:
using System;
using Microsoft.AspNet.Identity.EntityFramework;

namespace AuthorizationSample.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        public DateTime? BirthDate { get; set; }
        public string City { get; set; }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
    }
}

Jeżeli korzystamy z mechanizmu automatycznych migracji, wystarczy że utworzymy migrację i uaktualnimy bazę danych.

Projekt domyślnie tworzy szablonowe widoki tworzenia i modyfikowania danych użytkownika. Dostajemy gotowe klasy modeli. Jedynie co brakuje to metod odpowiedzialnych za widoki. Jeżeli chcemy użyć innej klasy (np. już istniejącej w systemie) do przechowywania informacji o użytkowniku, wystarczy że zaimplementujemy interfejs IUser:

Code:
  public interface IUser
  {
    /// <summary>
    /// Unique key for the user
    /// </summary>
    /// 
    /// <returns>
    /// The unique key for the user
    /// </returns>
    string Id { get; }

    string UserName { get; set; }
  }

Jak widać Id użytkownika przetrzymywane jest tutaj jako pole typu string - i takiego też typu jest domyślnie tworzone w bazie danych. Czy to dobre rozwiązanie? Moim zdaniem nie do końca. Dużo lepszym rozwiązaniem byłoby pozwolenie deweloperowi wybrania czy Id ma być łańcuchem, Guidem czy też liczbą. W nowej wersji frameworka (beta) poprawiono tę niedogodność.

Kod bibliotek .NET Online

Microsoft udostępnił nową wersję portalu umożliwiającego przeglądanie kodu bibliotek .NET.
Dostęp do narzędzia dostępny jest pod adresemhttp://referencesource-beta.microsoft.com/



Narzędzie jest proste w użyciu, nawigacja nie sprawia problemów. Wygląd, kolorowanie i podział na "Solution Explorer" dodatkowo uprzyjemnia przeglądanie.
W przypadku braku dostępu do narzędzi typu Reflector czy ILSpy świetnie się sprawdzi w celu szybkiego prześledzenia kodu w bibliotece .NET.

sobota, 12 października 2013

Czy .NET ma przyszłość?

Z pewnością wielu zadaje sobie to pytanie podchodząc do nauki nowej platformy.
Języka programowania można nauczyć się bardzo szybko - znając jeden opanowanie kolejnego zajmie maksymalnie dzień.
Nauka platformy i frameworków może zająć miesiące a nawet dłużej. Frameworki ewoluują, dostarczają nowe lepsze rozwiązania często pojawiających się problemów. Nauka i zmiana przyzwyczajeń związana jest z każdą nową wersją.
Naturalnie pojawia się więc pytanie czy warto jest inwestować swój czas w daną platformę, tutaj .NET. Świetnie na to pytanie odpowiada Rockford Lhotka. Zachęcam do przeczytania jego postu na ten temat - http://www.lhotka.net/weblog/DoesNETHaveAFuture.aspx.