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

poniedziałek, 28 kwietnia 2014

[C#|Visual Studio] C#: Reflection

Reflection to potężne narzędzie, za pomocą którego można zrobić wiele dobrego jak również i wiele złego w każdej .NET-owej aplikacji. Najważniejsze klasy, to Type, Assembly, Activator oraz wszystkie opisujące składowe typów, czyli MethodInfo, PropertyInfo, ConstructorInfo, FieldInfo, EventInfo itd. Poza swobodnym przeglądaniem assemblies za pomocą kodu, reflection ma zastosowanie w operacjach wykonywanych dynamicznie na starcie aplikacji. W każdym innym wypadku musimy liczyć się ze sporym spadkiem wydajności w stosunku do kodu statycznie typowanego.

Klasa assembly pozwala na odczytywanie metadanych z różnych assemblies a także ładowanie ich.

var current = Assembly.GetExecutingAssembly();

Console.WriteLine("Current assembly: " + current.FullName);

var fromGac =
    Assembly.Load("System.Xml.Linq,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089");

var restSharpAssembly = Assembly.LoadFrom("RestSharp.dll");

Console.WriteLine("\nLoaded assembly: " + restSharpAssembly.FullName);
var dependant = restSharpAssembly.GetReferencedAssemblies();

Console.WriteLine("\nReferenced assemblies:");
foreach (var assemblyName in dependant)
    Console.WriteLine("\t" + assemblyName.FullName);

Metoda GetExecutingAssembly zwróci nam informacje o assembly, w którym ją wywołujemy (np. plik exe). Za pomocą metody Load możemy ładować assemblies z Global Assembly Cache, podając full name. Z kolei dla assemblies bez strong name możemy użyć metody LoadFrom lub LoadFile szukających na dysku. Jeżeli interesują nas zależne assemblies, możemy uzyskać do nich dostęp przy użyciu metody GetReferencedAssemblies.

Mając obiekt typu Assembly możemy pobrać wszystkie publiczne typy w nim zdefiniowane - służy do tego metoda GetExportedTypes..

Console.WriteLine("\nPublic types:");
            var types = restSharpAssembly.GetExportedTypes();

            foreach (Type type in types)
                Console.WriteLine("\t" + type);

            var stype = (from type in types
                        orderby type.GetCustomAttributes(true).Count() descending
                        select type).First();

            var instance = Activator.CreateInstance(stype);
            Console.WriteLine("\nCreated type instance:" + stype.FullName);

            var props = stype.GetProperties();
            Console.WriteLine("Properties:");
            foreach (var propertyInfo in props)
                Console.WriteLine("\t" + propertyInfo.Name);

            var tfields = (from type in types
                           let fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                           orderby fields.Count() descending
                           select fields).First();

            foreach (var fieldInfo in tfields)
                Console.WriteLine("\t" + fieldInfo.Name);

Na każdym z typów możemy sprawdzić, jakie zdefiniowano atrybuty (GetCustomAttributes), a także pobrać wszystkie properties (GetProperties) czy pola (GetFields), także te prywatne, ustawiając odpowiednie flagi. Klasa Activator pozwala nam na tworzenie instancji dowolnego typu (rzutowanej do object). Mając taką instancję możemy pobrać MethodInfo i wywołać taką metodę przekazując do niej parametry.

var sw = new Stopwatch();
sw.Start();
var jsonArray = new JsonArray();
for (int i = 0; i < 1000000; i++)
{
    jsonArray.Add(i);
}
Console.WriteLine("Invoking instance method: {0} ms", sw.ElapsedMilliseconds);
sw.Restart();

var methodInfo = stype.GetMethod("Add");
for (int i = 0; i < 1000000; i++)
{
    methodInfo.Invoke(instance, new object[] {i});
}
Console.WriteLine("Invoking method via reflection: {0} ms", sw.ElapsedMilliseconds);
sw.Reset();

Porównanie takie wypada zdecydowanie na niekorzyść Reflection, głównie przez iterowanie po metadanych i boxing parametrów wejściowych.

Invoking instance method: 83 ms
Invoking method via reflection: 2241 ms

czwartek, 13 grudnia 2012

[SQL|ORM] Entity Framework : Loading Entities

Entity Framework udostępnia szereg mechanizmów umożliwiających sterowanie sposobem ładowania encji powiązanych w zapytaniach.Domyślnie ładowane są jedynie encje bezpośrednio wołane w zapytaniu. Czasami jednak potrzebujemy zredukować ilość zapytań do bazy danych i pobrać zarówno bezpośrednio interesujące nas encje, jak i encje powiązane za pomocą jednego zapytania. Wiąże się to jednak ze znacznym zwiększeniem grafu obiektów w pamięci. Nie ma jednoznacznej odpowiedzi na to, jak powinno się konstruować zapytania, wiele zależy od ilości danych i częstości korzystania z danych powiązanych.

Ładowanie encji powiązanych

W tym celu należy skorzystać z funkcji Include(), gdzie przez string podaje się nazwę navigation property..

var customers = context.Customers.Include("CustomerType").
                    Include("CustomerEmails");
foreach (var customer in customers)
{
    Console.WriteLine("Name : {0}, Email : {1}", customer.Name, 
        customer.CustomerType.Description);
    foreach (var customerEmail in customer.CustomerEmails)
    {
        Console.WriteLine(customerEmail.Email);
    }
}

Wykorzystując funkcję Include powyższy kod przetransformuje się do jednego dużego zapytania do bazy danych. Jeśli usuniemy Include, dostejemy po dwa dodatkowe zapytania do bazy danych dla każdej encji z Customers.

Pobieranie całego grafu encji

W bardziej skomplikowanym przypadku, przedstawionym na poniższym diagramie, poprzez odpowiedni dobór kolejności wywołań funkcji Include(), również możemy w jednym zapytaniu załadować do pamięci pełny graf.



var graph = context.Courses.Include("Sections.Students")
                    .Include("Sections.Instructor");

A zatem korzystając z funkcji Include możemy podawać nazwy encji z całego grafu poprzez ścieżkę do takiej nazwy budowaną przy użyciu navigation properties.

W przypadku dziedziczenia encji, gdy tworzymy zapytanie o encje dziedziczące, możemy bez problemu korzystać z navigation properties encji bazowych.

Korzystanie z Include w dowolnych zapytaniach LINQ

Jeżeli chcemy wykorzystać metodę Include() w przypadku dowolnych zapytań, np. joinów, group by czy where, warto pamiętać o kilku faktach:
  • Niezmaterializowane wyrażenie LINQ musimy rzutować do typu ObjectQuery<T>, aby móc skorzystać z metody Include()
  • Include() jest stosowane jedynie do końcowych rezultatów zapytania, w podzapytaniach będzie ignorowane
  • Include() będzie ignorowane, gdy kolekcja zawiera cokolwiek innego niż encje
W poniższym przykładzie zmienna events przechowuje zapytanie typu IQueryable<T> , dopiero podczas materializacji możemy skorzystać z Include().

var events = from ev in context.Events
             where ev.Club.City == "New York"
             group ev by ev.Club
             into g
             select g.FirstOrDefault(e1 => e1.EventDate == g
                 .Min(s => s.EventDate));
var e = ((ObjectQuery<Event>) events).Include("Club").First();

Opóźnione ładowanie encji powiązanych

Sytuacja przedstawia się następująco: mamy jeden obiekt w pamięci i chcemy pobrać dla niego dane z wielu encji powiązanych. Nie chcemy drugi raz pobierać instancji obiektu zmaterializowanego. Możemy skorzystać z metody CreateSourceQuery(), a na zwróconym przez nią obiekcie wywołać dopiero Include(). Tak zbudowane zapytanie należy dołożyć do kontekstu za pomocą funkcji Attach(). Jest to rozwiązanie bardzo wydajne, ponieważ nie musimy po raz drugi pozyskiwać kolumn z encji, którą mamy już w pamięci.

var jill = context.Employees.Where(e => e.Name == "Jill Carpenter").First();
var moreResults = jill.DepartmentReference.CreateSourceQuery()
    .Include("Company").First();
context.Attach(moreResults);
Console.WriteLine("{0} works in {1}", jill.Name, jill.Department.Company.Name);

CreateSourceQuery() zwraca obiekt zapytania, który w momencie wykonania zwraca ten sam zestaw obiektów, który istnieje w obecnej kolekcji.
Attach() dołącza obiekt lub graf obiektów do kontekstu.

Filtrowanie i sortowanie encji powiązanych

Mamy encję w pamięci i chcemy przefiltrować oraz posortować encje z nią powiązane. Kolejny raz skorzystać można z metody CreateSourceQuery(), która pozwoli uzyskać dostęp do zapytania używanego przy pobieraniu kolekcji przez navigation property. Funkcję Include() musimy wywołać w momecie, gdy mamy do czynienia z odpowiednim typem (ObjectQuery<T>). Metoda Attach() łączy przefiltrowane encje z pierwszą encją w pamięci.

var hotel = context.Hotels.First();
var rooms = hotel.Rooms.CreateSourceQuery()
            .Include("Reservations")
            .Where(r => r is ExecutiveSuite && r.Reservations.Any())
            .OrderBy(r => r.Rate);

hotel.Rooms.Attach(rooms);

Sprawdzenie, czy referencja została już załadowana do pamięci 

Chcemy sprawdzić, czy dana referencja do encji powiązanej (lub powiązanej kolekcji) została już załadowana do kontekstu. EF udostępnie property IsLoaded. Różnica polega na tym, że dla kolekcji sprawdzamy IsLoaded bezpośrednio na navigation property, natomiast dla pojedynczych referencji przez nazwa_propertyReference.

var project = context.Projects.Include("Manager").First();
if(project.ManagerReference.IsLoaded)
    Console.WriteLine("Manager reference is loaded");
else
    Console.WriteLine("Manager reference is NOT loaded");
if(project.Contractors.IsLoaded)
    Console.WriteLine("Contractors are loaded");

Aby doczytać kolekcje w sposób jawny, można skorzystać z funkcji Load(), przed jej wywołaniem warto sprawdzić, czy referencja jest już w pamięci za pomocą IsLoaded. Metoda Load jest przeładowana tak, że jako parametr przyjmuje flagę MergeOption, dostępne opcje:
  • AppendOnly dołącza instancje, których nie ma obecnie w kontekście
  • OverwriteChanges przywraca do kontekstu stan z bazy danych
  • NoTracking wyłącza śledzenie stanu
  • PreserveChanges przeciwieństwo dla OverwriteChanges

sobota, 14 lipca 2012

[Wzorce projektowe] Lazy Load

Analogia z życia:

Jednym z codziennie używanych produktów jest cukier. Idąc do sklepu możemy wpaść na pomysł, że zrobimy sobie zapas cukru na cały rok. Zabieramy zatem kilkanaście kilogramów i udajemy się do kasy. Zaletą takiego podejścia jest to, że nie musimy martwić się o to, czy nam zabraknie cukru przez wiele miesięcy, wadami kwota jaką płacimy, kłopoty z przetransportowaniem do domu i brak miejsca do składowania. Może się też zdarzyć, że za tydzień przestaniemy słodzić i wtedy cały zakup okaże się niepotrzebny.



Zastosowanie:

Wzorzec lazy load stosuje się wszędzie tam, gdzie pobranie obiektu wymaga dodatkowego ładowania danych, które nie są używane w momencie, kiedy korzystamy z obiektu. Do tego wzorca należy podchodzić ostrożnie, gdyż np. zbyt wiele zapytań do bazy danych może się okazać niekorzystne pod względem wydajności dla całej aplikacji. Zatem decydując się na zastosowanie tego wzorcu należy brać pod uwagę zarówno ilość zapytań, jak i wielkość danych, jakie zwracają te zapytania. Dzięki odpowiedniej implementacji wzorzec zapewni nam, że dane będą ładowane w momencie, kiedy zajdzie potrzeba ich użycia, a jeśli taka potrzeba nie zajdzie to nie załadowane zostaną wcale (z korzyścią dla wydajności aplikacji).

Zasada działania:

Wzorzec można zaimplementować na cztery sposoby.

Lazy Initialization:

Jest to najprostsze podejście, które wykorzystuje propercje z "backing field". W momencie, gdy odczytujemy wartość propercji, sprawdzane jest czy zainicjowane zostało już odpowiadające jej pole, a jeśli nie, to inicjalizujemy je. Wadą tej metody jest fakt, iż inne operacje klasy nie mogą odczytywać wartości w żaden inny sposób niż przez propercję (pole może nie być zainicjalizowane). W języku C# przy implementacji Lazy Initialization można użyć operatora ??

public class LazyInitializedObject
    {
        private HeavyObject _obj;
        public HeavyObject Obj
        {
            get
            {
                if(_obj == null)
                    _obj = new HeavyObject();
                return _obj;
            }
        }

        private HeavyObject _obj2;
        public HeavyObject Obj2
        {
            get { return _obj2 ?? (_obj2 = new HeavyObject()); }
        }
    }

    public class HeavyObject
    {
        public HeavyObject()
        {
            //symulacja długich operacji odczytu z bazy danych
            Thread.Sleep(300);
        }
    }  

Virtual Proxy:

Ideą tej metody jest zastosowanie klasy, która wygląda dla klienta jak obiekt, który nas interesuje, ale tak na prawdę klasa proxy steruje dostępem do właściwego obiektu. Problemem jest tutaj tożsamość obiektu proxy, który klient może porównywać z innymi rzeczywistymi obiektami. Można ten problem rozwiązać przeładowując metody Equals i GetHashCode. Jeśli chodzi o implementację, to warto zastosować prostą fabrykę. Klient wysyła żądanie do fabryki o nowy obiekt. Fabryka zwraca obiekt proxy, który zawiera w sobie obiekt właściwy. Proxy steruje logiką przy wołaniu usług z właściwego obiektu, a także zapewnia tożsamość obiektu.


public class Student
    {
        public Student()
        {
            //symulacja pobierania danych
            Thread.Sleep(300);
        }
    }

    public class StudentBook
    {
        public int Identity { get; set; }

        public virtual Student Entity { get; set; }

        public override int GetHashCode()
        {
            return Identity.GetHashCode();
        }
    }

    public class StudentBookProxy : StudentBook
    {
        public override Student Entity
        {
            get
            {
                if(base.Entity == null)
                    base.Entity = new Student();
                return base.Entity;
            }
            set
            {
                base.Entity = value;
            }
        }

        public override bool Equals(object obj)
        {
            var st = obj as StudentBook;
            if (st == null) return false;
            return st.Identity == this.Identity;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    public class StudentBookFactory
    {
        public StudentBook GetFromId(int id)
        {
            return new StudentBookProxy()
                       {
                           Identity = id
                       };
        }
    }

Value Holder:

W tej wersji klient ma świadomość funkcjonalności leniwego ładowania, zatem usługi wywoływane są z pełną świadomością typu Value Holdera. Korzystając z dot Neta w wersji 3.5 lub wyższej można do tego celu stworzyć generyczny interfejs, który zwraca obiekt, np poprzez metodę Load. Implementacja metody load może być dokonana na wiele sposobów. Tworzy się także klasę generyczną, która zawiera propercję z obiektem, który nas będzie interesował.


public interface IValueLoader<T>
    {
        T Load();
    }

    public class ValueHolder<T>
    {
        private T _value;
        private readonly IValueLoader<T> _loader;

        public T Value
        {
            get
            {
                if (_value == null)
                    _value = _loader.Load();
                return _value;
            }
        }

        public ValueHolder(IValueLoader<T> loader)
        {
            _loader = loader;
        }
    }

    public class SimpleLoader : IValueLoader<List<Employee>>
    {
        public List<Employee> Load()
        {
            Thread.Sleep(100);
            return new List<Employee>();
        }
    }

    public class Employee
    {
        public int Salary { get; set; }
    }

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

        public Salaries(int id)
        {
            Id = id;
        }

        private ValueHolder<List<Employee>> _employees;

        public List<Employee> GetEmployees()
        {
            return _employees.Value;
        }

        public void SetEmployees(ValueHolder<List<Employee>> loader)
        {
            _employees = loader;
        }
    }

    public class SalariesFactory
    {
        public Salaries GetItem()
        {
            var salaries = new Salaries(1);
            salaries.SetEmployees(
                new ValueHolder<List<Employee>>(new SimpleLoader()));
            return salaries;
        }

    }

Ghosts:

Duchem nazywany jest obiekt znajdujący się w stanie częściowym, początkowo obiekt taki zawiera jedynie Id. W momencie, kiedy odczytywana jest którakolwiek z propercji, obiekt ładuje cały swój stan. Można powiedzieć, że obiekt stanowi zatem wirtualne proxy sam dla siebie, przez co nie pojawiają się problemy z tożsamością obiektu. Obiekt może zatem znajdować się w trzech stanach : załadowany, w trakcie ładowania oraz duch. Implementację wzorca można pozostawić klasie abstrakcyjnej, natomiast reprezentację stanu wartości typu wyliczeniowego.


public class Customer : DomainObject
    {
        public Customer(int id) : base(id)
        {
            
        }

        private string _name;
        public string Name
        {
            get
            {
                Load();
                return _name;
            }
            set
            {
                Load();
                _name = value;
            }
        }

        private int _age;
        public int Age
        {
            get
            {
                Load();
                return _age;
            }
            set
            {
                Load();
                _age = value;
            }
        }

        private List<int> _orderCosts;
        public List<int> OrderCosts
        {
            get
            {
                Load();
                return _orderCosts;
            }
            set
            {
                Load();
                _orderCosts = value;
            }
        }

        public override ArrayList GetDataRow(int id)
        {
            //symulacja pobierania z bazy danych, np.
            //SELECT * FROM CUSTOMERS WHERE CustomerID = id
            var al = new ArrayList();
            al.Add("Marek Nowak");
            al.Add(31);
            al.Add(new List<int> {1, 7});
            return al;
        }

        public override void InjectLineIntoObject(ArrayList arrayList)
        {
            //Mapowanie row na propercje
            Name = arrayList[0] as string;
            Age = int.Parse(arrayList[1].ToString());
            OrderCosts = arrayList[2] as List<int>;
        }
    }

    public abstract class DomainObject
    {
        public int Id { get; set; }
        private LoadStatus Status { get; set; }

        protected DomainObject(int id)
        {
            Id = id;
        }

        public bool IsGhost
        {
            get { return Status == LoadStatus.Ghost; }
        }

        public bool IsLoading
        {
            get { return Status == LoadStatus.Loading; }
        }

        public bool IsLoaded
        {
            get { return Status == LoadStatus.Loaded; }
        }

        public void Load()
        {
            if (!IsGhost) return;
            Status = LoadStatus.Loading;
            var row = GetDataRow(1);
            InjectLineIntoObject(row);
            Status = LoadStatus.Loaded;
        }



        public abstract ArrayList GetDataRow(int id);
        public abstract void InjectLineIntoObject(ArrayList arrayList);

    }

    public enum LoadStatus
    {
        Ghost,
        Loading,
        Loaded
    }
Na koniec warto wspomnieć o klasie Lazy, która również zapewnia opisane powyżej mechanizmy. Opis tutaj