Jak z RavenDB i DataTables.Net 14

Link: https://antjanus.com/blog/web-development-tutorials/how-to-with-ravendb-and-datatables-net/

Zdjęcie autorstwa Brujo . Istotne: ekstrakcja bazy danych binarnych?

Dla tych, którzy mogą nie wiedzieć, DataTables.Net to fantastyczna wtyczka jQuery, która tworzy prezentację siatki danych i oferuje wsparcie dla filtrowania i stronicowania. Tak, zdefiniuj usługę internetową punktu końcowego, odfiltruj swoje dane i jesteś gotowy do pracy. Ale te fajne dzieci w dzisiejszych czasach są w stylu No-SQL, a jednym z wielkich wpisów w bazie danych opartej na dokumentach jest RavenDB . Dzięki Raven definiujesz swoje obiekty domeny i przechowujesz je w bazie danych, ponieważ Raven zrobi to magicznie i utrzyma twoje obiekty jako dokumenty. Miej listę <Klient> do przechowywania, daj ją Krukowi, a utworzy ona jeden dokument klientów i zapisze go w formacie JSON.

Ten post pokaże Ci, jak połączyć dobry front DataTables z magią RavenDB. Celem jest zapewnienie:

  • Możliwość zdefiniowania pojedynczej klasy dla tych indeksów danych.
  • Kontroluj, jakie dane są wybierane, definiując kolumny, kolejność sortowania i wielkość stronicowania w JavaScript. Innymi słowy, DataTables poinformuje serwer, co chce wycofać
  • Zapewnij wsparcie dla właściwości filtrowania za pomocą jednego pola wyszukiwania, w stylu Google.
  • Przede wszystkim oszczędzaj czas, spraw, byś stał się bohaterem przed fanami. 🙂

Dla niecierpliwych, oto produkt końcowy

Jest dużo gruntów, które omówimy, ale dla tych, którzy chcą zobaczyć światło na końcu tunelu, jak będzie wyglądało rozwiązanie końcowe. Może warto najpierw pobrać rozwiązanie , dzięki czemu można śledzić także w kodzie. Po pierwsze, twoja usługa sieciowa lub kontroler będzie miał:

[HttpPost]
public JsonResult GetTenants(string jsonAOData)
{
var tenantPager = new DataTablesPager(DocumentStore);
var results = tenantPager.PrepAOData(jsonAOData)
.FilterFormattedList();

return Json(results);
}

// The core method that is used to get the data is in DataTablesPager.cs
public List Filter(int pageSize, int pageIndex, string[] terms)
{
var targetList = new List();
RavenQueryStatistics stats;

using(var session = docStore.OpenSession())
{
if (terms[0].Length > 0)
{
targetList = session.Query()
.Customize(x => x.WaitForNonStaleResults())
.SearchMultiple(x => x.QueryFields, string.Join(" ", terms), options: SearchOptions.And)
.Statistics(out stats)
.Skip(pageIndex)
.Take(pageSize)
.As()
.ToList();

this.totalDisplayResults = stats.TotalResults;

session.Query()
.Statistics(out stats)
.As()
.ToList();
this.totalResults = stats.TotalResults;
}
// Code reduced for reading purposes.

Zwróć uwagę na Generics na konstruktorze – Najemcą jest twój obiekt domeny, Tenant_Search to klasa, której Raven użyje do stworzenia indeksu do pobierania danych, a także zdefiniowania właściwości, które możesz filtrować na obiekcie. Wkrótce omówimy indeksowanie wraz z tłem RavenDB.

Twój Javascript będzie następujący:

var otable;

$(document).ready(function(){
otable = $("#tenantTable").dataTable({
"bProcessing": true,
"bSort": true,
"sPaginationType": "full_numbers",
"aoColumnDefs": [
{ "sName": "Name", "aTargets": [0], "bSortable": true, "bSearchable": true },
{ "sName": "Agent", "aTargets": [1], "bSortable": true, "bSearchable": true },
{ "sName": "Center", "aTargets": [2], "bSortable": true, "bSearchable": true },
{ "sName": "StartDate", "aTargets": [3], "bSortable": true, "bSearchable": true },
{ "sName": "EndDate", "aTargets": [4], "bSortable": true, "bSearchable": true },
{ "sName": "DealAmount", "aTargets": [4], "bSortable": true, "bSearchable": true }
],
"oLanguage": {
"sSearch": "Search all columns:"
},
"aaSorting": [[1, "asc"]],
"iDisplayLength": 7,
"bServerSide": true,
"sAjaxSource": "GetTenants",
"fnServerData": function (sSource, aoData, fnCallback) {

var jsonAOData = JSON.stringify(aoData);

$.ajax({
//dataType: 'json',
contentType: "application/json; charset=utf-8",
type: "POST",
url: sSource,
data: "{jsonAOData : '" + jsonAOData + "'}",
success: function (msg) {
fnCallback(msg);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.status);
alert(XMLHttpRequest.responseText);

}
});
}
});

otable.fnSetFilteringDelay(1000);
});

W rzeczywistości jest dłuższy niż rzeczy .Net! Omówimy także, co to oznacza.

Pobieranie danych i zapewnianie wyszukiwania za pomocą RavenDB

Ten post zakłada, że zainstalowałeś RavenDB, możesz się z nim połączyć, wiesz, jak przechowywać twoje obiekty, i że możesz wykonywać zapytania za pomocą LINQ. Jest kilka dobrych samouczków, takich jak wprowadzenie wideo Roba Ashtona do Ravena, a także krótki przegląd Sensei (ja). Skoncentrujemy się na nieodłącznej funkcji wyszukiwania pełnotekstowego Ravena i polegamy na wbudowanym mechanizmie stronicowania Ravena, aby pomóc nam osiągnąć nasze cele. Chociaż istnieje duża zdolność, którą zapewnia Raven, to nie jest to SQL, a wiele z tego, co wiesz o LINQ i LINQ do SQL, pomoże ci i jednocześnie pomalować cię w kąt. Omówimy również te aspekty.

Po pierwsze, RavenDB jest zbudowany na bazie wyszukiwarki Lucene.Net. Jest to baza bez schematów, więc z góry musimy określić, w jaki sposób chcemy pobierać dane, ponieważ indeksy zapewniają bardzo szybkie pobieranie danych. Indeksy Raven redukują potrzebę poświęcania ogromnych cykli procesora na przetwarzanie zapytania, ponieważ indeks jest zbudowany z dokumentów i przetwarzany jako operacja w tle. Ta operacja jest asynchroniczna i jest wykonywana przez Lucene. Bez tego podejścia każde zapytanie wymusza pełne skanowanie wszystkich dokumentów. Nędznie wolne skanowanie. Dzięki indeksom definiującym z góry, Raven będzie działał poprawnie, aby indeksy były aktualne podczas tworzenia nowych dokumentów. Dlaczego tak jest ważne? Cóż, co myślisz robi w LINQ:

var search = "Bonus";
var steps = session.Query()
.Customize(x => x.WaitForNonStaleResults())
.Where(x => x.State.StartsWith(search) || x.WorkflowName.StartsWith(search))
.ToList();

jest naprawdę przetłumaczone na składnię Lucene . Ostatecznie otrzymujemy stan: Bonus LUB WorkflowName: Bonus. Choć łatwo jest napisać zapytanie, które zawiera wszystkie właściwości obiektu, gdybyś miał obiekt z 15 właściwościami, naprawdę chciałbyś stworzyć instrukcję potworów z toną ||? Do diabła, nie! Jeśli spojrzysz w projekt TestSuite kodu źródłowego, jest kilka przykładów użycia czystych zapytań LINQ. Sprawdź metodę “CanFilterAccrossAllStringProperties”, a zobaczysz, dokąd zmierza.

Chcemy być jak Fonzie, a czym był Fonzie? Correctomundo – jest fajny. Dobrym rozwiązaniem byłoby poznanie właściwości obiektu domeny i wykonanie filtru dla tych właściwości. Innymi słowy, byłoby naprawdę pomocne, gdybyśmy mogli napisać kod, który wyglądałby tak:

var propertyFilterSteps = session.Query()
.Customize(x => x.WaitForNonStaleResults())
.Where(AllStringPropertiesFilter(search, "Answer,AnsweredBy,Id,Participants,WorkflowName,WorkflowType,"))
.ToList();

Tutaj używamy wyrażenia <Func <T >>, aby przekazać ograniczoną listę nazw właściwości iz małą kwerendą LINQ przeciwko klasie Step, możemy wygenerować Lambda do procesu filtrowania. Jest to metoda testowa “CanFilterAccrossAllStringProperties”. Udało się to świetnie, dopóki nie musieliśmy uwzględnić właściwości DateTime. Kod jest zawarty w projekcie, więc patrzysz tam na niego.

Jak więc osiągnąć cel polegający na wyszukiwaniu w jednym polu tekstowym, które będzie wyszukiwać we wszystkich typach właściwości, a gdy wpiszesz “Spock 2010”, zapyta o właściwości określone dla obu wartości “Spock” i “2010” ? Wróćmy do Raven, ponieważ możesz określić zapytanie indeksowe poprzez mapowaniejakie właściwości chcesz uwzględnić w indeksie i jak chcesz, aby Raven / Lucene analizował tekst pod kątem dopasowywania wartości w tym indeksie. Raven udostępnia klasę o nazwie “AbstractIndexCreationTask”, w której definiujesz funkcję Map / Reduce w celu utworzenia indeksu. Innymi słowy wybierasz, które właściwości są zawarte w indeksie. Możesz zdefiniować wyjście mapy na wszystko, co chcesz. Odbywa się to w klasie, którą nazwiemy ReduceResult, a my zapytamy o właściwości tej klasy. Chcemy powiedzieć Ravenowi, aby wybrał istotne właściwości i zindeksował je w formacie, który możemy dopasować do naszych warunków. Dlatego utworzymy następujący indeks, który pozwoli nam filtrować dowolny termin. Można go znaleźć w pliku Step_Search.cs w folderze Index

public class Step_Search : AbstractIndexCreationTask
{
public class ReduceResult
{
public string[] QueryFields { get; set; }

// ... code eliminated for reading purpose
}

public Step_Search()
{
Map = steps =>
from step in steps
select new
{
QueryFields = new [] { step.State, step.Answer, step.AnsweredBy, step.WorkflowName,
step.Created.ToShortDateString(), step.Created.Year.ToString(),
step.Created.Month.ToString() + "/" + step.Created.Year.ToString()},
DateCreated = step.Created,
WorkflowName = step.WorkflowName,
State = step.State
};
Indexes.Add(x => x.QueryFields, FieldIndexing.Analyzed);

// ... more code eliminated for reading purposes

Więc to, co zrobiliśmy, to stworzyć indeks zawierający tablicę łańcuchów. Ta tablica zawiera tekst naszych właściwości, z którymi będziemy się dopasowywać. Raven ma metodę o nazwie Search, która będzie wykonywać dopasowanie stylu “StartsWith” dla każdego obiektu w tablicy. Wywołanie to .Search (x => x.QueryFields, “ciąg do przeszukania”). Jeśli spojrzysz na indeks, zrobiliśmy kilka dodatkowych rzeczy z datami: dla jednego tworzymy ciąg znaków w formacie ShortDate. Tak więc, gdy użytkownik zna dokładną datę, kiedy może go wprowadzić, a Pager go dopasuje. Ale chcemy sprawić, aby było to jak najprostsze, dlatego stworzyliśmy ciągi znaków w formacie mm / rrrr, więc użytkownicy mogą łatwo filtrować, jeśli znają tylko miesiąc i rok poszukiwanego elementu. “Myślę, że to był kwiecień zeszłego roku …”.

Jeszcze jedna rzecz, zanim przejdziemy do pracy z DataTables. Raven zapewnia metodę wyszukiwania, która działa z kolekcją IRavenQueryable. Spójrz na metodę DataTablesPager.Filter, a zobaczysz metodę SearchMultiple. Zostało to wprowadzone w celu wyszukiwania wielu terminów. Innymi słowy, wyszuka hasło “Spock”, a następnie wyszuka hasło “2010” w IRavenQueryable. Phillip Haydon wpadł na takie podejście, które działa z częściowymi dopasowaniami, ponieważ da to Lucene właściwą składnię. W przeciwnym razie otrzymasz dziwne wyniki, ponieważ karmisz Lucene “spoc 201”, a dzięki tokenom, które Lucene tworzy za pomocą analizatora tekstu, nie odbierze tego, czego potrzebujesz. Doskonałe podejście Phillipa wypełnia tę lukę, wykorzystując metodę rozszerzenia do wykonywania łańcuchów wyszukiwanych haseł. Znajduje się on w klasie RavenExtensionMethods.cs i zasadniczo tokenizuje ciąg wyszukiwania, tworzy tablicę i indywidualne wywołanie metody Search () dla elementu tablicy. Pozwala nam wykonywać zaawansowane filtrowanie, takie jak częściowe dopasowania, takie jak “spoc 201”. Wypróbuj to na stronie Tenant.aspx rozwiązania WebDemo, zobaczysz, jak to działa.

Outta Breath Yet? Let’s Talk DataTables.Net !!!

Oddychaj ciężko jeszcze? Dobry! Jest więcej do zrobienia – jak to działa z DataTables.Net? DataTables używa następujących parametrów podczas przetwarzaniadanych po stronie serwera :

Wysłane na serwer :

Rodzaj Imię Informacje
int iDisplayStart Wyświetl punkt początkowy
int iDisplayLength Liczba rekordów do wyświetlenia
int iColumns Liczba wyświetlanych kolumn (przydatne do uzyskania informacji o wyszukiwaniu poszczególnych kolumn)
string sSearch Globalne pole wyszukiwania
boolean bEscapeRegex Wyszukiwanie globalne to wyrażenie regularne lub nie
boolean bSortable_ (int) Wskaźnik, jeśli kolumna jest oflagowana jako możliwa do sortowania lub nie po stronie klienta
boolean bSearchable_(int) Wskaźnik, jeśli kolumna jest oznaczona jako przeszukiwalna lub nie po stronie klienta
string sSearch_ (int) Indywidualny filtr kolumnowy
boolean bEscapeRegex_(int) Indywidualny filtr kolumnowy to wyrażenie regularne lub nie
int iSortingCols Liczba kolumn do sortowania
int iSortCol_ (int) Kolumna jest posortowana (musisz rozszyfrować ten numer dla swojej bazy danych)
string sSortDir_ (int) Kierunek do posortowania – “desc” lub “asc”. Zwróć uwagę, że przedrostek dla tej zmiennej jest nieprawidłowy w wersji 1.5.x, gdzie użyto iSortDir_ (int )
string sEcho Informacje dotyczące DataTables do użycia do renderowania

Odpowiedz z serwera

W odpowiedzi na każde żądanie informacji, które DataTables wysyła na serwer, spodziewa się uzyskać dobrze utworzony obiekt JSON z następującymi parametrami.

Rodzaj Imię Informacje
int iTotalRecords Całkowita liczba rekordów przed filtrowaniem (tj. Całkowita liczba rekordów w bazie danych)
int iTotalDisplayRecords Całkowita liczba rekordów po filtrowaniu (tj. Całkowita liczba rekordów po filtrowaniu została zastosowana – nie tylko liczba rekordów zwracanych w tym zestawie wyników)
string sEcho Niezmieniona kopia sEcho wysłana ze strony klienta. Ten parametr zmienia się przy każdym losowaniu (jest to w zasadzie liczba losowań) – dlatego ważne jest, aby to zostało zaimplementowane. Zauważ, że ze względów bezpieczeństwa zdecydowanie zaleca sięrzutowanie tego parametru na liczbę całkowitą, aby zapobiec atakom Cross Site Scripting (XSS).
string sColumns Opcjonalnie – jest to ciąg nazw kolumn oddzielonych przecinkami (używanych w połączeniu z sName ), które umożliwiają DataTables zmianę kolejności danych po stronie klienta, jeśli jest to wymagane do wyświetlenia
array

array

mixed

aaData Dane w tablicy 2D

DataTables będzie POST obiekt AOData. Klasa DataTablesPager.cs obsłuży parsowanie tego obiektu za pomocą metody PrepAOData. Jest odpowiedzialny za określenie właściwości, o które pytamy, sposobu sortowania danych, wielkości stronicowania, a także wszelkich warunków filtrowania. Ponieważ używaliśmy generycznych, PrepAOData nie dba o to, jakiego obiektu w twojej domenie używasz, ponieważ jest zaprojektowany do odczytu właściwości i dopasowania tych właściwości do listy elementów danych, które DataTables wysłał do naszej aplikacji na serwerze. Ponownie, naszym celem jest umożliwienie DataTables narzucenia tego, czego należy szukać, i tak długo, jak robiliśmy naszą pracę, gdy tworzyliśmy indeks, powinniśmy mieć dużą elastyczność.

Spójrzmy teraz na JavaScript:

"aoColumnDefs": [
{ "sName": "Name", "aTargets": [0], "bSortable": true, "bSearchable": true },
{ "sName": "Agent", "aTargets": [1], "bSortable": true, "bSearchable": true },
{ "sName": "Center", "aTargets": [2], "bSortable": true, "bSearchable": true },
{ "sName": "StartDate", "aTargets": [3], "bSortable": true, "bSearchable": true },
{ "sName": "EndDate", "aTargets": [4], "bSortable": true, "bSearchable": true },
{ "sName": "DealAmount", "aTargets": [4], "bSortable": true, "bSearchable": true }
],

W aplikacji po stronie serwera mamy Tenant_Search.cs, który utworzył indeks z właściwościami Name, Agent, Center, StartDate, EndDate i DealAmount. Powyższy Javascript to sposób, w jaki DataTables mówi “Hej, serwer, daj mi informacje z powrotem w postaci szeregu par wartości i po drodze, oto kolumny danych, których należy użyć, gdy dostaniesz moje rzeczy.” Po stronie serwera , nie obchodzi nas, jaka będzie kolejność kolumn w siatce, ponieważ serwer zakłada, że DataTables to zajmie. I rzeczywiście, DataTables dostarcza żądanie w parze wartości sName. Serwer pobiera go, wypluwa z powrotem do przeglądarki, a DataTables go zatrzymuje. Możesz zmienić JavaScript i pozostawić samą aplikację serwera, dopóki będziesz trzymać się pól uwzględnionych w indeksie. Tak jak Fonzie, bądź spokojny.

Ale jeszcze chłodniejszy jest fakt, że Raven będzie obsługiwał stronicowanie dla nas: ma wbudowany limit zwrotu do 128 dokumentów w plasterku. Biorąc pod uwagę, że prędkość jego pobierania jest bardzo szybka, będzie nam dobrze działać. Jeśli spojrzysz na konsolę Raven dla każdej pobieranej strony, zobaczysz bardzo niski czas pobierania. Pamiętaj, że zapytanie jest bardzo mało przetwarzane, ponieważ jest to indeks, który już wykonał podnoszenie ciężkie. Na przykład strona Tenants.aspx w rozwiązaniu WebDemo będzie wyświetlać i filtrować 13 000 + dokumentów. Jest błyskawicznie szybki.

Czy Twoja głowa eksplodowała?

To jest dużo do strawienia. Kod źródłowy jest tutaj, wraz ze środkami do tworzenia 13 000 dokumentów, które można wykorzystać do testowania. Pamiętaj, że będziesz musiał usunąć zespoły / pakiety Kruka przez NuGet. W przeciwnym razie pobieranie będzie wynosić około 36 MB. Rozpoczęto pracę związaną z odpowiedzią na prośbę o sortowanie i mam nadzieję, że będziesz chciał zobaczyć, jak to rozwiązano w przyszłym poście. Ale osiągnęliśmy bardzo solidny i łatwy sposób wyświetlania danych z naszej bazy danych dokumentów, przy niewielkim nakładzie pracy na aplikację zaplecza.

Graj z kodem. Jedynym sposobem, w jaki to robimy, jest konstruktywna krytyka, adaptacja do lepszych pomysłów i rozwój! Niektóre z nieudanych eksperymentów zostały uwzględnione w teście, dzięki czemu można zobaczyć, jak postępują. Oznaczono jako niepowodzenia, dzięki czemu można skupić się na testowaniu klasy DataTablesPager. Te niepowodzenia są jednak interesujące i mogą dostarczyć informacji o tym, w jaki sposób osiągnięto rozwiązanie. Za pierwszym razem, gdy uruchomisz stronę internetową, strona Global.ascx będzie szukała rekordów testu i utworzyła je. Zajmuje to trochę czasu, więc jeśli chcesz poczekać, te sekcje są oznaczone, więc je skomentujesz i zrobisz to, czego potrzebujesz. Cieszyć się.

David Robbins jest ActiveEngine Sensei i publikuje swoje opowiadania o Internecie, rozwoju oprogramowania w .Net, Elvis i Zen wydajności na blogu ActiveEngine.Net . Czai się w zewnętrznej krainie oprogramowania open source i uwielbia mieszać najlepsze rozwiązania z jQuery, KnockoutJs i C #.

Leave a Reply