Pokazywanie postów oznaczonych etykietą linq. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą linq. Pokaż wszystkie posty

wtorek, 22 kwietnia 2014

[C#|Visual Studio] LINQ: join

Łączenie dwóch kolekcji w LINQ można wykonać na kilka sposobów:
  • inner join
  • group join
  • left outer join
Podstawowy i najczęściej używany sposób to inner join, czyli po prostu zwykłe złączenie kolekcji, przy czym jeżeli istnieją elementy, które nie mają swoich odpowiedników w drugiej kolekcji, to zostaną pominięte.

internal class Team
{
    public int Id { get; set; }
    public string Name { get; set; }
}

internal class Player
{
    public int Id { get; set; }
    public int TeamId { get; set; }
    public string Name { get; set; }
}

//...

var teams = new Team[3];
for (int i = 0; i < teams.Length; i++)
{
    teams[i] = new Team(){Id = i};
}

teams[0].Name = "Liverpool";
teams[1].Name = "Chelsea";
teams[2].Name = "Arsenal";

var players = new Player[3];
for (int i = 1; i <= players.Length; i++)
{
    players[i-1] = new Player() { Id = i - 1, TeamId =  i};

}

players[0].Name = "Hazard";
players[1].Name = "Ramsey";
players[2].Name = "Rooney";

var inner = from player in players
            join team in teams on player.TeamId equals team.Id
            select new
            {
                player.Name,
                TeamName = team.Name
            };

Console.WriteLine("Inner join:");
foreach (var result in inner)
{
    Console.WriteLine("{0}, {1}", result.Name, result.TeamName);
}    

Wypisane zostaną tylko dwa elementy, te, których indeksy TeamId pokrywają się z identyfikatorami drużyn (player[0], player[1])

Group join to konstrukcja sprowadzająca wynik do niepłaskiej struktury przypominającej wynik operacji group by. Operacja ta związana jest ze słowem kluczowym into, a jej wynik jest typu IEnumerable<IEnumerable<T>>. Możemy na tym oczywiście wywołać select spłaszczający wyniki.

var group = from team in teams
            join player in players on team.Id equals player.TeamId
                into playersWithTeams
            select new { Key = team.Name, Count = playersWithTeams.Count() };

foreach (var item in group)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Count);
}

Left outer join przydaje się tam, gdzie z jakichś powodów chcemy dostać wszystkie elementy z kolekcji będącej lewą stroną operacji. Problemem jest, jak przedstawić elementy, które nie mają swojego odpowiednika w kolekcji, będącej prawą stroną join-a. W takim przypadku LINQ dostarcza extension metodę DefaultIfEmpty<T>, gdzie możemy podać, jak ma wyglądać taki "sztuczny" element.

var outer = from player in players
            join team in teams on player.TeamId equals team.Id
                into playersWithTeams
            from item in playersWithTeams.DefaultIfEmpty(new Team() {Name = "<Empty>"})
            select new
                       {
                           item.Name,
                           Player = player.Name
                       };

foreach (var item in outer)
{
    Console.WriteLine("{1}, {0}", item.Name, item.Player);
}            

Zostaną wypisane wszystkie trzy elementy kolekcji players.

wtorek, 6 sierpnia 2013

[C#|Visual Studio] LINQ to XML

LINQ to XML, jak nazwa wskazuje umożliwia wykonywanie zapytań LINQ na plikach XML wczytywanych do pamięci za pomocą klasy XDocument. Przykładowy plik XML:

<?xml version="1.0" encoding="utf-8" ?>
<peaks>
  <peak>
    <name>Rysy</name>
    <height unit="mnpm">2499</height>
  </peak>
  <peak>
    <name>Swinica</name>
    <height unit="mnpm">2301</height>
  </peak>
  <peak>
    <name>Kasprowy Wierch</name>
    <height unit="mnpm">1987</height>
  </peak>
  <peak>
    <name>Szpiglasowy Wierch</name>
    <height unit="mnpm">2172</height>
  </peak>
</peaks> 

Plik taki możemy odpytywać przy użyciu zapytań LINQ, np.:

//Queries
XDocument xmlDocument = XDocument.Load("tatra.xml");

var twoThousand = from peak in xmlDocument.Root.Elements("peak")
                  let height = int.Parse(peak.Element("height").Value)
                  where height > 2000
                  select peak.Element("name").Value;

foreach (var peak in twoThousand)
    Console.WriteLine(peak);

LINQ to XML to nie tylko odczyt, ale także wygodna możliwość modyfikacji. Aby dodać nowy szczyt wystarczy skorzystać z klasy XElement, dla której jednym z argumentów jest params umożliwiający budowanie drzewa. Przykład poniżej:

//Modifying
var peaks = xmlDocument.Root;
peaks.Add(new XElement("peak", new XElement("name", "Zawrat"),
                       new XElement("height", new XAttribute("unit", "mnpm"), 2159)));

xmlDocument.Save("tatra2.xml");


Również usuwanie elementów jest bardzo proste.

//Deleting
var highest =
    peaks.Elements("peak").OrderByDescending(x => int.Parse(x.Element("height").Value))
    .FirstOrDefault();
highest.Remove();

xmlDocument.Save("tatra3.xml");

poniedziałek, 11 marca 2013

[NoSQL] RavenDB: Queries

Provider LINQ dla RavenDb implementuje interfejs IQueryable<T>, co oznacza między innymi, że zbudowanie zapytania nie jest tożsame z zapytaniem do bazy. Dane pobierane są w momencie materializacji, czyli np. gdy wywołamy na obiekcie IQueryable funkcje ToArray, ToList, First, Last, Take itd.
using (var session = store.OpenSession())

{

    var audi = from car in session.Query<Car>()

               where car.Make.StartsWith("Audi")

               select car;

    MessageBox.Show(String.Format("Audi count: {0}", audi.Count()));

}

W zapytaniach można także używać agregatów, np do policzenia średniej:
using (var session = store.OpenSession())

{

    var avg = session.Query<Car>().ToArray().Average(c => c.Price);

    MessageBox.Show(String.Format("Average Price: {0}",avg));

}

Nic nie stoi także na przeszkodzie, by używać projekcji - mechanizmu, w którym pobieramy tylko fragment dokumentu z bazy o takich polach jakie sobie wybierzemy w typie anonimowym. Należy pamiętać, że zmiany na takich obiektach nie będą śledzone przez klienta i nie zostaną uwzględnione przy operacji SaveChanges().
using (var session = store.OpenSession())

{

    var bestengines = (from car in session.Query<Car>()

                       orderby car.EnginePower descending

                       select new {car.Make, car.EnginePower}).Take(5);

    var s = Enumerable.Aggregate(bestengines, "",

        (current, bestengine) => current + (bestengine.Make + ", " + bestengine.EnginePower + "\n"));

    MessageBox.Show(String.Format("Best engines: {0}", s));

}

Klient Ravena wspiera także paginację, a więc możemy pobierać dane porcjami z bazy za pomocą operatorów Skip i Take
var bestengines = (from car in session.Query<Car>()
orderby car.EnginePower descending
select new { car.Make, car.EnginePower }).Skip(5).Take(5);

wtorek, 5 marca 2013

[NoSQL] RavenDB: CRUD

W tym poście przedstawione zostaną podstawowe operacje dodawania, odczytu, usuwania i modyfikowania dokumentów składowanych w bazie RavenDB.

Dostęp do API RavenDB uzyskujemy instalując w naszym projekcie klienckie dll-ki Ravena. Najprościej zrobić to przy użyciu managera pakietów NuGet.


Podczas CRUDowych operacji korzysta się z wzorca Unit of Work, to znaczy operacje wykonywane są na obiekcie sesji, a wszystkie zmiany wędrują do serwera przy wywołaniu instrukcji SaveChanges. 

Każdą sesję otwiera obiekt typu IDocumentStore, którego inicjalizację najlepiej wykonać raz, na początku pracy programu, na przykład korzystając ze wzorca singletonu.

public class CarsStore
{
    public static DocumentStore Instance
    {
        get { return NestedClass.instance; }
    }

    public class NestedClass
    {
        static NestedClass()
        {
            var store = new DocumentStore()
                {
                    Url = "http://localhost:8080", 
                    DefaultDatabase = "RavenTest"
                };
            store.Initialize();
            instance = store;
        }

        internal static readonly DocumentStore instance;
    }
}

Dodanie pojedynczego obiektu jest bardzo proste:

var store = CarsStore.Instance;
using (IDocumentSession session = store.OpenSession())
{
    session.Store(new Car()
    {
        EnginePower = powerVal,
        Make = make.Text,
        Price = priceVal,
        Rating = ratingVal
    });
    session.SaveChanges();
}

gdzie Car jest obiektem POCO (zawierającym jedynie propercje, serializowane następnie do JSONa).
Wyniki operacji można oglądać w Management Studio RavenDB, dostępnego w przeglądarce.


Odczyt danych, odbywa się za pomocą zapytań LINQ. Do usuwania z kolecji służy specjalna metoda Delete  z obiektu sesji. Update' ować wystarczy obiekty POCO, ponieważ są one śledzone przez sesję i zmiany lądują w bazie w momencie wywołania SaveChanges.

var store = CarsStore.Instance;
using (var session = store.OpenSession())
{
    var cars = session.Query<Car>().ToArray();

    foreach (var car in cars.Where(c => c.Rating < thresholdVal))
    {
        session.Delete(car);
    }

    foreach (var car in cars.Where(c => c.Rating >= thresholdVal))
    {
        car.Price += 1000;
    }
    session.SaveChanges();
}

niedziela, 17 lutego 2013

[C#|Visual Studio] Threading: PLINQ

.NET Framework w wersji 4.0 udostępnił programistom nową bibliotekę - PFX (Parallel Framework). Wprowadza ona nową jakość w programowaniu wielowątkowym. Jednym z najważniejszych osiągnięć PFX jest PLINQ - mechanizm oferujący automatyzację wszystkich kroków w zrównoleglaniu obliczeń. Kroki te to:
  • podział zadań na Taski 
  • wykonywanie Tasków na wątkach
  • zebranie wszystkich wyników w całość
Parallel LINQ to zatem nic innego, jak równoległa implementacja dobrze znanego LINQ to objects. Kluczem do użycia PLINQ jest specjalna metoda AsParallel(). To dzięki niej, w sposób deklaratywny programista wprowadza zrównoleglenie obliczeń. Prosty przykład to poszukiwanie liczb pierwszych, gdzie podczas filtrowania wykonuje się proste operacje matematyczne.

static void PrimeNumbers()
{
    IEnumerable<int> numbers = Enumerable.Range(3, 100000 - 3);

    var parallelQuery =
      from n in numbers.AsParallel()
      where Enumerable.Range(2, (int)Math.Sqrt(n)).All(i => n % i > 0)
      select n;

    int[] primes = parallelQuery.ToArray();
    foreach (var prime in primes)
    {
        Console.Write("{0} ", prime);
    }
}

Zapytania PLINQ, podobnie jak każde inne LINQ są wykonywane z opóźnieniem, w momencie, gdy np. wywołamy ToArray, pętlę foreach itd. Równoległość obliczeń powoduje jednak, że wyniki będą nieuporządkowane. Jeżeli zależy nam na tym, by wynik wyglądał tak, jak w sekwencyjnym zapytaniu, możemy dodatkowo wywołać metodę AsOrdered()

from n in numbers.AsParallel().AsOrdered()

Wywołania takie wpływa oczywiście na pogorszenie wydajności, dlatego takie zachowanie nie pojawia się domyślnie.

PLINQ optymalizuje zrównoleglenie pod kątem możliwości sprzętowych. To jedna z największych zalet zwłaszcza pod kątem przyszłości, gdzie liczba rdzeni w komputerach PC będzie ciągle wzrastać. W przypadku niektórych komputerów, PLINQ może wykonać obliczenia sekwencyjnie pomimo użycia AsParallel(), np. dlatego, że zrównoleglenie może pogorszyć wydajność. Programista ma możliwość wymuszenia równoległości instrukcją

from n in numbers.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism)

Kolejną ciekawą instrukcją jest WithDegreeOfParallelism(), gdzie podajemy ilość Tasków, na których ma być przetwarzane zapytanie. Szczególnie przydatne jest to w przypadku odpytywania WebSerwisów, gdzie tracimy czas nie na obliczeniach na CPU, ale na oczekiwaniu na odpowiedź.

from n in numbers.AsParallel().WithDegreeOfParallelism(6)