Професионална програма
Loading...
ttitto avatar ttitto 1154 Точки

[Homework] Database Apps - EF Code First

Колеги, ето го домашното по Code First. Има малък проблем с първата задача по конзолния клиент. Не иска да вземе домашните на студентите и съответно не ги отпечатва. Не можах да си намеря сам грешката и се надявам на помощ от ваша страна!

Условието отново има малък пропуск и ако се спазва стриктно таблицата Materials остава висяща в базата. Затова добавих малко условие в Additional requirements: • Materials can belong to one or more courses

Направих валидация на някои дати, за да не може да се подават дати в бъдещето. Това стана чрез имплементиране на собствен атрибут.

Иначе има няколко неща, с които се преборих.
1. Connection стринговете към базата - трябваше да ги добавям ръчно към App.config файловете на всичките проекти. Затова ви обръщам внимание, когато проверявате чужди домашни, не бързайте да пишете, че домашното не работи, а проверете дали случайно нямате такава инстанция на SQL Server при вас и затова не иска да тръгне.
2. Много време ми отне да накарам Seed методът да се извиква въобще. Просто се наложи да създам клас наследник на DropCreateDatabaseIfModelChanges класа, в който да овъррайдна Seed метода
3. Доста време ми отне да мине първоначалния seed, защото имаше проблеми с подредбата на данните, констрейнтите по foreign keys и т.н.
4. При анотациите за валидиране се оказа, че подаването само не errorMessage не е достатъчно, защото като гръмне EF въобще не го изписва никъде. Затова си подавайте и ErrorMessageResourceName. На мен това ми изяде 3 часа, докато разбера коя валидация ми пищи.

Моля, не копирайте домашното дословно в системата и не го качвайте като собствено!

4
Databases Basics 09/03/2015 16:32:59
petrovaliev95 avatar petrovaliev95 358 Точки

Най-накрая тема и за това домашно.

Ето моите творения - ЦЪК.

Относно 'connection' стринговете и на мен ми беше много странно, че трябва да ги добавям във всеки проект който ползва контекста.
Имам един въпрос относно "Database.SetInitializer()" метода.
Къде е правилното място да го викаме този метод, аз лично в "Main" метода го извиквам.

 

Поздрави,

Даниел Петровалиев

1
borislavml avatar borislavml 368 Точки

И според мен е "по-правилно" да се вика в main-а на конзолния клиент. Ако го сетнеш в DbContext конструктора, други клиенти (web, desktop) ще са длъжни да ползват същия migration. Пак според нуждите smile

0
VGeorgiev avatar VGeorgiev 1385 Точки

В моделите няма нужда да добавяш connection string-a.

Database.SetInitializer() го слагаш там където ти е началната точка на Application-a. Примерно в ASP.NET MVC проект го слагаш в Global.asax в Application_Start. В конзолен проект в Main методa.

2
08/03/2015 22:56:09
petrovaliev95 avatar petrovaliev95 358 Точки

@VGeorgiev
Оо да това е copy-paste грешка. Понеже ми гърмеше и аз навсякаде копирах "App.config" файловете.

Колкото до това да сложим "Database.SetInitializer()" в конструктора на контекста - Това си е чист "StackOverflow Exception".

0
08/03/2015 22:52:16
ttitto avatar ttitto 1154 Точки

Доколкото и аз търсих за мястото на SetInitializer, разбрах че няма правилно място. Важно е просто да се извика веднъж в началото от някъде.

За stackOverflowException-a се оказа, че изскача само при определени миграционни стратегии, не при всички. Затова може би Владо казва, че трябва да се пробва там.

Домашното ти ме подсети, че не съм си направил незадължителните пропъртита Nullable.

 

0
08/03/2015 22:55:53
petrovaliev95 avatar petrovaliev95 358 Точки

Ааа да и като стана дума за ""Nullable".

Кой е правилният начин :

           1. public DateTime? BirthDay { get; set; }

ИЛИ

           2. public Nullable<DateTime> BirthDay { get; set; }

0
ttitto avatar ttitto 1154 Точки

Само синтактично се различават. Компилират се до едно и също според Stack Overflow.

0
Tr00peR avatar Tr00peR 569 Точки

Еднакви са, но DateTime? е по-КПК.

Колкото за Database.SetInitializer() конкретно за MigrateDbToLatestVersion, някой може ли да обясни защо да не е добре да се вика в конструктура?

0
vladislav.karamfilov avatar vladislav.karamfilov 1123 Точки

@Tr00peR, защото нямаш гъвкавост, ако искаш няколко приложения да рънват със същия контекст, но с различни миграции. Като цяло по-правилното място на този Database.SetInitializer е в конструктора, защото чисто логически там е логиката, която е свързана с базата. Причината да се слага в стартиращия метод на приложението е посочената по-горе. :)

2
vladislav.karamfilov avatar vladislav.karamfilov 1123 Точки

@petrovaliev95, абсолютно едно и също нещо са DateTime? и Nullable<DateTime> за компилатора. Използва се DateTime?, защото се пише доста по-бързо. :)

3
vladislav.karamfilov avatar vladislav.karamfilov 1123 Точки

@petrovaliev95, connection string-ът трябва да го имаш само в приложението, в което ти е главния стартиращ се метод Main или Application_Start. Там се съхраняват всички настройки, от които има нужда приложението, докато работи, дори и част от тях да се ползват в други dll-ки, които си писал в него. ;)

3
09/03/2015 09:48:36
ZvetanIG avatar ZvetanIG 917 Точки

Относно мястото на Database.SetInitializer() открих, че трябва да ти е преди инстанцирането на DBContext, защото ако нямаш таблица при създаването на таблицата не ти сработва seed метода.  След повторно ръннване и налична база се изпълнява без проблем.

2
09/03/2015 22:51:11
nickpanaiotov avatar nickpanaiotov 21 Точки

Няма ли начин да се провери дали е променяна схемата и ако не е да не се вика seed метода? 

0
AndreiBozhilov avatar AndreiBozhilov 1 Точки

Или попаднах на твоето домашно или на някой, който е копирал едно към едно от теб, надявам се да е първото. Реших, че ще полезно да напиша коментар за домашното, като се надявам и този, който е копирал, ако има такъв да го прочете.

  • connection string трябва да е извън startup тагът. Като пусна проекта ми дава грешка като е вътре, не знам ти как си го подкарал.
  • Seet метода ти се вика всеки път, когато се стартира приложението, нямаш логика, която да му казва да не го прави. Най-лесното нещо, което можеш да направиш е в началото на seed метода да провериш дали имаш записи в таблица, който по-късно ще добавяш.
  • Като пуснеш приложението два пъти гърми с InnerException {"There is already an open DataReader associated with this Command which must be closed first."}, това според StackOverflow става, когато извикваш заявка, докато итерираш върху друга заявка или „This can happen if you execute a query while iterating over the results from another query. It is not clear from your example where this happens because the example is not complete. One thing that can cause this is lazy loading triggered when iterating over the results of some query.“

http://stackoverflow.com/questions/6062192/there-is-already-an-open-datareader-associated-with-this-command-which-must-be-c

Нещото, което можеш да направиш е да извикаш .ToList() като теглиш студентите. Като това има малък проблем, който води до следващата точка.

  • Това вече работи без грешка, но да видиш какво се случва в Profiler-a. Постигаш проблема с n+1 заявки, който Наков обясни в следващата лекция – „Производителност в Entity Framework“       
     var students = studentSystemEntities.Students.ToList();
 
     foreach (var student in students)
     {
         Console.WriteLine("Student: " + student.FullName);
         Console.Write(" Homeworks:");
 
         var currStudentHomeworks = student.Homeworks.ToList();          foreach (var homework in currStudentHomeworks)          {              Console.Write(" " 
+ homework.Content
 + ", Content Type: "
 + homework.ContentType.ToString()
 + ", Deadline: "
 + homework.DeadLine.ToShortDateString());              Console.WriteLine();         }           Console.WriteLine();     }

 

  • И всъщност трябва да бъде така, като по този начин ти прави една тлъста заявка.

Snippet

         var students = studentSystemEntities.Students
.Include(x => x.Homeworks)
.ToList();
        foreach (var student in students)          {              Console.WriteLine("Student: " + student.FullName);              Console.Write(" Homeworks:");              //var currStudentHomeworks = student.Homeworks.ToList();              foreach (var homework in student.Homeworks)              {                  Console.Write(" "
 + homework.Content
 + ", Content Type: " 
+ homework.ContentType.ToString()
 + ", Deadline: "
 + homework.DeadLine.ToShortDateString());                  Console.WriteLine();              }              Console.WriteLine();         }

 

Като за да имаш Include() трябва да се добави System.Data.Entity, което го имаш само го отбелязвам.

  • Като инстантираш ICollection в конструктора се препоръчва ползването на HashSet, защото е най-бързо.
  • Имаш using-ги извън namespace
  • Имаш ненужни using-ги.

Хареса ми, че ползвал EF Fluent Api за да си направиш типа на полето за датата.

Браво за старанието. И продължавай напред.

1
12/03/2015 23:29:25
petrovaliev95 avatar petrovaliev95 358 Точки

Благодаря ти за feedbacka. Значи първо при мен не гърми нищо, всичко си върви по вода.
Connection стринга - имах много главоболия с него копирах някакви неща трих и накрая така е останало, иначе да не е правилно. SeeD метода целта ми беше да го имплементирам в решението си, а не да имам такава логика, че да проверявам дали вече има такива записи. Метода Include() след лекцията по EF performance вече и аз го знам и знам каква е причината да го ползваме. С датите също имах много проблеми (EF попринцип не харесва датите). 

За жалост домашното което си оценил не е моето!
Още един път благодаря за изчерпателния feedback.

Поздрави,

Даниел Петровалиев

1
12/03/2015 23:36:07
ttitto avatar ttitto 1154 Точки

С такъв коментар бих искал и на моето домашно да попаднеш. Независимо дали аз съм го предал или някой друг от мое име :) Евала за коментара!

0