31

sto imparando Repository e Unità di modelli di lavoro in ASP.NET MVC 5 applicazione con Entity Framework 6.ASP.NET Identità con Repository e unità di lavoro

avevo già letto un sacco di tutorial e articoli, ma quasi tutti sono contraddittori. Alcuni dicono che i modelli di Repository e Unit of Work sono buoni, altri dicono che DbContext è già un repository e un'unità di lavoro, altri dicono qualcosa di simile, ma offrono un approccio completamente diverso. Ho provato tutti questi approcci diversi (beh, forse non tutti) e sto ancora cercando di capire qual è l'approccio più corretto.

Quello che ho attualmente è:

  • IRepository e GenericRepository attuazione IRepository
  • IUnitOfWork e UnitOfWork attuazione IUnitOfWork
  • IDbContext e MyDbContext ereditate da IdentityDbContext e l'attuazione di IDbContext

Non sono sicuro se Ho bisogno di incollare il codice per questo, penso che sia abbastanza generico e il problema in realtà non è con repository/Uni tOfWork in quanto tale. Il problema che ho è con l'utilizzo di classi di identità ASP.NET in combinazione con i miei archivi e unità di lavoro. Sto condividendo lo stesso database per l'appartenenza e per tutti gli altri dati - e penso che sia uno scenario comune. Non riesco a trovare la buona soluzione come posso creare istanze di classi di identità ASP.NET usando i miei repository.

UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(_DBCONTEXT_); 
this.UserManager = new UserManager<ApplicationUser>(store); 

Cosa devo mettere al posto di DbContext, in modo che sarebbe condividere lo stesso con il mio DbContext UnitOfWork? O come può essere fatto in un altro modo per far sì che ASP.NET Identity funzioni con UnitOfWork?

ho cercato di esporre DbContext come proprietà pubblica della classe UnitOfWork, qualcosa di simile a:

UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(this.unitOfWork.MyDbContext); 

Tuttavia non credo che sia giusto - non funziona con l'interfaccia IDbContext personalizzati, e rende il codice non va bene per il test dell'unità.

Ho anche provato a implementare CustomUserStore e CustomRoleStore - in generale ha funzionato, ma mentre lo stavo testando, era necessario implementare sempre più metodi. Questa soluzione sembra troppo complicata - spero davvero che ci sia un modo più semplice.

+2

' DbContext' è _a_ repository - se si sceglie di usarlo come repository _the_ allora siete tightly-. accoppiato a EF e prevenire qualsiasi tipo di unità di test su classi che richiedono un repository. –

+0

Penso che questo articolo sia molto chiaro http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using -mvc-4/implementazione-the-repository-and-unit-of-work-patterns-in-un-asp-net-mvc-application –

+0

Questo è stato il primo articolo che stavo attraversando. È stato davvero utile e ben scritto , ma .. non c'era nulla di menzionato riguardo il mio problema principale su come farlo funzionare con l'identità di ASP.NET. Alla fine ho dovuto passare un'istanza separata di DbContext per UserManager, che è in conflitto con il concetto di UnitOfWork. – Aleksanderis

risposta

2

Se si utilizza Repository ed il modello UnitofWork può essere lo si utilizza con DDD (Domain Driven Design) in cui si dichiara IRepository o IUnitofWork nel progetto Nucleo insieme a tutti gli altri modelli di dominio e classi astratte.

Ora si crea il progetto Infrastruttura che implementa tali interfacce nel progetto Core utilizzando l'oggetto di accesso ai dati concreto per questa istanza Entity Framework. quindi DbContext va bene, ma sì non lo esporre al livello di presentazione. Quindi, ad un certo punto se si desidera modificare EF in qualsiasi altro ORM, sarà più semplice senza toccare il livello di presentazione in cui si mettono le classi Identity separate dal progetto Data Access o Infrastructure. E ovviamente puoi usare il contenitore IOC per istanziare quei Repository concreti da Infrastructure in Controller di livello Presentation.

9

Ho trovato che lavorare con ASP.Net Identity 2.0 ed EF6 è un po 'impegnativo. Il più grande svantaggio è la mancanza di documentazione o documentazione conflittuale.

Utilizzo WebApi 2.0, EF6 e ASP.Net Identity 2.0. All'inizio è stato difficile andare avanti, ma una volta che funziona, è stato buono.

Ho creato le mie proprie classi di identità. Al momento non mi interessa estendere le classi di identità. Voglio solo generare le tabelle e accedere al sistema.

CustomRole

public class CustomRole : IdentityRole<int, CustomUserRole> 
{ 
    /// <summary> 
    /// Initializes a new instance of the <see cref="CustomRole"/> class. 
    /// </summary> 
    public CustomRole() { } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="CustomRole"/> class. 
    /// </summary> 
    /// <param name="name">The name.</param> 
    public CustomRole(string name) { Name = name; } 
} 

CustomUserClaim

public class CustomUserClaim : IdentityUserClaim<int> { } 

CustomUserLogin

public class CustomUserLogin : IdentityUserLogin<int> { } 

CustomUserRole

public class CustomUserRole : IdentityUserRole<int> {} 

utente

public class User : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim> 
{ 

    /// <summary> 
    /// Gets or sets the first name. 
    /// </summary> 
    /// <value>The first name.</value> 
    public string FirstName { get; set; } 

    /// <summary> 
    /// Gets or sets the last name. 
    /// </summary> 
    /// <value>The last name.</value> 
    public string LastName { get; set; } 

    /// <summary> 
    /// Gets or sets a value indicating whether this <see cref="User"/> is active. 
    /// </summary> 
    /// <value><c>true</c> if active; otherwise, <c>false</c>.</value> 
    public bool Active { get; set; } 

} 

non mi piace la denominazione delle tabelle di identità, così ho cambiato i nomi.

DataContext

public class DataContext : IdentityDbContext<User, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim> 
{ 
    public DataContext() : base("DefaultConnection"){} 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     base.OnModelCreating(modelBuilder); 

     modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles", "Security"); 
     modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins", "Security"); 
     modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims", "Security"); 
     modelBuilder.Entity<CustomRole>().ToTable("Roles", "Security"); 
     modelBuilder.Entity<User>().ToTable("Users", "Security"); 

    } 
} 

ho trovato ricevendo l'UserManager un po 'di dolore.

Ho creato una classe statica per gestirlo. Lo UserStore gestisce il ciclo di vita di DataContext, ma dovrai chiamare dispose perché ciò accada. Ciò potrebbe causare problemi se si sta utilizzando questa referenza DataContext altrove. Io alla fine collegarlo nel mio container DI, ma per ora questo è quello che ho:

public class Identity 
{ 
    /// <summary> 
    /// Gets the user manager. 
    /// </summary> 
    /// <returns>UserManager&lt;User, System.Int32&gt;.</returns> 
    public static UserManager<User, int> GetUserManager() 
    { 
     var store = new UserStore<User, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>(new DataContext()); 
     var userManager = new UserManager<User, int>(store); 

     return userManager; 
    } 
} 

io uso l'Unità di modello di lavoro per la maggior parte il mio accesso ai dati. Funziona bene. Ci sono alcuni casi in cui ho dati che richiedono un controllo maggiore di quello che l'unità di lavoro espone per questi casi che ho esposto DataContext. Se ciò non funziona ancora per me, ricado all'utilizzo di un repository.

public class UnitOfWork : IUnitOfWork 
{ 
    private readonly IContainer _container; 

    public UnitOfWork(IContainer container) :this() 
    { 
     _container = container; 
    } 

    //private readonly List<CommitInterception> _postInterceptions = new List<CommitInterception>(); 

    public DataContext Context { get; set; } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="UnitOfWork"/> class. 
    /// </summary> 
    public UnitOfWork() 
    { 
     Context = new DataContext(); 
    } 

    /// <summary> 
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 
    /// </summary> 
    /// <exception cref="System.NotImplementedException"></exception> 
    public void Dispose() 
    { 
     //Chuck was here 
     try 
     { 
      Commit(); 
     } 
     finally 
     { 
      Context.Dispose(); 
     } 
    } 

    /// <summary> 
    /// Begins the transaction. 
    /// </summary> 
    /// <returns>IUnitOfWorkTransaction.</returns> 
    public IUnitOfWorkTransaction BeginTransaction() 
    { 
     return new UnitOfWorkTransaction(this); 
    } 

    /// <summary> 
    /// Commits this instance. 
    /// </summary> 
    public void Commit() 
    { 
     Commit(null); 
    } 

    /// <summary> 
    /// Commits transaction. 
    /// </summary> 
    public void Commit(DbContextTransaction transaction) 
    { 
     //Lee was here. 
     try 
     { 
      Context.SaveChanges(); 

      if (transaction != null) 
      { 
       transaction.Commit(); 
      } 

      //foreach (var interception in _postInterceptions) 
      //{ 
      // interception.PostCommit(interception.Instance, this); 
      //} 

     } 
     catch (DbEntityValidationException ex) 
     { 
      var errors = FormatError(ex); 
      throw new Exception(errors, ex); 
     } 
     catch 
     { 
      if (transaction != null) 
      { 
       transaction.Rollback(); 
      } 
      throw; 
     } 
     finally 
     { 
      // _postInterceptions.Clear(); 
     } 
    } 

    /// <summary> 
    /// Formats the error. 
    /// </summary> 
    /// <param name="ex">The ex.</param> 
    /// <returns>System.String.</returns> 
    private static string FormatError(DbEntityValidationException ex) 
    { 
     var build = new StringBuilder(); 
     foreach (var error in ex.EntityValidationErrors) 
     { 
      var errorBuilder = new StringBuilder(); 

      foreach (var validationError in error.ValidationErrors) 
      { 
       errorBuilder.AppendLine(string.Format("Property '{0}' errored:{1}", validationError.PropertyName, validationError.ErrorMessage)); 
      } 

      build.AppendLine(errorBuilder.ToString()); 
     } 
     return build.ToString(); 
    } 

    /// <summary> 
    /// Inserts the specified entity. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="entity">The entity.</param> 
    /// <returns>``0.</returns> 
    public T Insert<T>(T entity) where T: class 
    { 
     var instance = _container.TryGetInstance<IUnitOfWorkInterception<T>>(); 

     if (instance != null) 
     { 
      instance.Intercept(entity, this); 
      // _postInterceptions.Add(new CommitInterception() { Instance = entity, PostCommit = (d,f) => instance.PostCommit(d as T, f) }); 
     } 

     var set = Context.Set<T>(); 
     var item = set.Add(entity); 

     return item; 
    } 

    public T Update<T>(T entity) where T : class 
    { 
     var set = Context.Set<T>(); 
     set.Attach(entity); 
     Context.Entry(entity).State = EntityState.Modified; 

     return entity; 
    } 

    /// <summary> 
    /// Deletes the specified entity. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="entity">The entity.</param> 
    public void Delete<T>(T entity) where T : class 
    { 
     var set = Context.Set<T>(); 
     set.Remove(entity); 
    } 

    /// <summary> 
    /// Finds the specified predicate. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="predicate">The predicate.</param> 
    /// <returns>IQueryable{``0}.</returns> 
    public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class 
    { 
     var set = Context.Set<T>(); 
     return set.Where(predicate); 
    } 

    /// <summary> 
    /// Gets all. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <returns>IQueryable{``0}.</returns> 
    public IQueryable<T> GetAll<T>() where T : class 
    { 
     return Context.Set<T>(); 
    } 

    /// <summary> 
    /// Gets the by identifier. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="id">The identifier.</param> 
    /// <returns>``0.</returns> 
    public T GetById<T>(int id) where T : class 
    { 
     var set = Context.Set<T>(); 
     return set.Find(id); 
    } 

    /// <summary> 
    /// Executes the query command. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="sql">The SQL.</param> 
    /// <returns>DbSqlQuery{``0}.</returns> 
    public DbSqlQuery<T> ExecuteQueryCommand<T>(string sql) where T : class 
    { 
     var set = Context.Set<T>(); 
     return set.SqlQuery(sql); 
    } 

    private class CommitInterception 
    { 
     public object Instance { get; set; } 

     public Action<object, IUnitOfWork> PostCommit { get; set; } 
    } 
} 

public class UnitOfWorkTransaction : IUnitOfWorkTransaction 
{ 
    private readonly UnitOfWork _unitOfWork; 
    private readonly DbContextTransaction _transaction; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="UnitOfWorkTransaction"/> class. 
    /// </summary> 
    /// <param name="unitOfWork">The unit of work.</param> 
    public UnitOfWorkTransaction(UnitOfWork unitOfWork) 
    { 
     _unitOfWork = unitOfWork; 
     _transaction = _unitOfWork.Context.Database.BeginTransaction(); 
     Context = unitOfWork.Context; 
    } 

    /// <summary> 
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 
    /// </summary> 
    public void Dispose() 
    { 
     _unitOfWork.Commit(_transaction); 
    } 

    public DataContext Context { get; set; } 

    /// <summary> 
    /// Commits this instance. 
    /// </summary> 
    public void Commit() 
    { 
     _unitOfWork.Commit(); 
    } 

    /// <summary> 
    /// Rollbacks this instance. 
    /// </summary> 
    public void Rollback() 
    { 
     _transaction.Rollback(); 
    } 

    /// <summary> 
    /// Inserts the specified entity. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="entity">The entity.</param> 
    /// <returns>T.</returns> 
    public T Insert<T>(T entity) where T : class 
    { 
     return _unitOfWork.Insert(entity); 
    } 

    /// <summary> 
    /// Updates the specified entity. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="entity">The entity.</param> 
    /// <returns>T.</returns> 
    public T Update<T>(T entity) where T : class 
    { 
     return _unitOfWork.Update(entity); 
    } 

    /// <summary> 
    /// Deletes the specified entity. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="entity">The entity.</param> 
    public void Delete<T>(T entity) where T : class 
    { 
     _unitOfWork.Delete(entity); 
    } 

    /// <summary> 
    /// Finds the specified predicate. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="predicate">The predicate.</param> 
    /// <returns>IQueryable&lt;T&gt;.</returns> 
    public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class 
    { 
     return _unitOfWork.Find(predicate); 
    } 

    /// <summary> 
    /// Gets all. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <returns>IQueryable&lt;T&gt;.</returns> 
    public IQueryable<T> GetAll<T>() where T : class 
    { 
     return _unitOfWork.GetAll<T>(); 
    } 

    /// <summary> 
    /// Gets the by identifier. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="id">The identifier.</param> 
    /// <returns>T.</returns> 
    public T GetById<T>(int id) where T : class 
    { 
     return _unitOfWork.GetById<T>(id); 
    } 

    /// <summary> 
    /// Executes the query command. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="sql">The SQL.</param> 
    /// <returns>DbSqlQuery&lt;T&gt;.</returns> 
    public DbSqlQuery<T> ExecuteQueryCommand<T>(string sql) where T : class 
    { 
     return _unitOfWork.ExecuteQueryCommand<T>(sql); 
    } 
} 

Ecco alcuni esempi in azione. Ho uno sfondo ibernato e mi piace definire una transazione nell'ambito di uno using quindi l'ho implementato nella mia unità di lavoro.

 using (var trans = _unitOfWork.BeginTransaction()) 
     { 
      var newAgency = trans.Insert(new Database.Schema.Agency() { Name = agency.Name, TaxId = agency.TaxId }); 

     } 

Un altro esempio di utilizzo del "Trova" fuori della unità di lavoro:

 var users = _unitOfWork.Find<Database.Schema.User>(s => s.Active && s.Agency_Id == agencyId) 
      .Select(u=> new {Label = u.FirstName + " " + u.LastName, Value = u.Id}) 
      .ToList(); 

creazione di utenti e utenti Sign-In

Io uso ASP.NET identità per la accesso e creazione degli utenti e la mia unità di lavoro per tutto il resto.

Testing

non vorrei provare a testare ASP.NET identità. Per uno sono sicuro che Microsoft ha fatto un buon lavoro testandolo. Sono sicuro che hanno fatto un lavoro migliore di quello che tu o io potevamo fare. Se vuoi veramente testare il codice di identità di ASP.NET, mettilo dietro un'interfaccia e prendi in giro l'interfaccia.

4

Trovato una soluzione, che sembra abbastanza generica, ma non sono ancora sicuro se è davvero buona e non infrange i principi del modello Repository/UnitOfWork.

ho aggiunto metodo generico GetDbContext() al mio IUnitOfWork:

public interface IUnitOfWork : IDisposable 
{ 
    void Save();  
    IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;  
    TContext GetDbContext<TContext>() where TContext : DbContext, IDbContext; 
} 

implementazione sua in classe UnitOfWork:

public class UnitOfWork<TContext> : IUnitOfWork where TContext : IDbContext, new() 
{ 
    private IDbContext dbContext; 

    public UnitOfWork() 
    { 
     this.dbContext = new TContext(); 
    } 

    public T GetDbContext<T>() where T : DbContext, IDbContext 
    { 
     return this.dbContext as T; 
    } 

    ... 
} 

Come si usa in un controllore, l'inizializzazione UserManager:

public class AccountController : ControllerBase 
{ 
    private readonly IUnitOfWork unitOfWork; 

    public UserManager<ApplicationUser> UserManager { get; private set; } 

    public AccountController() 
     : this(new UnitOfWork<MyDbContext>()) 
    { 
    } 

    public AccountController(IUnitOfWork unitOfWork) 
    { 
     this.unitOfWork = unitOfWork;  
     UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(unitOfWork.GetDbContext<MyDbContext>()); 
     this.UserManager = new UserManager<ApplicationUser>(store); 
    } 

    ... 
} 

Sospetto che GetDbContext() venga utilizzato solo per risolvere alcune difficoltà con ASP.Identity, così potrebbe non essere così male ..

3

"Un problema da tenere presente è che la classe UserStore non gioca bene quando si utilizza l'unità di modello di progettazione del lavoro. Nello specifico, lo UserStore richiama SaveChanges in quasi tutte le chiamate di metodo per impostazione predefinita, il che rende facile commettere anticipatamente un'unità di lavoro. Per modificare questo comportamento, cambiare la bandiera AutoSaveChanges sul UserStore "

var store = new UserStore<ApplicationUser>(new ApplicationDbContext()); 
store.AutoSaveChanges = false; 

Da Scott Allen: http://odetocode.com/blogs/scott/archive/2014/01/03/asp-net-identity-with-the-entity-framework.aspx

Problemi correlati