Entity Framework missing FAQ (Part 1 and Part 2)
- 1. Database First
- 1.1. Традиційні DB First без моделера Visual Studio
- 1.2. Вам допоможе Nuget.ORG
- 1.3. ObjectContext vs DbContext
- 1.4. Поширення EF
- 1.5. Довідники EF
- 2. Model First
- 2.1 Переваги Model First над Database First
- 2.2 Приклад
- 2.3 Схема моделі вбудована у ресурс DLL
- 2.4 Міграція (Package console/CMD utility/site code)
- 2.5 Публікація проекту з EF
- 2.6 Профайлер трансляции EF з Linq у SQL та EF Retry Policy (повний код проєкту)
- 2.7 Альтернативи мікрософтовському ModelFirst
- 3. Code First
- 3.1. Site with Project and without project (порівняння типів проєктів).
- 3.2. Стартовий MVC-project Contoso University in VB.
- 3.3. Attributes and Fluent API.
- 3.4 Перелік атрибутів Data Annotations.
- 3.5 Scaffold template and custom scaffold template.
- 3.6 Close look to controller code
- 3.7 Виготовлення власних Extension-хелперов
- 3.8 Store state in ViewState/ViewBag/ViewData/TempData
Entity Framework missing FAQ (Part 3).
Я, нажаль, не встиг закінчити другу частину у минулому році, вибачаюсь, бо не мав достатньо вільного часу, тому закінчую лише зараз.
- 3.9 Склад стартового проєкту.
- 3.10 Deploy Database to SQL server.
- 3.10.1 Manually Deploy.
- 3.10.2 Визначення зв'язків даних 1:1, 1:M, M:M у базі та EF CodeFirst.
- 3.10.3 Visual Designer.
- 3.10.4 Unexpected Deploy.
- 3.10.5 Save relation 1:M, M:M to DB.
- 3.11 Ще раз про Scaffold template.
- 3.11.1 Collection.Generic.List (of T), IQueryable vs IEnumerable
- 3.11.2 Атрибут Bind
- 3.11.3 Антиспам валідатор Validate Anti Forgery Code
- 3.11.4 jquery.validate.unobtrusive.js
- 3.12. MVC paging, sorting, filtering in EF level.
3.9 Склад стартового проєкту.
Стартовий проєкт цієї сторінки знаходиться ось тут, він нормально компілюється. Цю сторінку я робив трохи пізніше попередньої частини, тому проапгрейдів проєкт до NET 4.7.2.
Нище ви бачите опис файлів стартового проєкту цієї сторінки:
- Models\SchoolContext.vb
- Models\Course.vb
- Models\Enrollment.vb
- Models\Student.vb
- Models\Department.vb
- Models\Instructor.vb
- Models\OfficeAssignment.vb
- Models\CourseAssignment.vb
- Models\Person.vb
- Models\ErrorViewModel.vb
Сурогат вьюшек SQL-серверу, визначений на рівні кода, а не у самому SQL-сервері. Як я казав у першій частині, MS SQL база має крім табличок ще приблизно сто специфічних об'єктів, які Entity Framework ніяк їх не підтримує, але має ось такий засіб хоча б зібрати дані у такий сурогат вьюшек:
- Models\SchoolViewModels\AssignedCourseData.vb
- Models\SchoolViewModels\EnrollmentDateGroup.vb
- Models\SchoolViewModels\InstructorIndexData.vb
Ну я вже не кажу про службовий код, безпосередньо пов'язаний з Entity Framework:
- Data\EF_Config.vb, ILogger.vb, Logger.vb, - не є необхідним, це просто спроба мікрософтовських індусів перенести профайлер SQL-серверу у власний код
- Data\InterceptorLogging.vb, InterceptorTransientErrors.vb - те ж саме, але актуально для перевантаженого мікрософтовського хостингу Azure з величезною кількістю помилок у запросах
- Data\DbInitializer.vb - код необхідний мастеру Студії для заповнення табличок тестовими даними
- Migrations\Configuration.vb - код необхідний мастеру Студії для виконання розгортання класів у базу
- Migrations\201801202344102_InitialCreate.vb - код, утворений мастером Студії (командою add migration InitialCreate у даному випадку) для виконання розгортання класів у базу.
Якщо б ми користувалися звичайним Linq-to-SQL увесь вище перерахований код був би утворений автоматично, коли ми мишкою б перетягли іконку з базою на фейс сторінки DBML. Але у EF Code First нам необхідно ручками надзвичайно ретельно самому написати цей код без жодної помилки. Це й є ускладнення Code First порівняно з існуючим вже 20-30 років засобом Database First. Інші файли проєкту були б присутні, якщо б ми не користувалися Entity Framework, але б користувалися MVC-роутінгом сторінок (якщо б ми не користувалися MVC, то потрібно б були лише Global.asax та Web.config).
- Web.config - містить посилання на базу, на необхідні для роботи проєкту сборки
- Global.asax - у моєму проєкті містить явний визов EF, щоб його можна було легко закоментувати та перейти на DatabaseFirst.
- Views\_ViewStart.vbhtml - містить лінк на головну сторінку сайту Mvc
- Views\Shared\Error.vbhtml - заглушка сторінки для відображення помилок
- App_Start\RouteConfig.vb - містить визначення MVC для роутера, яку сторінку потрібно викликати по реквесту браузера, щоб її ім'я було незрозуміле клієнту
- App_Start\BundleConfig.vb - містить посилання на бібліотеку запаковки JavaScript
- App_Start\FilterConfig.vb - містить код фільтрації MVC-реквестів
Далі йде декілька важливих файлів самої студії, які визначають середовище компіляції та роботи проєкту.
- packages.config - усі бібліотеки знаходяться у папці packages поруч з проєктом. Це непоганий сервіс, за допомогою Nuget.ORG можна отримати взаємно пов'язані версії необхідних бібліотек.
- CU-VB-3.vbproj - головний файл проєкту, який містить теж визначення середовища роботи Студії та опис процеса компіляції сайту у бінарнік для виконання IIS-ом.
- My Project\PublishProfiles\/FolderProfile.pubxml.htm - опис засобу розгортання проєкту на хостинг
Ще декілька загальних файлів проєкту.
- каталог Scripts, який містить Ява-скріпти jQuery, Bootstrap та Modernizr
- каталог Content, який містить декілька загальних CSS-стілей, які використовують бібліотеки Яваскріпт, та наш власний файл Site.CSS
Усе це вище - лише деякі службові файли (частину яких можна б було взагалі утворити автоматично, наприклад по DatabaseFirst або по Linq-to-SQL), а частина взагалі була б непотрібна, якщо б ми не користувалися модним MVC-роутінгом. Поки що безпосередньо про код сайта ми ще не згадували взагалі. Єдиний спеціфічний для нашого проєкту файл, крім пов'язаних з EntityFramework, згаданий вище - це файл Site.CSS. I ось прийшла черга перерахувати саме файли з кодом VB та HTML нашого проєкту - у нашому проєкті 71-файл, але лише три з них безпосередньо пов'язані з нашим сайтом, якщо не рахувати визначення даних у базі та CSS-стіля.
- Controllers\HomeController.vb - поки що у даному проєкті заглушка, тут будуть сформовані дані, які будуть відображатися на сторінках
- Views\Home\Index.vbhtml - власне смисловий контент сайта, поки що лише заглушка
- Views\Shared\_Layout.vbhtml - мастер-пейдж, тобто плашка сайта, яка містить незмінну шапку, подвал та меню сайта.
3.10 Deploy Database to SQL server.
3.10.1 Manually Deploy.
Але пам'ятаємо про загальний сенс існування технології CodeFirst - постійне розгортання класів у базу з кожною модифікацією класів. Тому для початку спробуємо розгорнути поточну версію класів у базу за допомогою Package Console. Як бачите, класи розгорнулися у базу, навіть з необхідними тестовими даними.
3.10.2 Визначення зв'язків даних 1:1, 1:M, M:M у базі та EF CodeFirst.
Подивимося тепер на базу, у тому вигляді як це робилося останні 30 років та порівняємо з визначенням маркованих буферів даних у Code First.
- 1:M - Department тут довідник, а Course - основна табла.
1: create table [dbo].[Department] (
2: [DepartmentID] [int] not null identity,
3: [Name] [nvarchar](50) null,
4: [Budget] [money] not null,
5: [StartDate] [datetime] not null,
6: [InstructorID] [int] null,
7: [RowVersion] [rowversion] not null,
8: primary key ([DepartmentID])
9: );
10:
11: create table [dbo].[Course] (
12: [CourseID] [int] not null,
13: [Title] [nvarchar](50) null,
14: [Credits] [int] not null,
15: [DepartmentID] [int] not null,
16: primary key ([CourseID])
17: );
18:
19: alter table [dbo].[Course] add constraint [Department_Courses] foreign key ([DepartmentID]) references [dbo].[Department]([DepartmentID]) on delete cascade;
- M:M - Cource : Student, табла зв'язку Enrolnment
1: create table [dbo].[Student] (
2: [ID] [int] not null,
3: [EnrollmentDate] [datetime] not null,
4: primary key ([ID])
5: );
6:
7: create table [dbo].[Person] (
8: [ID] [int] not null identity,
9: [LastName] [nvarchar](50) not null,
10: [FirstName] [nvarchar](50) not null,
11: primary key ([ID])
12: );
13:
14: alter table [dbo].[Student] add constraint [Student_TypeConstraint_From_Person_To_Student] foreign key ([ID]) references [dbo].[Person]([ID]);
15:
16: create table [dbo].[Course] (
17: [CourseID] [int] not null,
18: [Title] [nvarchar](50) null,
19: [Credits] [int] not null,
20: [DepartmentID] [int] not null,
21: primary key ([CourseID])
22: );
23:
24:
25: create table [dbo].[Enrollment] (
26: [EnrollmentID] [int] not null identity,
27: [CourseID] [int] not null,
28: [StudentID] [int] not null,
29: [Grade] [int] null,
30: primary key ([EnrollmentID])
31: );
32:
33: alter table [dbo].[Enrollment] add constraint [Enrollment_Course] foreign key ([CourseID]) references [dbo].[Course]([CourseID]) on delete cascade;
34: alter table [dbo].[Enrollment] add constraint [Student_Enrollments] foreign key ([StudentID]) references [dbo].[Student]([ID]);
- 1:1 Student:Instructor визначено вище.
Тепер теж саме, але за допомогою EF.
- 1:M - Department тут довідник, а Course - основна табла.
51: Public Class Department
53: Public Property DepartmentID As Integer
38: Public Class Course
40: <DatabaseGenerated(DatabaseGeneratedOption.None)>
41: <Display(Name:="Number")>
42: Public Property CourseID As Integer
50: Public Property DepartmentID As Integer
52: Public Property Department As Department
- M:M - Cource : Student, табла зв'язку Enrolnment
38: Public Class Course
40: <DatabaseGenerated(DatabaseGeneratedOption.None)>
41: <Display(Name:="Number")>
42: Public Property CourseID As Integer
54: Public Property Enrollments As ICollection(Of Enrollment)
159: Public Class Student
160: Inherits Person
167: Public Property Enrollments As ICollection(Of Enrollment)
6: Public Class Person
8: Public Property ID As Integer
57: Public Class Enrollment
59: Public Property EnrollmentID As Integer
61: Public Property CourseID As Integer
63: Public Property StudentID As Integer
68: Public Property Course As Course
70: Public Property Student As Student
- 1:1 Student:Instructor визначено вище.
Another example of definition relation 1:M, M:1, 1:1, M:M see in this page FinancialBroker - MDI application with EF code first database..
3.10.3 Visual Designer.
Цікаво, що якщо додати EF Power pack, та клацнути View Model, то у web.config буде додан конект до розгорнутої бази і можна буде візуально подивитися на структуру бази у студії, тобто нібито перейти у режим Model First, тобто нібито повернутися на 30 років тому у SQL Server 7, де й існувала ця діаграма взаємовідносин табл, але тепер вона незрозуміло для чого перенесена з MS SMS безпосередньо у Visual Studio. Схоже на якісь іграшки Мікрософта, з величезними зусиллями за 20 років зробили дубль існуючого софта, але працюючий у мільйон разів повільніше. Не підтримуються базові можливості MS SQL, але з'явився мапінг внутрішніх класів-об'єктів на табли бази.
Хе-хе, не знаю як вам, але мені набагато зручніше зрозуміти структуру даних у візуальному вигляді SQL, як це й робилося останні 30 років, ніж по модному індуському EF Code First, що описує цей скрін.
3.10.4 Unexpected Deploy.
До запуску сайту нам потрібно спочатку необхідно визначити у конфігі ConnectionString до бази.
І тут з'ясувався один цікавий пунктик, спочатку я розгортав базу мастером студії 3.8.1 Manually Deploy. (тобто командою update-database -verbose) у базу .\SQLExpress, але потім, коли клацнув у Solution Explorer VS2017 пункт Entity Framework -> View Entity Data Model (read only), тобто 3.10.2 Visual Designer., мастер студії модифікував мій Web.config та додав туди Connection String, який дивився вже зовсім у інший сервер, у якому теж розгорнулася база (LocalDb)\MSSQLLocalDB. Тобто я спочатку навіть не зрозумів, що моя база розгорнулася у два місця, у одне місце я розгорнув її ручками, у друге місце її розгорнув Visual Designer з пакету EF Power Pack. Треба бути обережним з цим, бо мені здавалося, що метадані проєкта описують місце розгортання бази, але це не так. EF у метаданих проєкта не має посилання на місце розгортання бази, якщо наприклад ви доставили собі на кампутер ще один instance SQL-серверу, база несподівано може розгорнутися у ньому, навіть коли ви цього не очікуєте.
3.10.5 Save relation 1:M, M:M to DB..
Зберігання зв'язків у базу я покажу на прикладі іншого сайту, я його тільки почав розробляти, коли писав ці нотатки по ContosoUniversity, й тому він ще дуже простий та у ньому все більш зрозуміло. Я зробив там ось таку модель, тобто кожне резюме має декілька скілів, які угруповані у мета-скіл групи. Цей проєкт я робив як модел-first, це більш зручно для мене.
.EDMX-designer зробив мені з цієї екстремально простої моделі ось такі класи.
1: Partial Public Class [Resume]
2: Public Property Id As System.Guid
3: Public Property Title As String
4: Public Property PdfFileName As String
5: Public Property InputAvaFile As String
6: Public Property Description As String
7: Public Property PublicContacts As String
8: Public Property AdminContact As String
9: Public Property Login As String
10: Public Property Pass As String
11: Public Property CrDate As Date
12:
13: Public Overridable Property Skills As ICollection(Of Skill) = New HashSet(Of Skill)
14:
15: End Class
1: Partial Public Class Skill
2: Public Property Id As Integer
3: Public Property SkillName As String
4: Public Property SkillTypeId As Short
5:
6: Public Overridable Property SkillType As SkillType
7: Public Overridable Property Resumes As ICollection(Of [Resume]) = New HashSet(Of [Resume])
8:
9: End Class
1: Partial Public Class SkillType
2: Public Property Id As Short
3: Public Property SkillTypeName As String
4:
5: Public Overridable Property Skills As ICollection(Of Skill) = New HashSet(Of Skill)
6:
7: End Class
При розгортанні у базу цієї екстремально простої моделі утворилася додаткова табла ResumeSkill, яка зберігає відносини M:M і та до записів якої немає безпосереднього доступа з коду, лише тільки через роботу з колекціями ось так:
...
52: Dim NewResume As New [Resume] With {
53: .Id = Guid.NewGuid,
54: .CrDate = Now,
55: .Title = Model.Title,
56: .Description = Model.ShortDescription,
57: .PublicContacts = Model.PublicContacts
58: }
...
73: Dim DB1 As New ProgrammerExpertContainer
74: For Each One In Model.Skills
75: Dim SkillRow = DB1.Skills.FirstOrDefault(Function(x) x.SkillName = One)
76: NewResume.Skills.Add(SkillRow)
77: Next
78: DB1.Resumes.Add(NewResume)
79: DB1.SaveChangesAsync()
Таким чином, коли ми не маємо з коду доступу до табли зв'язків M:M ResumeSkill, яка зберігається у базі та виконує тут головну роль, утворюється ілюзія, що ми нібито працюємо лише з об'єктами та колекціями, а не с плоскими реляційними даними. Тобто EF це такий слой коду, який нам забезпечує доступ до об'ектів на верхньому рівні, а сам цей код мапірує усі об'екти на реляційні базу (та навіть на MongoDB) так, що ми цього не бачимо взагалі.
3.11 Ще раз про Scaffold template.
Але повернемось до проєкту ContosoUniversity. Я вже казав раніше пару слів про Scaffold template, давайте ще раз повернемося. Якщо ми подивимося на схему даних, то зрозуміємо, що Student, Instructor, Course, Department - це чисті довідники (справочники), тобто вони потребують лише найпростіших CRUD-операцій. Отже ми можемо спробувати зробити чотири скафорд-шаблона на цих таблах. Почнемо, наприклад з student та подивимося, що утворив мастер Scaffold:
У більш складних випадках використовуютья власні шаблони Use UIHint attribute to define template for EditorFor and DisplayFor helpers.
3.11.1 Collection.Generic.List (of T), IQueryable vs IEnumerable
Хм, чудово. Все вибрали правильно, але скафолд утворив якісь дурниці замість коду. З одного боку, у вьюху передається List (of T), з іншого боку вьюха очікує IEnumerable. Більш того, ми попросили передати таблу Student, а Скафолд-мастер утворив передачу табли People. Все, як і завжди у мікрософті, навіть на найпростіших тестових прикладах ніхто цей код ніколи не перевіряв, незважаючи на тисячу мікрософтовских книг про необхідність тестів написаного програмного кода.
11: ....
12:
13: Namespace Controllers
14: Public Class StudentsController
15: Inherits System.Web.Mvc.Controller
16:
17: Private db As New SchoolContext
18:
19: ' GET: Students
20: Async Function Index() As Task(Of ActionResult)
21: Return View(Await People.ToListAsynk())
22: End Function
23:
24: ....
1: @ModelType IEnumerable(Of CU_VB_3.Models.Student)
2: @Code
3: ViewData("Title") = "Index"
4: Layout = "~/Views/Shared/_Layout.vbhtml"
5: End Code
6: ....
Ось тут повний код контролера Student, та п'ять вьюшек, що утворив мастер Scaffold (після мого приведення до IEnumerable). До речі, вьюшки непогані та корисні, у випадках настільки простих форм це реально полегшує працю програміста, погано що у реальних проєктах таких форм мабуть меньше 10%.
- Students/Index.vbhtml
- Students/Create.vbhtml
- Students/Delete.vbhtml
- Students/Details.vbhtml
- Students/Edit.vbhtml
Додамо у плашку Layout виклики редакторів цих довідників та спробуємо викликати цю сторінку.
Так і є, працювати не буде. Тому передамо у вьюху правильну таблу та виконаємо коректне перетворення System.Collection.Generic.List (of T) до IEnumerable:
12:
13: Namespace Controllers
14: Public Class StudentsController
15: Inherits System.Web.Mvc.Controller
16:
17: Private db As New SchoolContext
18:
19: ' GET: Students
20: Async Function Index() As Task(Of ActionResult)
21: Return View(Await Task.FromResult(db.Students.ToList()))
22: End Function
23 ....
Так працює все добре, зрозуміло що довідники видаляти не можна, поки на них є референси у інших таблах.
І щоб закінчити тему про засоби передачі регулярних даних, зробимо ще одну помітку, зверніть увагу що прості LINQ-запроси взагалі не видають ніяких реквестів у базу, це лише визначення інтерфейсу IQueryable(of T) - "Queryable take expression trees, which - rather than regular IL, get compiled to an object model", безпосередньо звернення у базу виконуються лише при викликанні Where(), ToList(), Select(), тобто лише тоді сформовані компілятором статичні визначення Expression Trees почнуть виконуватися. Дебагер Студії вміє виконувати визначення IQueryable(of T), але вони виконуються один раз, потім настає стан EOF, кінця даних та повторити прохід по даних IQueryable неможливо. Звичайно, що коли дані перетворені у List - то по буферу з даними System.Collection.Generic.List (of T) можна рухатися скільки завгодно у будь якому напрямку, навіть без звернення у базу.
More about IEnumerable.
- Yield/Iterator/IEnumerable - і ці люди забороняли нам багато років колупатися у носі?
- Serialize Table to CSV with Iterator and Yeld
- Generic VB.NET function for loading ExcelFile to DataBase (EF6 CodeFirst) with Reflection and avoiding Linq-to-Entity Linq.Expressions (use Linq-to-Object IEnumerable)
- Collection.Generic.List (of T), IQueryable vs IEnumerable.
3.11.2 Атрибут Bind
Давайте подивимось далі на код, що утворив Scaffold. Як я казав раніше - код контролера насичений атрибутами, я перерахував сотню атрибутів ось тут 3.6 Close look to controller code Але зараз я звернув увагу на стрічку :
Взагалі з моменту утворення ASP.NET ми користувалися різноманітними парсерами постбеков, наприклад ось тут лежить опис одного з них - Пакетный загрузчик файлов на сайт, ось тут ще один Перемикач мови для сайту, зазвичай я прибіндюю параметри ось так Мой первый сайт на MVC 3 Razor, Общий шаблон простейшего приложения на ASP NET MVC, Формування безопасного токена для зміни паролю, Додаток про загальний код у проектах MVC.. Я так робив у достатньо великих своїх комерційних MVC-проєктах, наприклад Проекты нового Вотпуска, Электронный магазин запчастей SHEL-AUTO.RU на web-сервисах EMEX.RU, Social network for Canada with printed version of calendar to communities та інших. Але я ніколи ще не використовував аттрібут BIND. Тому я звернув на нього увагу як на ще один засіб распарсити постбек параметри колекції Request.FormCollection.
3.11.3 Антиспам валідатор Validate Anti Forgery Code
Ще один цікавий атрибут з сотні головних атрибутів MVC та NET, якого здається не було у попередніх версіях MVC - ValidateAntiForgeryToken.
Цей атрибут додає Hidden field кожному реквесту, без якого реквест на сайт не буде прийматися ковеером IIS, який обробляє усі реквести та передає їх формам сайту. Якщо ми уважно роздимивося сформовану HTML-сторінку, то побачимо це Hidden field у кожному тегі <form.
39: <form action="/Students/Edit/1" method="post">
41: <input name="__RequestVerificationToken" type="hidden" value="Rg2XiX--2-Ylkgi3W-b7Oq0p5ZaRXAi5X_N4feQs24Y5hk0L4HjzT_gW2q_YJKbryT_XDqQpRn_Y0yxHuJgE-HVdhgj_igKidP4Kmw_bYpY1" />
42:
Another approach to prove human Use Google reCaptcha ASP.NET MVC (clearly in View and as filter attribute).
3.11.4 jquery.validate.unobtrusive.js
Ще можна звернути увагу на сервіс unobtrusive, який дозволяє формувати більш приємний код валідації нестандартними тегами HTML.
52: <input class="form-control text-box single-line" data-val="true" data-val-length="The field Last Name must be a string with a maximum length of 50." data-val-length-max="50" data-val-required="The Last Name field is required." id="LastName" name="LastName" type="text" value="Alexander1" />
Щоб unobtrusive-валідація запрацювала, треба додати необхідні параметри у конфіг та, зрозуміло, власне скрипти на сторінку (за допомогою Nuget наприклад) .
Інші засоби валідації (за допомогою власного контроллеру) дивиться на сторінці Use jQuery Unobtrusive Validation, custom attributes for validation and validation service/controller.
3.12. MVC paging, sorting, filtering in EF level.
Звичайна, та найбільш поширена технологія - це піднімати з SQL лише ті дані, які потрібні, сортувати та фільтрувати їх безпосередньо у SQL-Сервері, ось так SPA-page на Classic ASP.NET та jQuery., Делаем SEO SiteMap., Мой первый фото-слайдер на Flex 4, Collection and processing information about your system. - це так званий Connected Data Mode. Він автоматично вирішує проблеми будь-яких блокировок на рівні SQL та є найшвидшим. Я саме його застосую у більшості своїх проєктів, бо вважаю його найкращім. Будь які пейджери MVC-сторінок я роблю точно так же, й не тільки для MVC, але й для класичного ASP.NET - Пейджер для DataList.
Але існує протилежний погляд на речі - вичитати усю базу у пам'ять і більш не чіпати її. Це так званий Disconected Mode. У EF принципово можливо працювати і так і інакше, але якщо ви працюєте у disconnected mode то це складніше, сайт може легко заблокуватися, потрібно також розуміти що таки Optimistic/Pessimistic mode, потрібно розуміти які методи Бейсіка виробляють запроси в SQL, а які лише працюють з буферами пам'яті, також потрібно розуміти стан записів у буферах.
Тому мені було дуже цікаво, що саме пропонують мікрософтовськи індуси. А вони зробили ось такий клас:
1: Imports System.Data.Entity
2: Imports System.Threading.Tasks
3:
4: Public Class PaginatedList(Of T)
5: Inherits List(Of T)
6:
7: Public Property PageIndex As Integer
8: Public Property TotalPages As Integer
9:
10: Public Sub New(ByVal items As List(Of T), ByVal count As Integer, ByVal pageIndex_ As Integer, ByVal pageSize As Integer)
11: PageIndex = pageIndex_
12: TotalPages = CInt(Math.Ceiling(count / CDbl(pageSize)))
13: Me.AddRange(items)
14: End Sub
15:
16: Public ReadOnly Property HasPreviousPage As Boolean
17: Get
18: Return (PageIndex > 1)
19: End Get
20: End Property
21:
22: Public ReadOnly Property HasNextPage As Boolean
23: Get
24: Return (PageIndex < TotalPages)
25: End Get
26: End Property
27:
28: Public Shared Async Function CreateAsync(ByVal source As IQueryable(Of T), ByVal pageIndex As Integer, ByVal pageSize As Integer) As Task(Of PaginatedList(Of T))
29: Dim count = Await source.CountAsync()
30: Dim items = Await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync()
31: Return New PaginatedList(Of T)(items, count, pageIndex, pageSize)
32: End Function
33: End Class
Потім зробили ось таку сторінку та ось такий контролер, у якому, нажаль у звичайному MVC (не CORE) нічого не працює, тільки у CORE, бо не вистачає методів TryUpdateModelAsync, ThenInclude, Update. Але для пейджера це не важливо.
Але те, що стосується пейджінга та фільтрації - працює чудово.
На сторінці це виглядає ось так:
1: @ModelType PaginatedList(Of CU_VB_3.Models.Student)
..
16: Find by name: <input type="text" name="SearchString" value='@ViewData("currentFilter")' />
17: <input type="submit" value="Search" class="btn btn-default" /> |
..
27: @Html.ActionLink("Last Name", "Index", New With {.SortOrder = ViewData("NameSortParm")})
..
33: @Html.ActionLink("Enrollment Date", "Index", New With {.SortOrder = ViewData("DateSortParm")})
..
61: @Code
62: Dim prevDisabled = If(Not Model.HasPreviousPage, "disabled", "")
63: Dim nextDisabled = If(Not Model.HasNextPage, "disabled", "")
64: End Code
..
67: @Html.ActionLink("Previous", "Index", New With {.SortOrder = ViewData("CurrentSort"), .Page = (Model.PageIndex - 1), .currentFilter = ViewData("CurrentFilter")}, New With {.class = "btn btn-default " & prevDisabled})
68: @Html.ActionLink("Next", "Index", New With {.SortOrder = ViewData("CurrentSort"), .Page = (Model.PageIndex + 1), .currentFilter = ViewData("CurrentFilter")}, New With {.class = "btn btn-default " & nextDisabled})
А у контролері ось так:
191: Public Async Function Index(ByVal sortOrder As String, ByVal currentFilter As String, ByVal searchString As String, ByVal page As Integer?) As Task(Of ActionResult)
192: ViewData("CurrentSort") = sortOrder
193: ViewData("NameSortParm") = If(String.IsNullOrEmpty(sortOrder), "name_desc", "")
194: ViewData("DateSortParm") = If(sortOrder = "Date", "date_desc", "Date")
195:
196: If searchString IsNot Nothing Then
197: page = 1
198: Else
199: searchString = currentFilter
200: End If
201:
202: ViewData("CurrentFilter") = searchString
203: Dim students = From s In _context.Students Select s
204:
205: If Not String.IsNullOrEmpty(searchString) Then
206: students = students.Where(Function(s) s.LastName.Contains(searchString) OrElse s.FirstMidName.Contains(searchString))
207: End If
208:
209: Select Case sortOrder
210: Case "name_desc"
211: students = students.OrderByDescending(Function(s) s.LastName)
212: Case "Date"
213: students = students.OrderBy(Function(s) s.EnrollmentDate)
214: Case "date_desc"
215: students = students.OrderByDescending(Function(s) s.EnrollmentDate)
216: Case Else
217: students = students.OrderBy(Function(s) s.LastName)
218: End Select
219:
220: Dim pageSize As Integer = 3
221: Return View(Await PaginatedList(Of Student).CreateAsync(students.AsNoTracking(), If(page, 1), pageSize))
222: End Function
Опис мого власного, бяль универсального пейджера дивиться пліз на цей сторінці ASP.NET MVC Pager based on Generic and Constraint Interfaces.
Більш нічого цікавого я не побачив у цьому проєкті взагалі, за винятком того, що це взагалі не мій стиль програмування. Так пишуть тільки індуси. Наприклад, я обгортаю кожний метод контролерів у TRY та обробляю повідомлення про помилки. Я перевіряю кожний параметр на вході кожного важливого метода та видаю специфічні повідомлення про помилки, наприклад навіть ось такий найпростіший хандлер на 10 стрічок кода Proxy-handler for graphhopper.com, я обгортаю повідомленнями про помилки, індуси так не роблять взагалі.
Тому, хоча я спочатку мав бажання написати ще про дещо
- Lazy/Eager/Explicit loading data,
- Row state Added/Deleted/Updated/Modified/Detached,
- Set Many-to-Many in OnModelCreating,
- Reading and updating related data,
- EF Transaction: SaveChanges(false) + AcceptAllChanges(),
- Async/Await in EF,
- Raw SQL Queries DbSet.SqlQuery/Database.ExecuteSqlCommand,
- Optimistic/Pessimistic Concurrency,
- Inheritance Strategy in Code-First TPH/TPT/TPC,
- Doing deployment/migration from Package console/CMD utility/site code
на прикладі проєкту Contoso Univercity, з часом я зрозумів що копошитися у цих індуських помиях я не маю бажання. До того ж, цей сайт написан на NET CORE, а NET CORE має інші теги замість звичайних MVC-хелперів, а саме (asp-controller, asp-action, asp-route-{value}, asp-route, asp-all-route-data, asp-fragment, asp-area, asp-protocol, asp-host, asp-page, asp-page-handler, cache, distributed-cache, environment, Form, Input, Textarea, Label, Validation, Select, img, partial) - та ці теги потрібно конвертувати у звичайні хелпери ручками (ніякого сервісу індуси не передбачили), це додає складнощів до звичайної конвертації Шарпа у Бейсік, бо Щарп великі та маленькі літери розуміє як різні змінні, і коли конвертер перетворює код Шарпа на Бейсік (у якому це однакові змінні), навіть після бездоганно-працюючого конвертеру потрібно кожну стрічку кода зрозуміти за індусом та уважно перевірити, а це навіть складніше, ніж зробити самому той самий код з самого початку. Тому, на жаль, наступних частин з цим Contoso University вже не буде, можливо з іншими сайтами.
Entity Framework missing FAQ (Part 4). From EF Code First class definition to WebAPI2 on VB.NET
- 4.1. Ten step master to create DB from class defenition
- 4.1.1. Create MVC project
- 4.1.2. This operation only add reference to progect and change web.config
- 4.1.3. Add the Connection String
- 4.1.4. Create DbContext
- 4.1.5. Create ApplicationDbContext
- 4.1.6. Enable-Migrations
- 4.1.7. Add-Migration Initial
- 4.1.8. Update-Database -Verbose
- 4.1.9. Seed the Database
- 4.1.10. Show DB structure
- 4.2 - Set Up ASP.NET API Endpoints to open database on internet
- 4.2.1. Add WebAPI Controller
- 4.2.2. Add Json formater to WebApiConfig
- 4.2.3. Add callback to execute WebApiConfig at the beginning of the Application_Start
- 4.2.4. Check result
- 4.2.5. Is WebApi a best choice to flash data on internet?
|