6

Tengo un DbContext con varios de los siguientes tipos de miembros:Cómo generalizar el acceso a DbSet <TEntity> miembros de 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; } 

Todos estos son where T: IdNamePairBase, que tiene sólo Id y Name miembros. Estoy tratando desesperadamente de encontrar una interfaz común con la que acceder a cualquiera de estos miembros, a generalizar el siguiente código de controlador de MVC3 en un solo controlador:

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}); 
} 

¿Cómo podría hacer para resolver mi problema de que ahora mismo necesito uno controlador para cada uno de los miembros DbContext, como el anterior está dedicado a DbSet<Country> Countries?

solución parcial: En una línea similar a la respuesta de GertArnold a continuación, antes de conocer la _dbContext.Set<T> todo lo que pone de relieve, he implementado este método en mi clase de contexto para obtener conjuntos de un tipo específico:

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 enfoque totalmente diferente en un contexto diferente, pero tal vez sea interesante para usted: http://stackoverflow.com/questions/9762808/change-fluent-api-mapping-dynamically –

Respuesta

9

Es posible hacer algunas generalizaciones usando

var dbSet = _dbContext.Set<T> 

y poniendo la mayor parte de su método en un método con un parámetro de tipo genérico.

Sin embargo, debe haber un interruptor en algún lugar para decidir qué tipo debe especificarse y qué tipo crear, porque creo que el tipo se suministra como una propiedad del modelo (¿verdad?). Por lo tanto, probablemente no se vea realmente elegante, pero probablemente sea mucho más corto, con el código DRY-er.

3

Para añadir a la respuesta de Gert Arnold, quiero señalar que hay otra sobrecarga del método en la dbContext que devuelve un DbSet general desde un objeto de tipo:

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

Si desea agregar ciego un objeto, a continuación, crear el objeto mediante el método set.Create(), o si ya tiene un objeto creado con el keyowrd "new", se puede convertir mediante el uso (similar a this answer)

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

he estado buscando una respuesta a th es una pregunta y descubrí que es fácil hacerlo usando el Marco de Extensibilidad Administrada. Hay una manera más rápida en la parte inferior de esta publicación, sin embargo, MEF permite un enfoque mucho más escalable.

MEF le permite crear complementos de acceso dinámico desde ensamblajes dispares; sin embargo, se puede usar para rellenar rápidamente colecciones dentro de una sola aplicación de ensamblaje. En esencia, lo usaremos como una forma segura de reflejar nuestro ensamblaje de nuevo en la clase. Para hacer esto completamente funcional, también voy a implementar el Patrón de Estrategia para el Modelo de Marco de la Entidad.

Agregue una referencia a su proyecto, señalando System.ComponentModel.Composition. Esto dará acceso a la biblioteca de MEF.

Ahora, tenemos que implementar el Patrón de estrategia. Si no tiene una carpeta Interfaces, cree una y agregue IEntity.cs, como se muestra a continuación.

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; } 
    } 
} 

Ahora, cada uno de ustedes entidades concretas necesita para implementar esta interfaz:

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... 
} 

me parece que es la mejor práctica, para no crear interfaces individuales para cada entidad, a menos que esté trabajando en un ambiente de prueba alto. Pragmáticamente, las interfaces solo deberían usarse cuando se necesita ese nivel de abstracción; principalmente cuando heredará más de una clase concreta o cuando se trabaje con un motor de inversión de control demasiado entusiasta. Si tiene interfaces para todo en su modelo de producción, su arquitectura es más que probable, tiene fallas importantes. De todos modos, basta de las divagaciones.

Ahora que tenemos todas nuestras entidades "estratégicas", podemos utilizar MEF para cotejarlas y completar una colección dentro de su contexto.

Dentro de su contexto, añadir una nueva propiedad:

/// <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; } 

El [ImportMany(typeof(DbSet<IEntity>))] aquí, permite MEF para poblar la colección.

A continuación, añadir el atributo correspondiente Export a cada DbSet dentro del contexto:

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

Cada una de las propiedades Import ed y Export ed se conoce como una "parte". La última pieza del rompecabezas es componer esas partes. Agregue lo siguiente al constructor de su contexto:

// 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); 

Ahora, con un poco de suerte, usted debe tener una lista dinámica de población de DbSets, dentro de su contexto.

He usado este método para permitir truncar fácilmente todas las tablas mediante un método de extensión.

/// <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; 
    } 
} 

He añadido un método al contexto para truncar toda la base de datos.

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

EDIT (Revisión):

La solución anterior ha sido ahora depreciado. Hubo que hacer algunos tweets para que esto funcione ahora. Para que esto funcione, debe importar los DbSets en una colección temporal de DbSet del tipo "objeto", luego, eche esta colección a DbSet del tipo de interfaz requerido. Para propósitos básicos, la interfaz IEntity será suficiente.

#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 

A continuación, ejecute el CompileSetsList() fachada del constructor (con las mejores prácticas para la Web se muestra):

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(); 
    } 

Entonces, simplemente decorar tu DbSet <> s como esto:

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

Ahora funcionará correctamente.

+0

Muy buena respuesta. Solo aprendí todo sobre MEF a principios de este año. – ProfK

Cuestiones relacionadas