Entity Framework Core-ის მიგრაციების production მონაცემთა ბაზაზე გასაშვებად სამი საიმედო გზა არსებობს: გამოიძახე Database.Migrate() აპლიკაციის გაშვებისას, გაუშვი migration bundle (efbundle, დამოუკიდებელი შესრულებადი ფაილი, რომელსაც dotnet ef migrations bundle აწარმოებს) ცალკე ნაბიჯად აპლიკაციის გაშვებამდე, ან დააგენერირე idempotent SQL სკრიპტი და თავად გაუშვი. ერთი აპლიკაციისთვის ერთ სერვერზე გაშვებისას მიგრაცია მისაღები და მარტივია; start ბრძანებიდან გაშვებული bundle უკეთესი ნაგულისხმევია, რადგან "მიგრაციას" "მომსახურებისგან" გამოყოფს დამატებითი ინფრასტრუქტურის გარეშე; გადახედილი SQL სკრიპტი სწორი პასუხია, როცა მონაცემთა ბაზას აპლიკაციის გარდა სხვა ვინმე ფლობს. რომელიც არ უნდა აირჩიო, თავად მიგრაციები უსაფრთხოდ უნდა ეშვებოდეს მომუშავე აპლიკაციის პირობებში, და ეს მათი წერის დისციპლინაა და არა ინსტრუმენტის პარამეტრი.
ეს პოსტი თითოეულ მეთოდს ზუსტი ბრძანებებით განიხილავს, შემდეგ კი იმ ნაწილებს, რომლებიც წყვეტს, კარგად ჩაივლის თუ არა deploy: რა დააგენერირა EF-მა და მისი გადახედვა, სქემის შეცვლა მომუშავე ვერსიის გატეხვის გარეშე და რას ნიშნავს "rollback" სინამდვილეში.
როგორ მუშაობს EF Core-ის მიგრაციები#
მიგრაცია არის C# კლასი Up მეთოდით, რომელიც სქემას ცვლის, და Down მეთოდით, რომელიც ცვლილებას აბრუნებს. ის გენერირდება შენი მიმდინარე მოდელის შედარებით მოდელის snapshot-თან, რომელიც წინა მიგრაციის დროს იყო.
$ dotnet tool install --global dotnet-ef$ dotnet ef migrations add AddOrderStatus$ dotnet ef migrations list$ dotnet ef database updateინსტრუმენტებს startup პროექტში Microsoft.EntityFrameworkCore.Design პაკეტი სჭირდება. dotnet-ef-ის დაყენება ასევე შეგიძლია ლოკალურ ინსტრუმენტად tool manifest-ში, რაც მის ვერსიას repository-სთან აფიქსირებს - გუნდისთვის უკეთესია, რადგან ინსტრუმენტის ვერსია პროექტში გამოყენებულ EF Core-ის ვერსიას უნდა ემთხვეოდეს.
migrations add Migrations საქაღალდეში სამ რამეს წერს: მიგრაციის კლასს, designer ფაილს მეტამონაცემებით და განახლებულ ModelSnapshot-ს. სამივე დააკომიტე. snapshot-ით იგებს შემდეგი მიგრაცია, რა შეიცვალა, ხოლო მასში merge კონფლიქტებით იგებენ ერთმანეთის შესახებ ორი დეველოპერი, რომლებიც ცალკე ბრენჩებზე მიგრაციებს ამატებენ.
მონაცემთა ბაზის მხარეს არის ცხრილი, __EFMigrationsHistory, რომელიც ყოველი გაშვებული მიგრაციის ID-სა და EF Core-ის იმ ვერსიას ინახავს, რომელმაც ის გაუშვა. მიგრაციების გაშვების ყოველი მეთოდი ამ ცხრილს კითხულობს, ადგენს, რომელი მიგრაციები აკლია, და მათ რიგით უშვებს. მდგომარეობას სხვა არაფერი აკონტროლებს, რის გამოც სქემაში ხელით შეტანილი ცვლილებები - აჩქარებით ხელით დამატებული ინდექსი - EF-ისთვის უხილავია და ხშირად ეჯახება მოგვიანებით მიგრაციას, რომელიც იმავეს დამატებას ცდილობს.
რა დააგენერირა EF-მა - გადახედვა#
EF მიგრაციებს diff-იდან აგენერირებს, ხოლო diff-მა შენი განზრახვა არ იცის. ყოველი მიგრაცია წაიკითხე, სანამ დააკომიტებ.
სახელის შეცვლა. property-ის სახელის შეცვლა შეიძლება დაგენერირდეს როგორც გადარქმევა, ან როგორც ძველი სვეტის წაშლა და ახლის დამატება, იმის მიხედვით, კიდევ რა შეიცვალა. წაშლა და დამატება სვეტის ყველა მნიშვნელობას კარგავს. როცა EF აგენერირებს ოპერაციას, რომელმაც შეიძლება მონაცემები დაკარგოს, ის ბეჭდავს "An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy." ეს ხაზი გაჩერების ნიშნად მიიღე და დაგენერირებული ოპერაციები ჩაანაცვლე migrationBuilder.RenameColumn(...)-ით, თუ გადარქმევა იყო ის, რაც გულისხმობდი.
ახალი არა-nullable სვეტები. სავალდებულო property-ის დამატება ცხრილში, სადაც სტრიქონებია, ქმნის სვეტს ტიპის ნაგულისხმევი მნიშვნელობით - ცარიელი სტრიქონი, ნული, 0001-01-01. ეს იშვიათად არის ის, რაც მონაცემებში უნდა ეწეროს. ან ჯერ სვეტი nullable გახადე და შეავსე (იხილე expand-contract სექცია ქვემოთ), ან მიგრაციაში მიეცი შეგნებული ნაგულისხმევი მნიშვნელობა HasDefaultValue-ით ან defaultValue:-ით.
მონაცემების ცვლილებები. მიგრაციებს სქემის შეცვლის გარდა SQL-ის გაშვებაც შეუძლია:
protected override void Up(MigrationBuilder migrationBuilder){ migrationBuilder.AddColumn<string>( name: "Status", table: "Orders", nullable: true); migrationBuilder.Sql( "UPDATE Orders SET Status = 'paid' WHERE PaidAt IS NOT NULL");}მონაცემების ცვლილებები პატარა და სიმრავლეებზე დაფუძნებული დატოვე. მიგრაცია, რომელიც entity-ებს C#-ში ტვირთავს და მათზე ციკლს ატარებს, ნელია, დამოკიდებულია მოდელის კლასებზე, რომლებიც მომდევნო მიგრაციებში შეიცვლება, და ისე ტყდება, რომ გასწორება ძნელია, როცა ის სადმე უკვე გაშვებულია.
ბრძანებები, რომლებიც ტრანზაქციაში ვერ ეშვება. ზოგიერთი ოპერაცია - CREATE INDEX CONCURRENTLY PostgreSQL-ზე, ზოგიერთი ALTER DATABASE ბრძანება SQL Server-ზე - ტრანზაქციის შიგნით გაშვებაზე უარს ამბობს. მათთვის migrationBuilder.Sql(...)-ს გადაეცი suppressTransaction: true და შეეგუე, რომ მიგრაცია აღარ არის "ყველაფერი ან არაფერი".
მიგრაციების გაშვების ოთხი გზა#
| მეთოდი | სად ეშვება | სჭირდება SDK სერვერზე | რისთვის კარგია |
|---|---|---|---|
dotnet ef database update | დეველოპერის მანქანა ან CI | კი | development მონაცემთა ბაზები |
Database.Migrate() გაშვებისას | აპლიკაციის შიგნით | არა | ერთი ინსტანცია, მარტივი კონფიგურაციები |
Migration bundle (efbundle) | აპლიკაციამდე, იმავე სერვერზე | არა | ნაგულისხმევი deploy-ების უმეტესობისთვის |
| Idempotent SQL სკრიპტი | სადაც DBA SQL-ს უშვებს | არა | გადახედილი, კონტროლირებადი ცვლილებები |
dotnet ef database update production-ზე ლეპტოპიდან ის მეთოდია, რომელსაც თავი უნდა აარიდო. მას production-ის connection string დეველოპერის მანქანაზე სჭირდება, ის იყენებს იმ კოდს, რაც შემთხვევით ლოკალურადაა checkout-ებული, და სხვამ არავინ იცის, რომ ეს მოხდა. გამოიყენე ის მხოლოდ development მონაცემთა ბაზებისთვის.
მიგრაციების გაშვება აპლიკაციის გაშვებისას#
უმარტივესი production მეთოდი ერთი ხაზია, სანამ აპლიკაცია მომსახურებას დაიწყებს:
var app = builder.Build();using (var scope = app.Services.CreateScope()){ var db = scope.ServiceProvider.GetRequiredService<ShopDb>(); db.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); await db.Database.MigrateAsync();}app.Run();რას აკეთებს სწორად: სქემა ყოველთვის ემთხვევა იმ კოდს, რომელიც ეშვება, და დასავიწყებელი ცალკე ნაბიჯი არ არსებობს. რას აკეთებს არასწორად:
- აპლიკაციის მონაცემთა ბაზის მომხმარებელს სქემის შეცვლის უფლებები სჭირდება. ჩვეულებრივ აპლიკაციამ მხოლოდ მონაცემები უნდა წაიკითხოს და ჩაწეროს. გაშვებისას მიგრაცია ნიშნავს, რომ ანგარიშს, რომელსაც შენი ვებ აპლიკაცია ყოველდღე იყენებს, ცხრილების წაშლაც შეუძლია.
- ჩავარდნილი მიგრაცია ჩავარდნილი გაშვებაა. აპლიკაცია არ ეშვება, პლატფორმა მას რესტარტავს და მიგრაცია ისევ ვარდება - restart ციკლი, რომლის რეალური მიზეზი stack trace-შია ჩამარხული.
- რამდენიმე ინსტანცია ეჯიბრება ერთმანეთს. ორმა ერთდროულად გაშვებულმა ინსტანციამ შეიძლება ორივემ სცადოს ერთი და იმავე მიგრაციის გაშვება. EF Core 9-მა ამისგან დასაცავად მიგრაციების გარშემო მთელი მონაცემთა ბაზის lock დაამატა, მაგრამ ძველ ვერსიებზე ეს რეალური საფრთხეა.
- გრძელი მიგრაციები timeout-ებს აწყდება. ბრძანების ნაგულისხმევი timeout 30 წამია; დიდ ცხრილზე ინდექსის დამატებას შეიძლება მეტი დრო დასჭირდეს, აქედან ზემოთ მოცემული
SetCommandTimeout.
EF Core 9-მა ასევე შეცვალა Migrate() ისე, რომ გამონაკლისს ისვრის, თუ მოდელს აქვს ცვლილებები, რომლებსაც არცერთი მიგრაცია არ ფარავს (PendingModelChangesWarning). ეს "მიგრაციის დამატება დამავიწყდა" შეცდომას გაშვებისას იჭერს და არა პირველი ჩავარდნილი query-ის დროს, რაც სასარგებლოა, მაგრამ აკვირვებს მათ, ვინც EF Core 8-იდან ახლდება.
ერთი აპლიკაციისთვის ერთ სერვერზე, მონაცემთა ბაზით, რომელსაც აპლიკაცია ფლობს, გაშვებისას მიგრაცია გონივრული არჩევანია. მიგრაციისთვის გამოიყენე ცალკე, პრივილეგირებული connection string, აპლიკაციისთვის კი ჩვეულებრივი, რომ ყოველდღიური ანგარიში შეზღუდული დარჩეს.
Migration bundle-ები#
bundle არის ერთი შესრულებადი ფაილი, რომელიც შენს მიგრაციებს და მათ გასაშვებად საჭირო ყველაფერს შეიცავს. იქ, სადაც ეშვება, მას არც SDK სჭირდება და არც შენი წყაროს კოდი:
$ dotnet ef migrations bundle --self-contained -r linux-x64 -o efbundle --force$ ./efbundle --connection "Host=203.0.113.10;Database=shop;Username=shop_owner;Password=..."--connection-ის გარეშე bundle იყენებს connection string-ს, რომლითაც შენი DbContext არის დაკონფიგურირებული, აპლიკაციის კონფიგურაციიდან წაკითხულს. --self-contained -r linux-x64 მას სერვერზე არსებულ .NET runtime-ისგან დამოუკიდებელს ხდის; --force წინა bundle-ს გადააწერს.
ააწყე bundle CI-ში აპლიკაციის გვერდით, გააგზავნე ორივე და start ბრძანებაში პირველად bundle გაუშვი:
./efbundle --connection "$MIGRATIONS_CONNECTION" && exec dotnet Shop.Web.dllთუ მიგრაცია ჩავარდა, && აპლიკაციას ნახევრად მიგრირებულ სქემაზე გაშვებას არ აძლევს, ხოლო ჩავარდნა ლოგში ბოლო ჩანაწერია და არა გაშვების ხმაურში ჩამარხული. მიგრაცია საკუთარი, სქემის უფლებების მქონე connection string-ით ეშვება, რომელიც გარემოს ცვლადიდან იკითხება, აპლიკაცია კი იყენებს ანგარიშს, რომელსაც მხოლოდ მონაცემების წაკითხვა და ჩაწერა შეუძლია. RE:NODE-ზე გარემოს ცვლადები Startup ჩანართზე იწერება, ხოლო start ბრძანება ყოველ გაშვებაზე ეშვება - ამიტომ bundle ყოველ restart-ზე ეშვება, ვერაფერს პოულობს გასაკეთებელს და დაახლოებით წამში სრულდება. ASP.NET Core აპლიკაციის deploy GitHub-იდან განიხილავს CI-ში publish-ს და შედეგის ბრენჩიდან deploy-ს.
Idempotent SQL სკრიპტები#
სკრიპტი მიგრაციებს უბრალო SQL-ად აქცევს, რომლის წაკითხვაც ნებისმიერს შეუძლია გაშვებამდე:
$ dotnet ef migrations script --idempotent -o migrate.sql$ dotnet ef migrations script AddOrders AddOrderStatus -o step.sqlპირველი ფორმა თავიდან ყველა მიგრაციას აწარმოებს, თითოეულს __EFMigrationsHistory-ზე შემოწმებაში გახვეულს, ამიტომ მისი გაშვება ნებისმიერი ვერსიის მონაცემთა ბაზაზე შეიძლება და ის მხოლოდ იმას უშვებს, რაც აკლია. მეორე ორ დასახელებულ მიგრაციას შორის SQL-ს აწარმოებს, რასაც ერთი გამოშვებისთვის გადამხედველს აწვდი.
სკრიპტები სწორი არჩევანია, როცა production მონაცემთა ბაზას მონაცემთა ბაზის ადმინისტრატორი ფლობს, როცა ცვლილებები ადამიანმა უნდა გადახედოს, ან როცა აპლიკაციის ანგარიშს სქემის უფლებები არასოდეს უნდა ჰქონდეს. ისინი ასევე პატიოსანი გზაა იმის სანახავად, რას გააკეთებს მიგრაცია: SQL-ის წაკითხვა ხშირად ავლენს, რომ "პატარა" ცვლილება ცხრილს თავიდან აშენებს. Idempotent სკრიპტებს ზოგიერთ provider-ზე შეზღუდვები აქვს - გარკვეული ბრძანებები სხვაგვარად იქცევა, როცა პირობით ბლოკშია გახვეული - ამიტომ სკრიპტი production-ის ასლზე გაუშვი, სანამ production-ზე გაუშვებ.
SQL Server-ზე შედეგს sqlcmd-ით ან SSMS-ით უშვებ; PostgreSQL-ზე psql -f-ით; MySQL-ზე mysql კლიენტით. თუ შენი მონაცემთა ბაზა ცალკე მონაცემთა ბაზის ჰოსტინგის გეგმაზეა, უკავშირდები პანელში მოცემული ჰოსტით, პორტით და მონაცემებით. თითოეული ძრავის კავშირის მხარე აღწერილია პოსტში .NET PostgreSQL-თან, MySQL-თან ან SQL Server-თან.
სქემის შეცვლა მომუშავე ვერსიის გატეხვის გარეშე#
deploy-ის დროს აპლიკაციის ძველი ვერსია ახალ სქემაზე წამით მუშაობს - ან, თუ მიგრაცია restart-მდე ეშვება, restart-ის მთელი ხანგრძლივობით. მიგრაცია, რომელსაც ძველი კოდი ვერ იტანს, საიტს ამ ფანჯრის განმავლობაში ტეხს. ნიმუში, რომელიც ამას თავიდან იცილებს, არის expand, შემდეგ contract:
- Expand. დაამატე ახალი სვეტი nullable-ად, ან ახალი ცხრილი. ძველი კოდი მას აიგნორებს. Deploy.
- მონაცემების მიგრაცია. ახალი კოდი წერს ძველ და ახალ სვეტებს ორივეს; მიგრაცია ან background job არსებულ სტრიქონებს ავსებს. Deploy.
- გადართვა. ახალი კოდი მხოლოდ ახალი სვეტიდან კითხულობს. Deploy.
- Contract. გახადე სვეტი არა-nullable, თუ ასე უნდა იყოს, და ძველი სვეტი მოგვიანებით მიგრაციაში წაშალე, როცა მას აღარაფერი კითხულობს. Deploy.
ოთხი deploy ერთი გადარქმევისთვის გადაჭარბებულად გეჩვენება, სანამ პირველად ერთნაბიჯიანი გადარქმევა საიტს restart-ის ხანგრძლივობით არ ჩამოაგდებს. პატარა აპლიკაციაზე მოკლე restart-ით და წყნარი საათებით მისი ორ ნაბიჯამდე შეკუმშვა (expand პლუს გადართვა, შემდეგ contract) გონივრული კომპრომისია. სვეტის წაშლა, რომელსაც მომუშავე კოდი ჯერ კიდევ ირჩევს, არასოდეს არის გონივრული. მიგრაციები downtime-ის გარეშე ამ ნიმუშს მეტი მაგალითით გადის, ხოლო zero-downtime deploy-ები პატარა სერვერზე აპლიკაციის მხარეს განიხილავს.
Rollback-ები და ჩავარდნილი მიგრაციები#
EF Core-ს Down მეთოდების გაშვება შეუძლია: dotnet ef database update AddOrders აბრუნებს ყველა მიგრაციას AddOrders-ის შემდეგ, ხოლო dotnet ef migrations script AddOrderStatus AddOrders ამ დაბრუნების SQL-ს აწარმოებს. ეს სქემის rollback-ია. ეს არ არის მონაცემების rollback: Down, რომელიც წაშლილ სვეტს ხელახლა ამატებს, მას ცარიელს ამატებს.
პრაქტიკული წესები:
- აიღე backup ყოველი მიგრაციის წინ, რომელიც რამეს შლის ან გადაწერს. dump, რომელიც ერთხელ მაინც აღადგინე, მონაცემებისთვის ერთადერთი რეალური rollback-ია. მონაცემთა ბაზის backup-ები და აღდგენა აღწერს, როგორ გააკეთო ეს სწორად; RE:NODE-ის მონაცემთა ბაზის ჰოსტინგის გეგმებში backup slot-ები შედის და backup-ის აღება deploy-მდე პანელიდან მოთხოვნისამებრ შეიძლება.
- უპირატესობა მიანიჭე წინ წასვლას. თუ მიგრაცია არასწორი იყო, ახალი მიგრაცია, რომელიც მას ასწორებს, ჩვეულებრივ დაბრუნებაზე უსაფრთხოა, რადგან ის ნებისმიერი სხვა ცვლილების მსგავსად ტესტირდება და არ არის დამოკიდებული
Downმეთოდზე, რომელიც არავის არასოდეს გაუშვია. - იცოდე, როგორ გამოიყურება ნაწილობრივი ჩავარდნა. SQL Server-სა და PostgreSQL-ზე ყოველი მიგრაცია ტრანზაქციაში ეშვება, ამიტომ ჩავარდნა ამ მიგრაციას გაუშვებელს ტოვებს. MySQL-ზე DDL ბრძანებები იმპლიციტურად აკომიტებს, ამიტომ მიგრაციამ, რომელიც შუა გზაზე ვარდება, შეიძლება თავისი ცვლილებების ნაწილი ადგილზე დატოვოს ისტორიის ჩანაწერის გარეშე. ამის გასწორება ნიშნავს სქემის წაკითხვას, ცვლილების ხელით დასრულებას ან გაუქმებას და მხოლოდ ამის შემდეგ ხელახლა ცდას.
- არასოდეს შეცვალო მიგრაცია, რომელიც სადმე საერთო გარემოში უკვე გაეშვა. შეცვალე ის production-ზე გაშვების შემდეგ და შემდეგი გარემო production-ისგან განსხვავებულ სქემას მიიღებს. ამის ნაცვლად ახალი მიგრაცია დაამატე.
dotnet ef migrations removeმხოლოდ იმ მიგრაციებისთვისაა, რომლებიც არ გაშვებულა.
FAQ#
გამოვიყენო EnsureCreated მიგრაციების ნაცვლად?
მხოლოდ ერთჯერადი მონაცემთა ბაზებისთვის ტესტებში ან პროტოტიპებში. EnsureCreated სქემას მიმდინარე მოდელიდან აგებს და ისტორიის ცხრილს არ ქმნის, ამიტომ ამ მონაცემთა ბაზაზე მიგრაციების გაშვება მოგვიანებით მისი თავიდან შექმნის გარეშე შეუძლებელია.
შემიძლია ბევრი ძველი მიგრაციის ერთში გაერთიანება?
კი, ფრთხილად: წაშალე ძველი მიგრაციის ფაილები, დაამატე ერთი ახალი მიგრაცია, რომელიც მიმდინარე სქემას ქმნის, და არსებულ მონაცემთა ბაზებში მისი ID ჩასვი __EFMigrationsHistory-ში, რომ ორჯერ არ გაეშვას. პროექტების უმეტესობას ეს არასოდეს სჭირდება; ასობით მიგრაცია ცოტა ჯდება.
რატომ ვარდება bundle შეცდომით "unable to create an object of type DbContext"?
ინსტრუმენტებმა design time-ში შენი context-ის აგება ვერ შეძლეს, ჩვეულებრივ იმიტომ, რომ ის დამოკიდებულია კონფიგურაციაზე, რომელიც მხოლოდ აპლიკაციის გაშვებისას არსებობს. დაამატე IDesignTimeDbContextFactory<T>, რომელიც context-ს გარემოდან აღებული connection string-ით აგებს.
მიგრაციები ერთნაირად მუშაობს PostgreSQL-ზე, MySQL-სა და SQL Server-ზე?
ბრძანებები იდენტურია; დაგენერირებული SQL და ტრანზაქციული ქცევა - არა. დააგენერირე სკრიპტი ერთხელ ყოველი provider-ისთვის, რომელსაც მიზნად ისახავ, და წაიკითხე, განსაკუთრებით MySQL-ისთვის, სადაც DDL-ის უკან დაბრუნება შეუძლებელია.
სად უნდა ინახებოდეს მიგრაციის connection string?
სერვერზე გარემოს ცვლადში, აპლიკაციის საკუთარი connection string-ისგან ცალკე, რომ სქემის უფლებების მქონე ანგარიში მხოლოდ მიგრაციისთვის გამოიყენებოდეს. ASP.NET Core-ის კონფიგურაცია და საიდუმლოებები აღწერს, როგორ იკითხება ეს ორი.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.