6

Ho un DbContext con vari dei seguenti tipi di membri:Come generalizzare l'accesso ai membri DbSet <TEntity> di un DbContext?

public DbSet<JobLevel> JobLevels { get; set; } 
public DbSet<Country> Countries { get; set; } 
public DbSet<Race> Races { get; set; } 
public DbSet<Language> Languages { get; set; } 
public DbSet<Title> Titles { get; set; } 

Tutti questi sono where T: IdNamePairBase, che ha solo Id e Name membri. Sto cercando disperatamente di trovare un'interfaccia comune con il quale accedere a qualsiasi di questi membri, per generalizzare il seguente codice di controllo MVC3 in un controller:

public ActionResult Edit(DropDownListModel model, Guid) 
{ 
    var dbSet = _dbContext.Countries; 
    var newItems = model.Items.Where(i => i.IsNew && !i.IsDeleted).Select(i => new { i.Name }); 
    foreach (var item in newItems) 
    { 
     if (!string.IsNullOrWhiteSpace(item.Name)) 
     { 
      var undead = ((IEnumerable<IdNamePairBase>)dbSet).FirstOrDefault(p => p.Name.ToLower() == item.Name.ToLower()); 
      if (undead != null) 
      { 
       // Assign new value to update to the new char. case if present. 
       undead.Name = item.Name; 
       undead.IsDeleted = false; 
       _dbContext.SaveChanges(); 
       continue; 
      } 
      var newPair = new Country { Name = item.Name }; 
      dbSet.Add(newPair); 
      _dbContext.SaveChanges(); 
     } 
    } 
    return RedirectToAction("Edit", new {listName = model.ListName}); 
} 

Come potrei fare per risolvere il mio problema che in questo momento ho bisogno di uno controller per ciascuno dei membri DbContext, come quello sopra è dedicato a DbSet<Country> Countries?

soluzione parziale: lungo linee simili a risposta di GertArnold di seguito, prima di sapere circa la _dbContext.Set<T> tutto ciò che mette in luce, ho implementato questo metodo su mia classe contesto per ottenere set di un tipo specifico:

public IEnumerable<DbSet<T>> GetDbSetsByType<T>() where T : class 
{ 
    //var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance; 
    var props = GetType().GetProperties() 
     .Where(p => p.PropertyType.IsGenericType && p.PropertyType.Name.StartsWith("DbSet")) 
     .Where(p => p.PropertyType.GetGenericArguments().All(t => t == typeof(T))); 
    return props.Select(p => (DbSet<T>)p.GetValue(this, null)); 
} 
+0

Un approccio totalmente diverso in un contesto diverso, ma forse interessante per te: http://stackoverflow.com/questions/9762808/change-fluent-api-mapping-dynamically –

risposta

9

alcuni generalizzazione è possibile utilizzando

var dbSet = _dbContext.Set<T> 

e mettendo la maggior parte del metodo in un metodo con un parametro di tipo generici.

Tuttavia, ci dovrebbe essere un interruttore da qualche parte per decidere quale tipo deve essere specificato e quale tipo creare, perché penso che il tipo sia fornito come una proprietà del modello (vero?). Quindi probabilmente non sarà davvero elegante, ma probabilmente sarà molto più breve, con il codice DRY-er.

3

Per aggiungere alla risposta di Gert Arnold, voglio sottolineare che c'è un altro overload del metodo sul DbContext che restituisce un DbSet generale da un oggetto di tipo:

var dbSet = dbContext.Set(typeof(T)) 

Se si desidera aggiungere cieco un oggetto, quindi creare l'oggetto utilizzando il metodo set.Create(), o se si dispone già di un oggetto creato con la "new" keyowrd, è possibile convertirlo utilizzando (simile a this answer)

var entity = dbSet.Create(); 
dbSet.Add(entity); 
DbEntityEntry entry = context.Entry(entity); 
entry.CurrentValues.SetValues(yourObject); 
3

ho cercato una risposta a th è una domanda e ho scoperto che è facile fare uso del Managed Extensibility Framework. C'è un modo più veloce in fondo a questo post, tuttavia MEF consente un approccio molto più scalabile.

MEF consente di creare plug-in di accesso dinamico da gruppi disparati; tuttavia può essere utilizzato per popolare rapidamente le raccolte all'interno di una singola applicazione di assemblaggio. In pratica, lo utilizzeremo come un modo sicuro per rispecchiare il nostro assembly nella classe. Per rendere tutto ciò perfettamente funzionante, implementerò anche il modello di strategia per il modello di Entity Framework.

Aggiungere un riferimento al progetto, che punta a System.ComponentModel.Composition. Ciò consentirà l'accesso alla libreria MEF.

Ora è necessario implementare il modello di strategia. Se non si dispone di una cartella Interfacce, crearne una e aggiungere IEntity.cs, come di seguito.

IEntity.cs

namespace Your.Project.Interfaces 
{ 
    /// <summary> 
    ///  Represents an entity used with Entity Framework Code First. 
    /// </summary> 
    public interface IEntity 
    { 
     /// <summary> 
     ///  Gets or sets the identifier. 
     /// </summary> 
     /// <value> 
     ///  The identifier. 
     /// </value> 
     int Id { get; set; } 
    } 
} 

Ora, ognuno di voi entità concrete necessità di implementare questa interfaccia:

public class MyEntity : IEntity 
{ 
    #region Implementation of IEntity 

    /// <summary> 
    ///  Gets or sets the identifier. 
    /// </summary> 
    /// <value> 
    ///  The identifier. 
    /// </value> 
    public int Id { get; set; } 

    #endregion 

    // Other POCO properties... 
} 

trovo che è meglio la pratica, non creare interfacce individuali per ogni entità, a meno che non sei lavorando in un ambiente di test elevato. Pragmaticamente, le interfacce dovrebbero essere utilizzate solo dove è necessario quel livello di astrazione; soprattutto quando più di una classe concreta erediterà, o quando si lavora con un motore Inversion of Control troppo entusiasta. Se hai interfacce per tutto ciò che è nel tuo modello di produzione, la tua architettura più che probabile, ha grossi difetti. Ad ogni modo, abbastanza del vagabondare.

Ora che abbiamo tutte le nostre entità "strategizzate", possiamo usare MEF per raggrupparle e popolare una raccolta nel vostro contesto.

All'interno del vostro contesto, aggiungere una nuova proprietà:

/// <summary> 
///  Gets a dynamically populated list of DbSets within the context. 
/// </summary> 
/// <value> 
///  A dynamically populated list of DbSets within the context. 
/// </value> 
[ImportMany(typeof(DbSet<IEntity>))] 
public IEnumerable<DbSet<IEntity>> Sets { get; private set; } 

Il [ImportMany(typeof(DbSet<IEntity>))] qui, permette MEF per popolare la raccolta.

Successivamente, aggiungere il Export attributo corrispondente a ciascun DbSet nel contesto:

[Export(typeof(DbSet<IEntity>))] 
public DbSet<MyEntity> MyEntities { get; set; } 

Ciascuna delle proprietà Import cati e Export DE è noto come "parte". Il pezzo finale del puzzle è comporre quelle parti. Aggiungere il seguente al costruttore del contesto:

// Instantiate the Sets list. 
Sets = new List<DbSet<IEntity>>(); 

// Create a new Types catalogue, to hold the exported parts. 
var catalogue = new TypeCatalog(typeof (DbSet<IEntity>)); 

// Create a new Composition Container, to match all the importable and imported parts. 
var container = new CompositionContainer(catalogue); 

// Compose the exported and imported parts for this class. 
container.ComposeParts(this); 

Ora, con tutta la fortuna, si dovrebbe avere un elenco popolato dinamicamente dei DbSets, all'interno del vostro contesto.

Ho usato questo metodo per consentire un facile troncamento di tutte le tabelle tramite un metodo di estensione.

/// <summary> 
///  Provides extension methods for DbSet objects. 
/// </summary> 
public static class DbSetEx 
{ 
    /// <summary> 
    ///  Truncates the specified set. 
    /// </summary> 
    /// <typeparam name="TEntity">The type of the entity.</typeparam> 
    /// <param name="set">The set.</param> 
    /// <returns>The truncated set.</returns> 
    public static DbSet<TEntity> Truncate<TEntity>(this DbSet<TEntity> set) 
     where TEntity : class, IEntity 
    { 
     set.ToList().ForEach(p => set.Remove(p)); 
     return set; 
    } 
} 

Ho aggiunto un metodo al contesto per troncare l'intero database.

/// <summary> 
///  Truncates the database. 
/// </summary> 
public void TruncateDatabase() 
{ 
    Sets.ToList().ForEach(s => s.Truncate()); 
    SaveChanges(); 
} 

EDIT (Overhaul):

Questa soluzione è stata ora ammortizzati. Qualche tweeking come doveva essere fatto per farlo funzionare ora. Per fare in modo che funzioni, è necessario importare i DbSet in una raccolta temporanea di DbSet di tipo "object", quindi lanciare questa raccolta su DbSet del tipo di interfaccia richiesto. Per gli scopi di base, l'interfaccia IEntity sarà sufficiente.

#region Dynamic Table List 

    /// <summary> 
    ///  Gets a dynamically populated list of DbSets within the context. 
    /// </summary> 
    /// <value> 
    ///  A dynamically populated list of DbSets within the context. 
    /// </value> 
    public List<DbSet<IEntity>> Tables { get; private set; } 

    /// <summary> 
    ///  Gets a dynamically populated list of DbSets within the context. 
    /// </summary> 
    /// <value> 
    ///  A dynamically populated list of DbSets within the context. 
    /// </value> 
    [ImportMany("Sets", typeof (DbSet<object>), AllowRecomposition = true)] 
    private List<object> TableObjects { get; set; } 

    /// <summary> 
    ///  Composes the sets list. 
    /// </summary> 
    /// <remarks> 
    ///  To make this work, you need to import the DbSets into a temporary collection of 
    ///  DbSet of type "object", then cast this collection to DbSet of your required 
    ///  interface type. For basic purposes, the IEntity interface will suffice. 
    /// </remarks> 
    private void ComposeSetsList() 
    { 
     // Instantiate the list of tables. 
     Tables = new List<DbSet<IEntity>>(); 

     // Instantiate the MEF Import collection. 
     TableObjects = new List<object>(); 

     // Create a new Types catalogue, to hold the exported parts. 
     var catalogue = new TypeCatalog(typeof (DbSet<object>)); 

     // Create a new Composition Container, to match all the importable and imported parts. 
     var container = new CompositionContainer(catalogue); 

     // Compose the exported and imported parts for this class. 
     container.ComposeParts(this); 

     // Safe cast each DbSet<object> to the public list as DbSet<IEntity>. 
     TableObjects.ForEach(p => Tables.Add(p as DbSet<IEntity>)); 
    } 

    #endregion 

Avanti, eseguire il CompileSetsList() facciata dal costruttore (con le migliori pratiche per il Web mostrati):

public MvcApplicationContext() 
    { 
     // Enable verification of transactions for ExecuteSQL functions. 
     Configuration.EnsureTransactionsForFunctionsAndCommands = true; 

     // Disable lazy loading. 
     Configuration.LazyLoadingEnabled = false; 

     // Enable tracing of SQL queries. 
     Database.Log = msg => Trace.WriteLine(msg); 

     // Use MEF to compile a list of all sets within the context. 
     ComposeSetsList(); 
    } 

Poi, basta decorare il vostro DbSet <> s come questo:

/// <summary> 
    /// Gets or sets the job levels. 
    /// </summary> 
    /// <value> 
    /// The job levels. 
    /// </value> 
    [Export("Sets", typeof(DbSet<object>))] 
    public DbSet<JobLevel> JobLevels { get; set; } 

Ora funzionerà correttamente.

+0

Risposta molto buona. Ho solo imparato tutto su MEF all'inizio di quest'anno. – ProfK

Problemi correlati