niedziela, 23 września 2012

Mapowanie obiektów - Extension Methods

Extension Methods można wykorzystać nie tylko do dodawania metod do istniejących klas, ale także jako najprostszy mechanizm mapujący. Nie musimy tworzyć całej biblioteki do mapowania, wystarczy dodać do klasy odpowiednią metodę np. Map.

Jako kod bazowy użyjemy obiekt encji tabeli:

Code:
    public class PersonEntity
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }
    }

klasa którą chcemy uzyskać ma następującą strukturę:

Code:
    public class PersonView
    {
        public string FullName { get; set; }
        public string Address { get; set; }
    }

Kod mapowania:

Code:
    public static class Mappers
    {
             public static PersonView Map(this PersonEntity personEntity)
             {
                 return new PersonView
                            {
                                FullName = personEntity.FirstName + " " + personEntity.LastName,
                                Address = personEntity.Address
                            };
             }
    }

użycie sprowadza się do wywołania metody Map na obiekcie bazowym:

Code:
            var personEntity = new PersonEntity
                                   {
                                       Id = 1, Address = "Andrychowska 34, 10-124 Andrychów", Age = 31, FirstName = "Jan", LastName = "Kowalski"
                                   };
            var pv = personEntity.Map();

wtorek, 18 września 2012

TortoiseSVN Ignore dla C# i Resharper

Przydatny filtr dla ignorowanych w przypadku korzystania z Resharpera:

*\bin* *\obj* *.suo *.user *.bak **.ReSharper** **\_ReSharper.** StyleCop.Cache

Dzięki temu unikniemy komitowania folderów bin, obj i wszystkich zbędnych rzeczy - łącznie z plikami generowanymi przez Resharper.
Filtr znalazłem na blogu: Struggles by Lars C.

niedziela, 16 września 2012

jQuery selekcja

Przeglądanie drzewa DOM dokumentu HTML jest bardzo proste przy użyciu selektorów z jQuery.

$("h1") - selekcja wszystkich elementów h1
$(".klasa") - selekcja wszystkich elementów posiadających klasę klasa
$("#id_elementu") - selekcja elementu o id = id_elementu - jest to najszybszy dostępny selektor
$("*") - pobiera wszystkie elementy na stronie
$("p.klasa") - wybiera wszystkie elementy p posiadające klasę klasa
$("p:first") - wybiera pierwszy element typu p
$("[name]") - wybiera wszystkie elementy posiadające atrybut name
$("form[name='formatka1']") - wybiera wszystkie formularze o nazwie formatka1
$("a, p, div") - wyszukiwanie wielu na raz
$("div p") - wybiera wszystkie paragrafy znajdujące się w elemencie div

Metody pomocnicze:
$(".klasa").parent() - wyszukuje wszystkie elementy nadrzędne do tych o klasie klasa
$(".klasa").children() - Elementy zawarte w elementach o klasie klasa
$(".klasa").prev() / .next() - element po prawej/lewej stronie elementów o klasie klasa

Korzystając z wymienionych funkcji można w łatwy sposób zmieniać całą strukturę drzewa DOM dokumentu HTML.

Dodatkowo warto pamiętać aby każdy blok kodu jQuery rozpoczynać od sprawdzenia czy dokument został załadowany:

Code:
$(document).ready(function() {
});

sobota, 15 września 2012

Czym się różni Single od First w LINQ

W LINQ mamy dwie metody First() oraz Single().
Obie metody zwracają po jednym rekordzie. Jaka jest więc między nimi różnica?

Zobaczmy na kawałek kodu i jego reprezentację w bazie MS SQL (zapytania wyciągnięte z Profilera):


Code:
        static void Main(string[] args)
        {
            var test1Context = new test1Context();
            var single = test1Context.Pojazds.Single(x => x.Id == 100);
            var first = test1Context.Pojazds.First(x => x.Id == 100);
        }

SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Nazwa] AS [Nazwa]FROM [dbo].[Pojazd] AS [Extent1]WHERE 100 = [Extent1].[Id]
SELECT TOP (1) [Extent1].[Id] AS [Id], [Extent1].[Nazwa] AS [Nazwa]FROM [dbo].[Pojazd] AS [Extent1]WHERE 100 = [Extent1].[Id]


Jak widać Single wybiera 2 rekordy a First tylko jeden. Dlaczego?
W przypadku Single pobierane są dwa pierwsze rekordy, które zwróci zapytanie. Następnie sprawdzane jest czy faktycznie jest jeden rekord, a w przypadku gdy są dwa rzucany jest wyjątek. W przypadku metody First wyjątek nie zostanie rzucony, jeżeli zapytanie zwróci więcej niż jeden wynik.

Ma to swoje zalety, w przypadku gdy musimy otrzymać tylko jeden wynik a z jakichś względów otrzymujemy więcej wyników. Korzystając z metody Single możemy szybciej zdiagnozować problemy, które mogą się objawić w aplikacji.

Wstawianie wielu rekordów za pomocą LINQ - Batch Instert by LINQ

Wielu sobie zadaje pytanie dlaczego wstawianie dużej ilości rekordów do bazy za pomocą LINQ2SQL bądź też Entity Framework trwa tak długo.

Zobaczmy na poniższy kawałek kodu:


Code:
        static void Main(string[] args)
        {
            var test1Context = new test1Context();
            test1Context.Configuration.AutoDetectChangesEnabled = false;
            test1Context.Configuration.ProxyCreationEnabled = false;
            for (int i = 0; i < 1000; i++)
            {
                var pojazd = new Pojazd {Nazwa = Guid.NewGuid().ToString()};
                test1Context.Pojazds.Add(pojazd);
            }

            test1Context.SaveChanges();
        }

Wydawałoby się że zostanie wykonana pojedyncza transakcja, która wstawi wszystkie rekordy. Przyglądając się profilerowi MS SQL możemy stwierdzić, że tak się nie dzieje:

EXEC sp_executesql N'insert [dbo].[Pojazd]([Nazwa])
values (@0)
select [Id]
from [dbo].[Pojazd]
where @@ROWCOUNT > 0 and [Id] = scope_identity()'
,N'@0 nchar(100)',@0=N'6dd1fb44-61d3-4fce-8ef8-4f0d61492df2       
'


Każdy rekord jest traktowany osobno, a dodatkowo pobierane jest jego Id. Mamy więc do czynienia z sekwencją: wstaw rekord, pobierz Id, przypisz Id do obiektu.

W jaki sposób poradzić sobie z tym problemem? Na ten moment nie ma jednego uniwersalnego rozwiązania zawartego w EF czy też LINQ2SQL. Niestety musimy sami zaimplementować takie rozwiązanie, korzystając z klas do szybkiego wstawiania rekordów do tabel oferowanych przez poszczególnych providerów komponentów do obsługi baz danych. Innym rozwiązaniem jest napisanie własnego "czystego" SQLa który wykonuje INSERT do bazy przy użyciu transakcji. 

Optymalizacja operacji JOIN

Zobaczmy na prosty przykład:

Naszym zadaniem jest przedstawienie ile kół posiada każdy z pojazdów.
Standardowo napiszemy następujące zapytanie:


SELECT p.Nazwa, COUNT(1) AS 'Ilosc kól'
FROM Pojazd p JOIN Kolo k ON p.Id = k.IdPojazd
GROUP BY p.Nazwa 


Zapytanie jest poprawne, jednak ma ono jedną wadę. W przypadku kiedy w tabelach będzie przybywało danych zapytanie będzie stawało się coraz wolniejsze.
Analizując zapytanie możemy dojść do wniosku że główne obciążenie stanowi połączenie dwóch tabel.
Zapytanie to można podzielić na dwa zliczając najpierw w drugiej tabeli koła dla samochodów a następnie łącząc z pierwszą tabelą:

Jeżeli w pierwszej tabeli jest 2 mln rekordów a w drugiej np 10 mln połączenie spowoduje grupowanie 10 mln rekordów. Łatwiej najpierw pogrupować 2 tabelę i połączyć ją z pojazdami niż na odwrót:

SELECT p.Nazwa, k.IloscFROM Pojazd p JOIN (SELECT IdPojazd, COUNT(1) 'Ilosc' FROM Kolo GROUP BY IdPojazd) AS k ON p.Id = k.IdPojazd

Takie zapytanie wykona się w dużo krótszym czasie oraz zużyje znacznie mniej zasobów sprzętowych.

Przetwarzanie zapytania SQL

Zapytanie SQL jest przetwarzane w pewnej ustalonej kolejności. W większości baz danych kolejność ta jest stała i przedstawia się następująco:

(8) SELECT (9) TOP
(1) FROM
(3) JOIN
(2) ON
(4) WHERE
(5) GROUP BY
(6) WITH
(7) HAVING
(1) ORDER BY


Znajomość tej kolejności przydaje się zwłaszcza w przypadku optymalizacji zapytań. Wiedząc że najwcześniej mamy do czynienia z łączeniem tabel, możemy podzapytaniami ograniczyć łączone zbiory co przełoży się na wydajność zapytania.

piątek, 14 września 2012

jQuery + Ajax w ASP.NET MVC

Obsługa Ajax w jQuery dzięki pomocniczym metodom jest niezwykle prosta.
Podstawową metodą jest $.ajax()

Załóżmy, że chcemy wyświetlić klientów, ale dopiero po kliknięciu na przycisk. Dodatkowo nie potrzebujemy aby cała strona została przeładowana, tylko nowe rekordy zostały dodane w interesujące nas miejsce na stronie.

Model:


Code:
    public class ClientModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }

        public ClientModel(int id, string firstName, string lastName, int birthYear)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
            BirthYear = birthYear;
        }

        public ClientModel()
        {
        }

        public override string ToString()
        {
            return string.Format("Id: {0}, FirstName: {1}, LastName: {2}, BirthYear: {3}", Id, FirstName, LastName, BirthYear);
        }
    }


Kontroler:


Code:
    public class HomeController : Controller
    {
        public List<ClientModel> Clients
        {
            get
            {
                return new List<ClientModel>
                    {
                        new ClientModel(1, "Jacek", "Markowski", 2000),
                        new ClientModel(2, "Marek", "Polkowski", 1988),
                        new ClientModel(3, "Sebastian", "Dunkowski", 1900)
                    };
            }
        }

        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";

            return View();
        }

        public ActionResult About()
        {
            return View();
        }

        public JsonResult GetPersonList()
        {
            return Json(Clients, JsonRequestBehavior.AllowGet);
        }

        public JsonResult GetPersonById(int id)
        {
            var client = Clients.Single(x => x.Id == id);
            return Json(client, JsonRequestBehavior.AllowGet);
        }
    }


HTML:


Code:
@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>
<p>        
    <div id="PersonList">
    </div>

    <input type="button" value="Pobierz dane" id="przycisk1"/>    
</p>


Do wyrenderowanej strony:


Chcemy, aby po naciśnięciu przycisku Pobierz dane zostały pobrane i wyświetlone rekordy. Możemy tego dokonać za pomocą następującej funkcji:


Code:
@{
    ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
    <div id="PersonList">
    </div>
    <input type="button" value="Pobierz dane" id="przycisk1" />
    <script type="text/javascript">
        $(document).ready(function () {
            $("#przycisk1").click(function () {
                $.ajax({
                    url: '@Url.Action("GetPersonList")',
                    cache: false,
                    dataType: "JSON",
                    success: function (data) {
                        $.each(data, function (i, item) {
                            $("#PersonList").append(item.Id + " " + item.FirstName + " " + item.LastName + "</br>");
                        });
                    }
                });
                return false;
            });
        });
    </script>
</p>


Efekt:



Wysyłanie danych do kontrolera jest tak samo proste jak pobieranie danych. Należy do tego wykorzystać właściwość data, np chcemy pobrać dane użytkownika o id = 2:


Kod:

Code:
@{
    ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
    <div id="PersonList">
    </div>
    <form method="post" name="formularz">
        <label>Id: </label>
        <input type="text" name="id" id="id"/>
        <input type="button" value="Pobierz dane" id="przycisk1" name="przycisk1" />
    </form>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#przycisk1").click(function() {
                $.ajax({
                    url: '@Url.Action("GetPersonById")',
                    cache: false,
                    dataType: "JSON",
                    data: $("form[name=formularz]").serialize(),
                    success: function(data) {
                        $("#PersonList").append(data.Id + " " + data.FirstName + " " + data.LastName + "</br>");
                    }
                });
                return false;
            });
        });
    </script>
</p>

czwartek, 13 września 2012

Wstawianie wielu rekordów do tabeli SqLite

Poprzednio opisałem w jaki sposób można poradzić sobie z wstawianiem wielu rekordów do bazy PostgreSql. Tym razem na warsztat wziąłem SqLite. Biorąc pod uwagę brak potrzeby konfiguracji, jest ona świetną alternatywą przechowywania wszystkich ustawień aplikacji itp.

Bazę i jej schemat tworzy metoda CreateDatabase:


Code:
        private static void CreateDatabase()
        {
            SQLiteConnection.CreateFile("database.sqlite");
            using (var connection = GetSqLiteConnection())
            {
                using(var cmd = connection.CreateCommand())
                {
                    cmd.CommandText = @"CREATE TABLE Table1 (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name1 VARCHAR,
Name2 VARCHAR,
Name3 VARCHAR,
Name4 VARCHAR);";
                    cmd.ExecuteNonQuery();
                }
            }
        }


Testy zostały wykonane przy 80k wierszy:
1. Zwykły insert:
Jak w poprzednim przypadku najmniej wydajne rozwiązanie - dla każdego pojedynczego INSERTA jest tworzona osobna transakcja co wydłuża proces wstawiania danych:


Code:
        private static long NormalInsert(List<Table1> data, SQLiteConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "INSERT INTO Table1(Name1,Name2,Name3,Name4) VALUES(@Name1,@Name2,@Name3,@Name4);";
                cmd.Parameters.AddWithValue("@Name1", "");
                cmd.Parameters.AddWithValue("@Name2", "");
                cmd.Parameters.AddWithValue("@Name3", "");
                cmd.Parameters.AddWithValue("@Name4", "");
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters["@Name1"].Value = data[i].Name1;
                    cmd.Parameters["@Name2"].Value = data[i].Name2;
                    cmd.Parameters["@Name3"].Value = data[i].Name3;
                    cmd.Parameters["@Name4"].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
            }

            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }



2. Transakcja
Wykorzystując jedną transakcję do wstawienia dużej ilości rekordów przyśpieszymy całą operację wielokrotnie, co też zobaczymy w wynikach:


Code:
        private static long TransactionInsert(List<Table1> data, SQLiteConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.Transaction = connection.BeginTransaction();
                cmd.CommandText = "INSERT INTO Table1(Name1,Name2,Name3,Name4) VALUES(@Name1,@Name2,@Name3,@Name4);";
                cmd.Parameters.AddWithValue("@Name1", "");
                cmd.Parameters.AddWithValue("@Name2", "");
                cmd.Parameters.AddWithValue("@Name3", "");
                cmd.Parameters.AddWithValue("@Name4", "");
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters["@Name1"].Value = data[i].Name1;
                    cmd.Parameters["@Name2"].Value = data[i].Name2;
                    cmd.Parameters["@Name3"].Value = data[i].Name3;
                    cmd.Parameters["@Name4"].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
                cmd.Transaction.Commit();
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }



3. Transakcja wraz z przygotowaniem zapytania


Code:
        private static long TransactionInsertWithPrepare(List<Table1> data, SQLiteConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.Transaction = connection.BeginTransaction();
                cmd.CommandText = "INSERT INTO Table1(Name1,Name2,Name3,Name4) VALUES(@Name1,@Name2,@Name3,@Name4);";
                cmd.Parameters.AddWithValue("@Name1", "");
                cmd.Parameters.AddWithValue("@Name2", "");
                cmd.Parameters.AddWithValue("@Name3", "");
                cmd.Parameters.AddWithValue("@Name4", "");
                cmd.Prepare();
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters["@Name1"].Value = data[i].Name1;
                    cmd.Parameters["@Name2"].Value = data[i].Name2;
                    cmd.Parameters["@Name3"].Value = data[i].Name3;
                    cmd.Parameters["@Name4"].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
                cmd.Transaction.Commit();
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }



4. Synchronous = OFF
Podczas zapisu danych do bazy tworzone są punkty kontrolne, przy których sprawdzany jest stan zapisu danych w pliku bazy. Jeżeli wyłączymy synchronizację, unikniemy sprawdzania, jednak musimy się wtedy liczyć z tym, iż w razie awarii sprzętu (np. wyłączenia prądu) może dojść do uszkodzenia pliku bazy danych.

Code:
        private static long TransactionPrepareSynchronousOff(List<Table1> data, SQLiteConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "PRAGMA synchronous = OFF";
                cmd.ExecuteNonQuery();
                cmd.Transaction = connection.BeginTransaction();
                cmd.CommandText = "INSERT INTO Table1(Name1,Name2,Name3,Name4) VALUES(@Name1,@Name2,@Name3,@Name4);";
                cmd.Parameters.AddWithValue("@Name1", "");
                cmd.Parameters.AddWithValue("@Name2", "");
                cmd.Parameters.AddWithValue("@Name3", "");
                cmd.Parameters.AddWithValue("@Name4", "");
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters["@Name1"].Value = data[i].Name1;
                    cmd.Parameters["@Name2"].Value = data[i].Name2;
                    cmd.Parameters["@Name3"].Value = data[i].Name3;
                    cmd.Parameters["@Name4"].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
                cmd.Transaction.Commit();
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }



Zobaczmy na wyniki testu:


Czas wstawiania dla zwykłych insertów jest wręcz nie do zaakceptowania. Lepszy obraz całości przedstawia wykres pokazujący ilość wstawianych rekordów na sekundę:

środa, 12 września 2012

Dodawanie dużej ilości rekordów do bazy PostgreSql

Zadaniem jest wstawienie 80 tys. rekordów do przykładowej tabeli której strukturę podano poniżej:

CREATE TABLE Table2(
  
Id serial NOT NULL,
  
Name1 CHARacter VARYING(60),
  
Name3 CHARacter VARYING(60),
  
Name2 CHARacter VARYING(60),
  
Name4 CHARacter VARYING(60),
  
CONSTRAINT "Table2_pkey"PRIMARY KEY (Id)
);


Kod tworzący dane testowe:


Code:
    public class Table1
    {
        public int Id { get; set; }
        public string Name1 { get; set; }
        public string Name2 { get; set; }
        public string Name3 { get; set; }
        public string Name4 { get; set; }

        public Table1(string name1, string name2, string name3, string name4)
        {
            Name1 = name1;
            Name2 = name2;
            Name3 = name3;
            Name4 = name4;
        }
    }



Code:
    public class Repository
    {
         public static List<Table1> GetData(int rowCount)
         {
             var data = new List<Table1>();
             for (int i = 0; i < rowCount; i++)
             {
                 data.Add(new Table1(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
                                     Guid.NewGuid().ToString()));
             }

             return data;
         }
    }


Podam tutaj 3 znane mi sposoby, które można użyć w tym celu.

1. Zwykłe inserty:
Standardowy sposób dodawania rekordów, który sprawdza się w przypadku dodania pojedynczego rekordu  do bazy, jednak w przypadku ich dużej ilości powoduje zamulenie całego systemu (co później zobaczymy w wynikach):


Code:
        private static long InsertAsSingleRows(List<Table1> data, NpgsqlConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.Parameters.AddWithValue(Name1, "");
                cmd.Parameters.AddWithValue(Name2, "");
                cmd.Parameters.AddWithValue(Name3, "");
                cmd.Parameters.AddWithValue(Name4, "");
                cmd.CommandText = "INSERT INTO Table2(Name1,Name2,Name3,name4) VALUES(@Name1,@Name2,@Name3,@Name4)";
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters[Name1].Value = data[i].Name1;
                    cmd.Parameters[Name2].Value = data[i].Name2;
                    cmd.Parameters[Name3].Value = data[i].Name3;
                    cmd.Parameters[Name4].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }


2. Transakcja
W przypadku dużej ilości dodawanych rekordów lepiej skorzystać z transakcji, którą na końcu zatwierdzamy:


Code:
        private static long InsertAsTransaction(List<Table1> data, NpgsqlConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.Transaction = connection.BeginTransaction();
                cmd.Parameters.AddWithValue(Name1, "");
                cmd.Parameters.AddWithValue(Name2, "");
                cmd.Parameters.AddWithValue(Name3, "");
                cmd.Parameters.AddWithValue(Name4, "");
                cmd.CommandText = "INSERT INTO Table2(Name1,Name2,Name3,name4) VALUES(@Name1,@Name2,@Name3,@Name4)";
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters[Name1].Value = data[i].Name1;
                    cmd.Parameters[Name2].Value = data[i].Name2;
                    cmd.Parameters[Name3].Value = data[i].Name3;
                    cmd.Parameters[Name4].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
                cmd.Transaction.Commit();
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }


3. Insert batchowy
Mechanizm ten służy przede wszystkim ładowaniu bardzo dużej ilości rekordów z plików. Jego wydajność jest nieporównywalnie większa niż przedstawionych dwóch poprzednich:


Code:
        private static long InsertAsBatch(List<Table1> data, NpgsqlConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                var dataAsFile = new StringBuilder();
                for (int i = 0; i < data.Count; i++)
                {
                    dataAsFile.AppendFormat("{0}\t{1}\t{2}\t{3}\n", data[i].Name1, data[i].Name2, data[i].Name3,
                                            data[i].Name4);
                }
                var memoryStream = new MemoryStream(new UTF8Encoding().GetBytes(dataAsFile.ToString()));
                cmd.CommandText = "COPY Table2(Name1,Name2,Name3,name4) FROM STDIN";
                var npgsqlCopyIn = new NpgsqlCopyIn(cmd, connection, memoryStream);
                npgsqlCopyIn.Start();

                stopwatch.Stop();

                return stopwatch.ElapsedMilliseconds;
            }
        }



Czas na wyniki pomiarów czasu:


bardzo dobrze widać to na wykresie:


Podsumowując:
Jeżeli wstawiamy bardzo dużą ilość rekordów warto skorzystać z transakcji. Jeżeli dysponujemy ogromnymi zbiorami danych które chcemy umieścić w bazie możemy skorzystać z inserta batchowego.