Tożsamość ASP.NET z EF Database First MVC5

88

Czy można używać nowej tożsamości Asp.net z usługami Database First i EDMX? Czy tylko z kodem na początku?

Oto co zrobiłem:

1) Zrobiłem nowy projekt MVC5 i poleciłem, aby nowa tożsamość utworzyła nowe tabele użytkowników i ról w mojej bazie danych.

2) Następnie otworzyłem mój plik Database First EDMX i przeciągnąłem do nowej tabeli Identity Users, ponieważ mam inne tabele, które się z nią wiążą.

3) Po zapisaniu EDMX, generator Database First POCO automatycznie utworzy klasę użytkownika. Jednak UserManager i RoleManager oczekują klasy User dziedziczącej z nowej przestrzeni nazw tożsamości (Microsoft.AspNet.Identity.IUser), więc użycie klasy POCO User nie będzie działać.

Wydaje mi się, że możliwym rozwiązaniem jest edycja moich klas generacji POCO, aby moja klasa użytkownika dziedziczyła po IUser?

A może ASP.NET Identity jest zgodne tylko z Code First Design?

+++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++

Aktualizacja: Postępując zgodnie z sugestią Andersa Abla poniżej, zrobiłem to. To działa, ale zastanawiam się, czy istnieje bardziej eleganckie rozwiązanie.

1) Rozszerzyłem klasę użytkownika jednostki, tworząc klasę częściową w tej samej przestrzeni nazw, co moje automatycznie generowane jednostki.

namespace MVC5.DBFirst.Entity
{
    public partial class AspNetUser : IdentityUser
    {
    }
}

2) Zmieniłem mój DataContext na dziedziczenie z IdentityDBContext zamiast DBContext. Zauważ, że za każdym razem, gdy aktualizujesz EDMX i ponownie generujesz klasy DBContext i Entity, będziesz musiał ustawić to z powrotem na to.

 public partial class MVC5Test_DBEntities : IdentityDbContext<AspNetUser>  //DbContext

3) W ramach automatycznie generowanej klasy encji użytkownika musisz dodać słowo kluczowe override do następujących 4 pól lub skomentować te pola, ponieważ są one dziedziczone po IdentityUser (krok 1). Zauważ, że za każdym razem, gdy aktualizujesz EDMX i ponownie generujesz klasy DBContext i Entity, będziesz musiał ustawić to z powrotem na to.

    override public string Id { get; set; }
    override public string UserName { get; set; }
    override public string PasswordHash { get; set; }
    override public string SecurityStamp { get; set; }
Patrick Tran
źródło
1
czy masz przykładowy kod swojej implementacji? gdy próbuję odtworzyć powyższe, pojawia się błąd, gdy próbuję zalogować się lub zarejestrować użytkownika. „Typ jednostki AspNetUser nie jest częścią modelu dla bieżącego kontekstu”, gdzie AspNetUser jest moją jednostką użytkownika
Tim
Czy dodałeś tabelę AspNetUser do swojego EDMX? Ponadto upewnij się, że AccountController używa MVC5Test_DBEntities (lub jakiejkolwiek nazwy kontekstu bazy danych), a nie ApplicationContext.
Patrick Tran
8
ASP.NET Identity to parujący stos ____. Straszne wsparcie dla bazy danych, brak dokumentacji, słabe ograniczenia referencyjne (brak ON CASCADE DELETE na serwerze SQL) i używa ciągów dla identyfikatorów (problem z wydajnością i fragmentacją indeksu). I to jest ich 297. próba
stworzenia
1
@ DeepSpace101 Identity obsługuje DB-first tak samo jak Code-first. Szablony są skonfigurowane tak, aby najpierw wykonać kod, więc jeśli zaczynasz od szablonu, musisz zmienić pewne rzeczy. Usuwanie kaskadowe działa dobrze, możesz łatwo zmienić ciągi znaków na int. Zobacz moją odpowiedź poniżej.
But
1
@Shoe Muszę powiedzieć, że myślę, że możesz się mylić. Nie znalazłem jeszcze działającego, obszernego przykładu / samouczka, jak zaimplementować to w db-first (brak dokumentacji). Interfejs API próbuje odwołać się do tabeli skrzyżowań „IdentityUserRoles” za pośrednictwem właściwości IdentityUser.Roles, co powoduje zerwanie relacji w EF db-first, ponieważ tabele skrzyżowań nie są ujawniane jako jednostki (słabe ograniczenia referencyjne-ish). Nie zgadzam się z ciągami znaków dla identyfikatorów, ponieważ można je dostosować, określając parametry typu w odziedziczonych klasach. Podsumowując, wydaje mi się, że w ogóle nie myśleli o DB.
Krzywo

Odpowiedzi:

16

Powinno być możliwe użycie systemu tożsamości z POCO i Database First, ale będziesz musiał wprowadzić kilka poprawek:

  1. Zaktualizuj plik .tt do generowania POCO, aby utworzyć klasy jednostek partial. Umożliwi to dostarczenie dodatkowej implementacji w osobnym pliku.
  2. Wykonaj częściową implementację Userklasy w innym pliku

 

partial User : IUser
{
}

To sprawi, że Userklasa zaimplementuje właściwy interfejs, bez dotykania faktycznie wygenerowanych plików (edycja wygenerowanych plików jest zawsze złym pomysłem).

Anders Abel
źródło
Dzięki. Będę musiał spróbować i zgłosić, jeśli to się uda.
Patrick Tran,
Wypróbowałem to, o czym wspomniałeś. Zobacz mój post po zaktualizowane informacje ... ale rozwiązanie nie było zbyt eleganckie: /
Patrick Tran
Miałem ten sam problem. Wydaje się, że dokumentacja dotycząca DB-first jest bardzo rzadka. To fajna sugestia, ale myślę, że masz rację, to nie do końca działa
Phil
4
Czy jest jeszcze jakieś rozwiązanie, które nie jest włamaniem?
user20358
Używam Onion Architecture i wszystkie POCO są w Core. Tam nie zaleca się używania IUser. Więc jakieś inne rozwiązanie?
Usman Khalid
13

Moje kroki są bardzo podobne, ale chciałem się nimi podzielić.

1) Utwórz nowy projekt MVC5

2) Utwórz nowy plik Model.edmx. Nawet jeśli jest to nowa baza danych i nie ma tabel.

3) Edytuj plik web.config i zastąp ten wygenerowany ciąg połączenia:

<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-SSFInventory-20140521115734.mdf;Initial Catalog=aspnet-SSFInventory-20140521115734;Integrated Security=True" providerName="System.Data.SqlClient" />

z tym ciągiem połączenia:

<add name="DefaultConnection" connectionString="Data Source=.\SQLExpress;database=SSFInventory;integrated security=true;" providerName="System.Data.SqlClient" />

Następnie skompiluj i uruchom aplikację. Zarejestruj użytkownika, a następnie zostaną utworzone tabele.

JoshYates1980
źródło
1
rozwiązało to problem, nie widziałem użytkownika aplikacji w bazie danych, ale po wymianie domyślnego połączenia zadziałało.
Hassen Ch.
10

EDYCJA: ASP.NET Identity z EF Database First dla szablonu projektu MVC5 CodePlex.


Chciałem użyć istniejącej bazy danych i stworzyć relacje z ApplicationUser. W ten sposób zrobiłem to przy użyciu SQL Server, ale ten sam pomysł prawdopodobnie zadziałałby z każdą bazą danych.

  1. Utwórz projekt MVC
  2. Otwórz bazę danych wymienioną w DefaultConnection w pliku Web.config. Zostanie nazwany (aspnet- [znacznik czasu] lub coś w tym rodzaju).
  3. Skrypt tabel bazy danych.
  4. Wstaw tabele skryptowe do istniejącej bazy danych w SQL Server Management Studio.
  5. Dostosuj i dodaj relacje do ApplicationUser (w razie potrzeby).
  6. Utwórz nowy projekt sieci Web> MVC> Pierwszy projekt DB> Importuj bazę danych za pomocą EF ... Z wyłączeniem wstawionych klas tożsamości.
  7. W IdentityModels.cs zmień ApplicationDbContext, :base("DefaltConnection")aby używał DbContext projektu.

Edycja: Diagram klas tożsamości Asp.Net wprowadź opis obrazu tutaj

smród
źródło
6
Problem nie dotyczy DBContext, ale ten UserManager i RoleManager oczekują klasy, która dziedziczy po Microsoft.AspNet.Identity.EntityFramework.IdentityUser
Patrick Tran
public class IdentityDbContext <TUser>: DbContext gdzie TUser: Microsoft.AspNet.Identity.EntityFramework.IdentityUser. W przypadku pierwszego korzystania z bazy danych wygenerowane klasy jednostek nie dziedziczą z żadnych klas bazowych.
Patrick Tran,
Następnie po prostu wyklucz je z bazy danych podczas generowania klas za pomocą struktury jednostki.
smród
Jeśli wykluczysz tabele tożsamości z EDMX, utracisz właściwości nawigacji w innych klasach, które mają klucze obce do Twojego identyfikatora użytkownika
Patrick Tran
34
Nie jestem niechętny, aby najpierw przejść do kodu ... Po prostu w niektórych scenariuszach i firmach administrator bazy danych tworzy tabele podstawowe, a nie koder.
Patrick Tran,
8

IdentityUser jest tutaj bezwartościowy, ponieważ jest to pierwszy obiekt kodu używany przez UserStore do uwierzytelniania. Po zdefiniowaniu własnego Userobiektu zaimplementowałem częściową klasę, która implementuje IUserużywaną przez UserManagerklasę. Chciałem, aby moje Ids były intzamiast ciągu, więc po prostu zwracam toString () z UserID. Podobnie chciałem nsię Usernamebyć uncapitalized.

public partial class User : IUser
{

    public string Id
    {
        get { return this.UserID.ToString(); }
    }

    public string UserName
    {
        get
        {
            return this.Username;
        }
        set
        {
            this.Username = value;
        }
    }
}

W żadnym wypadku nie potrzebujesz IUser. To tylko interfejs używany przez UserManager. Więc jeśli chcesz zdefiniować inny "IUser", musisz przepisać tę klasę, aby używała własnej implementacji.

public class UserManager<TUser> : IDisposable where TUser: IUser

Teraz napisać własną rękę UserStore, która obsługuje wszystkie przechowywania użytkowników, roszczeń, ról itp Wdrożenie interfejsów wszystkiego, że kod, najpierw UserStorerobi i zmiana where TUser : IdentityUserna where TUser : Usermiarę „Użytkownik” to Twój obiekt podmiot

public class MyUserStore<TUser> : IUserLoginStore<TUser>, IUserClaimStore<TUser>, IUserRoleStore<TUser>, IUserPasswordStore<TUser>, IUserSecurityStampStore<TUser>, IUserStore<TUser>, IDisposable where TUser : User
{
    private readonly MyAppEntities _context;
    public MyUserStore(MyAppEntities dbContext)
    { 
        _context = dbContext; 
    }

    //Interface definitions
}

Oto kilka przykładów niektórych implementacji interfejsu

async Task IUserStore<TUser>.CreateAsync(TUser user)
{
    user.CreatedDate = DateTime.Now;
    _context.Users.Add(user);
    await _context.SaveChangesAsync();
}

async Task IUserStore<TUser>.DeleteAsync(TUser user)
{
    _context.Users.Remove(user);
    await _context.SaveChangesAsync();
}

Korzystając z szablonu MVC 5, zmieniłem AccountControllerwygląd tak.

public AccountController()
        : this(new UserManager<User>(new MyUserStore<User>(new MyAppEntities())))
{
}

Teraz logowanie powinno działać z własnymi tabelami.

But
źródło
1
Niedawno miałem okazję to zaimplementować iz jakiegoś powodu (wydaje mi się, że z powodu aktualizacji tożsamości 3.0) nie byłem w stanie zaimplementować logowania poprzez dziedziczenie IdentityUser, a następnie nadpisanie właściwości; ale pisanie własnego magazynu UserStore i dziedziczenie IUser działało dobrze; Podaję tylko aktualizację, może ktoś uzna to za przydatne.
Naz Ekin
Czy możesz podać link do wszystkich wdrożeń interfejsu lub do pełnego projektu?
DespeiL
czy jest jakiś konkretny powód, dla którego użyłeś tutaj klasy częściowej?
user8964654
Jeśli używasz edmx do generowania modeli, musisz użyć klasy częściowej. Jeśli najpierw tworzysz kod, możesz to pominąć
But
3

Dobre pytanie.

Jestem raczej pierwszą osobą w bazach danych. Pierwszy paradygmat kodu wydaje mi się luźny, a „migracje” wydają się zbyt podatne na błędy.

Chciałem dostosować schemat tożsamości aspnet i nie przejmować się migracjami. Jestem dobrze zorientowany w projektach baz danych Visual Studio (sqlpackage, data-dude) i jak radzi sobie całkiem nieźle z uaktualnianiem schematów.

Moje uproszczone rozwiązanie to:

1) Utwórz projekt bazy danych, który odzwierciedla schemat tożsamości aspnet 2) użyj danych wyjściowych tego projektu (.dacpac) jako zasobu projektu 3) Wdróż .dacpac w razie potrzeby

W przypadku MVC5 modyfikacja ApplicationDbContext klasy wydaje się działać ...

1) Wdrożenie IDatabaseInitializer

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDatabaseInitializer<ApplicationDbContext> { ... }

2) W konstruktorze zasygnalizuj, że ta klasa zaimplementuje inicjalizację bazy danych:

Database.SetInitializer<ApplicationDbContext>(this);

3) Wdrożenie InitializeDatabase :

Tutaj zdecydowałem się użyć DacFX i wdrożyć mój .dacpac

    void IDatabaseInitializer<ApplicationDbContext>.InitializeDatabase(ApplicationDbContext context)
    {
        using (var ms = new MemoryStream(Resources.Binaries.MainSchema))
        {
            using (var package = DacPackage.Load(ms, DacSchemaModelStorageType.Memory))
            {
                DacServices services = new DacServices(Database.Connection.ConnectionString);
                var options = new DacDeployOptions
                {
                    VerifyDeployment = true,
                    BackupDatabaseBeforeChanges = true,
                    BlockOnPossibleDataLoss = false,
                    CreateNewDatabase = false,
                    DropIndexesNotInSource = true,
                    IgnoreComments = true,

                };
                services.Deploy(package, Database.Connection.Database, true, options);
            }
        }
    }
Aaron Hudon
źródło
2

Pracując nad tym spędziłem kilka godzin iw końcu znalazłem rozwiązanie, które podzieliłem się tutaj na moim blogu . Zasadniczo musisz zrobić wszystko, co zostało powiedziane w odpowiedzi smrodu , ale z jedną dodatkową rzeczą: upewnij się, że Identity Framework ma określone parametry połączenia SQL-Client na szczycie parametrów połączenia Entity Framework używanych dla jednostek aplikacji.

Podsumowując, aplikacja będzie używać parametrów połączenia dla platformy Identity Framework i innych dla jednostek aplikacji. Każdy ciąg połączenia jest innego typu. Przeczytaj mój wpis na blogu, aby zapoznać się z pełnym samouczkiem.

Daniel Eagle
źródło
Dwukrotnie wypróbowałem wszystko, o czym wspomniałeś na swoim blogu, ale mi się to nie udało.
Badhon Jain
@Badhon Bardzo mi przykro, że instrukcje z mojego wpisu na blogu nie działają. Wiele osób wyraziło wdzięczność, ponieważ odnieśli sukces po przeczytaniu mojego artykułu. Zawsze pamiętaj, że jeśli Microsoft coś zaktualizuje, może to wpłynąć na wynik. Napisałem artykuł dla ASP.NET MVC 5 z Identity Framework 2.0. Wszystko poza tym może powodować problemy, ale jak dotąd otrzymałem bardzo niedawne komentarze wskazujące na sukces. Chciałbym dowiedzieć się więcej o Twoim problemie.
Daniel Eagle
Spróbuję jeszcze raz śledzić, problem, z jakim się spotkałem, polegał na tym, że nie mogłem użyć mojej niestandardowej bazy danych, korzystała ona z bazy danych zbudowanej automatycznie. W każdym razie nie chciałem powiedzieć czegoś złego w twoim artykule. Dzięki za podzielenie się swoją wiedzą.
Badhon Jain
1
@Badhon Nie martw się, przyjacielu, nigdy nie czułem, że powiedziałeś, że coś jest nie tak z artykułem. Wszyscy mamy wyjątkowe sytuacje z różnymi skrajnymi przypadkami, więc czasami to, co działa dla innych, niekoniecznie musi działać dla nas. Mam nadzieję, że udało Ci się rozwiązać swój problem.
Daniel Eagle
2

Odkryłem, że @ JoshYates1980 ma najprostszą odpowiedź.

Po serii prób i błędów zrobiłem to, co zasugerował Josh i zastąpiłem connectionStringwygenerowanym ciągiem połączenia z bazą danych. pierwotnie byłem zdezorientowany następującym postem:

Jak dodać uwierzytelnianie tożsamości ASP.NET MVC5 do istniejącej bazy danych

Gdzie zaakceptowana odpowiedź od @Win wskazywała na zmianę ApplicationDbContext()nazwy połączenia. Jest to trochę niejasne, jeśli używasz Entity i pierwszego podejścia Database / Model, w którym parametry połączenia z bazą danych są generowane i dodawane do Web.configpliku.

Nazwa ApplicationDbContext()połączenia jest mapowana na domyślne połączenie w Web.configpliku. Dlatego metoda Josha działa najlepiej, ale aby uczynić ją ApplicationDbContext()bardziej czytelną, sugerowałbym zmianę nazwy na nazwę bazy danych zgodnie z pierwotnym opublikowaniem @Win, upewniając się, że zmienisz opcję connectionString„DefaultConnection” w Web.configi skomentuj i / lub usuń jednostkę wygenerowana baza danych zawiera.

Przykłady kodu:

TheSchnitz
źródło
1

Mamy projekt DLL modelu encji, w którym przechowujemy naszą klasę modelu. Prowadzimy również projekt bazy danych ze wszystkimi skryptami bazy danych. Moje podejście było następujące

1) Stwórz swój własny projekt, w którym EDMX używa najpierw bazy danych

2) Skryptuj tabele w swojej bazie danych, użyłem VS2013 połączonego z localDB (Połączenia danych) i skopiowałem skrypt do projektu bazy danych, dodaj dowolne niestandardowe kolumny, np. Data urodzenia [DATA] nie null

3) Wdróż bazę danych

4) Zaktualizuj projekt modelu (EDMX) Dodaj do projektu modelu

5) Dodaj dowolne kolumny niestandardowe do klasy aplikacji

public class ApplicationUser : IdentityUser
{
    public DateTime BirthDate { get; set; }
}

W projekcie MVC AccountController dodano następujące elementy:

Dostawca tożsamości chce, aby parametry połączenia SQL działały, aby zachować tylko 1 parametry połączenia dla bazy danych, wyodrębnij ciąg dostawcy z parametrów połączenia EF

public AccountController()
{
   var connection = ConfigurationManager.ConnectionStrings["Entities"];
   var entityConnectionString = new EntityConnectionStringBuilder(connection.ConnectionString);
        UserManager =
            new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(
                    new ApplicationDbContext(entityConnectionString.ProviderConnectionString)));
}
Haroon
źródło