Podczas swej jakże krótkiej przygody z C# już na starcie spotkało mnie sporo problemów i niedogodności, jedną z nich był Access denied do mojej lokalnej bazy danych. Postanowiłem więc w kilku krokach przedstawić w jaki sposób problem, nie tyle rozwiązałem, co ominąłem. Przed wami więc krótka notatka z placu boju, a dotyczyć będzie jak wspominałem odpalenia lokalnej bazy danych (LocalDB) z pliku tak by działała z Entity Framework w konwencji Code First.
Dlaczego LocalDB
Na to pytanie odpowiedzi jest kilka, a najważniejszą z nich jest: nie udało mi się poskromić SQLite. Chciałem mieć lokalną bazę danych ponieważ eksport moich danych na zewnętrzne serwery nie wchodzi w grę. Z powodu wrażliwości danych nie mogę użyć MySQL ani innych baz danych hostowanych na moich kontach. Nie mam też dostępu do instancji serwera MsSQL, tak więc pozostaje LocalDB. No i LocalDB jest domyślnie instalowane wraz z VS2017.
Oczywiście i tutaj pojawiły się problemy i nie było tak kolorowo jak w tutorialach. Nie obyło się na siedzeniu w sieci, szukaniu rozwiązań po forach i męczenia Tayniaka na messengerze. A wszystko to przez Access Denied a konkretniej:
Error Number:5105,State:2,Class:16
A file activation error occurred. The physical file name '.\Database.mdf' may be incorrect. Diagnose and correct additional errors, and retry the operation.
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
i właśnie rozwiązaniu tego problemu poświęcę ten wpis.
Wydaje mi się, że błąd pojawił się u mnie w momencie usunięcia i reinstalacji serwera SQL od Microsoftu (dla ciekawskich dodam, że system to Windows 10 64bit). Stwierdziłem, że nie będę używał MsSQL i wywaliłem – przecież mam LocalDB z VS. Potem jednak zainstalowałem z powrotem, bo nie umiałem otworzyć plików mdf. Nie dam jednak głowy, że to jest przyczyna. Być może wcześniej po prostu nie wszystko sprawdziłem. Jest to jednak mało istotne. Najważniejsze jest odpalenie ustrojstwa tak by działało. Do dzieła więc.
Baza danych
Kluczowym elementem operacji jest utworzenie przez nas bazy danych za pomocą Visual Studio. Ja w inny sposób nie potrafiłem tego zrobić (kopiowanie, przenoszenie i tworzenie pliku DB za pomocą czegoś innego owocowało komunikatami błędu dostępu). Tak więc dodajemy do naszego projektu Bazę danych opartą o usługi. Nazwa bazy według uznania, lokalizacja też, choć dla ułatwienia sobie życia proponuję lokalizację domyślną (i tak później zmienimy lokalizację bazy).
DbContext
Kolejnym krokiem jest dodanie do projektu ADO.NET Entity Data Model. Tutaj tworzymy tak zwany kontekst. Jestem zupełnie początkujący w tych tematach, dlatego nie będę się rozpisywał na ten temat. Nie chcę udawać fachowca w dziedzinie i pleść bzdur o zastosowaniu tej klasy. Gdzieś wyczytałem, że DbContext umożliwia dostęp do objektów POCO. Ale po więcej wiedzy odsyłam was do googli. Mnie na tym etapie taka wiedza nie jest potrzebna ani przydatna. Nazwę przyjąłem DbCon. Ot tak, by mi się łatwiej zapamiętywało co to jest i po co mi to jest.
I cała moja magia
Tutaj jest mój „magiczny” krok, który sobie ubzdurałem, odkryłem, wymyśliłem i wykorzystałem. Nie mogłem za żadne skarby dobrać się do bazy jeśli dodawałem Empty Code First Model. Cały czas tylko Access denied. No ale przecież ja bazę danych już mam. VS utworzyło mi do niej połączenie i wpisało ją w konfigurację programiku. Dlaczego więc mam tworzyć pusty kontekst? Oj nie, trzeba coś zmienić. Tutaj właśnie wybrałem Code First from database i to okazało się strzałem w dziesiątkę. Dostałem pustą klasę DbCon i połączenie z bazą danych.
Uwaga
Połączenie z bazą danych ale nie tą w ./ tylko tą w ./bin/Debug !! Bazę danych z ./ usunąłem, w explorer serwer odłączyłem i zrobiłem nowe połączenie do bazy w lokalizacji ze skompilowanym projektem. O dziwo, jak później się przekonacie – To działa!
Teraz już z górki
Skoro połączenie z bazą mamy i w dodatku działa to reszta idzie jak z płatka. Ale zaraz zaraz, czy aby na pewno działa? By to sprawdzić wystarczy wydać z konsoli managera pakietów polecenie:
enable-migrations
W odpowiedzi powinnyśmy dostać folder Migrations z plikiem Configuration.cs. Plik ten służy do wprowadzania konfiguracji migracji oraz tego co się ma dziać po ich zakończeniu. Te operacje być może opiszę później, na chwilę obecną to co mamy w zupełności wystarczy.
Jak wspomniałem, połączenie z bazą działa, migracje są uruchomione.
Pierwszy model do bazy
By utworzyć model wystarczy postępować zgodnie z tutorialami na stronach. Nie będę tutaj wielkim odkrywcą i nie będę przytaczał ton przykładów z kilogramami teorii. W moim przypadku najprościej będzie utworzyć model produktu (ja robię to w folderze Models i nadaję klasie nazwy zakończone słowem Model) o nazwie ProductModel. Mój produkt jest bardzo prosty dlatego jego zawartość jest taka:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace brs.Models
{
public class ProductModel
{
[Key]
public int ProductID { get; set; }
public int ProductNumber { get; set; }
public string Name { get; set; }
public int Dim { get; set; }
public int Length { get; set; }
public string Comment { get; set; }
}
}
Teraz już tylko wystarczy dodać migrację
add-migration NazwaMigracji
oraz poinformować nasz kontekst o nowej zawartości dodając w pliki DbCon.cs nową linijkę z deklaracją zmiennej Products
public DbSet<Models.ProductModel> Products { get; set; }
na zakończenie wystarczy już tylko zrobić update-database i cieszyć się nową strukturą tabeli w bazie.
Ale ja dane też bym chciał
To też nie problem. Da się to zrobić i tutaj z pomocą przychodzi nam plik Configuration.cs. Możemy w nim nadpisać metodę Seed i w niej wymusić dopisanie danych do bazy zaraz po zakończeniu migracji.
namespace brs.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<brs.DbCon>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(brs.DbCon context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data.
context.Products.AddOrUpdate(
new Models.ProductModel() { ProductNumber = 153, Dim = 1, Length = 20, Name = "Demo 1" },
new Models.ProductModel() { ProductNumber = 1654, Dim = 1, Length = 60, Name = "Demo 2" },
new Models.ProductModel() { ProductNumber = 778, Dim = 2, Length = 20, Name = "Demo 3" },
new Models.ProductModel() { ProductNumber = 998, Dim = 2, Length = 40, Name = "Demo 4" }
);
}
}
}
Teraz już tylko wymusić ponowny update-database z dopiskiem -force i baza wypełniona.