Я искал ответ на этот вопрос и обнаружил, что это легко сделать с помощью Managed Extensibility Framework. Ниже приведен более быстрый путь, однако MEF допускает гораздо более масштабируемый подход.
MEF позволяет создавать плагины динамического доступа из разрозненных сборок; однако его можно использовать для быстрого заполнения коллекций в одном приложении сборки. По сути, мы будем использовать его как безопасный способ отражения нашей сборки обратно в класс. Чтобы сделать это полностью функциональным, я также собираюсь внедрить шаблон стратегии в модель Entity Framework.
Добавьте ссылку на ваш проект, указывая на System.ComponentModel.Composition
. Это даст доступ к библиотеке MEF.
Теперь нам нужно реализовать шаблон стратегии. Если у вас нет папки Interfaces, создайте ее и добавьте IEntity.cs, как показано ниже.
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; }
}
}
Теперь каждый из вас должен реализовать этот интерфейс:
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...
}
Я считаю, что рекомендуется не создавать отдельные интерфейсы для каждого объекта, если только вы не работаете в среде с высоким уровнем тестирования. С практической точки зрения, интерфейсы должны использоваться только там, где необходим этот уровень абстракции; главным образом, когда более чем один конкретный класс унаследует, или когда работает с чрезмерно восторженным движком Inversion of Control. Если у вас есть интерфейсы для всего в вашей производственной модели, ваша архитектура, скорее всего, имеет серьезные недостатки. Во всяком случае, достаточно бессвязных.
Теперь, когда у нас есть все наши сущности, "стратегизированные", мы можем использовать MEF для их сравнения и заполнения коллекции в вашем контексте.
В вашем контексте добавьте новое свойство:
/// <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; }
Здесь [ImportMany(typeof(DbSet<IEntity>))]
позволяет MEF заполнять коллекцию.
Затем добавьте соответствующий атрибут Export
к каждому DbSet в контексте:
[Export(typeof(DbSet<IEntity>))]
public DbSet<MyEntity> MyEntities { get; set; }
Каждое из свойств Import
ed и Export
ed называется «деталью». Последняя часть головоломки состоит в том, чтобы составить эти части. Добавьте следующее в конструктор вашего контекста:
// 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);
Теперь, если повезет, у вас должен быть динамически заполняемый список DbSets в вашем контексте.
Я использовал этот метод для упрощения усечения всех таблиц с помощью метода расширения.
/// <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;
}
}
Я добавил метод в контекст для усечения всей базы данных.
/// <summary>
/// Truncates the database.
/// </summary>
public void TruncateDatabase()
{
Sets.ToList().ForEach(s => s.Truncate());
SaveChanges();
}
РЕДАКТИРОВАТЬ (капитальный ремонт):
Решение, приведенное выше, теперь устарело. Нужно немного подправить, чтобы это сработало. Для этого вам нужно импортировать DbSets во временную коллекцию DbSet типа «объект», а затем преобразовать эту коллекцию в DbSet требуемого типа интерфейса. Для базовых целей достаточно интерфейса IEntity.
#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
Затем запустите фасад CompileSetsList()
из конструктора (показаны лучшие практики для Web):
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();
}
Затем просто украсьте свои DbSet <> как это:
/// <summary>
/// Gets or sets the job levels.
/// </summary>
/// <value>
/// The job levels.
/// </value>
[Export("Sets", typeof(DbSet<object>))]
public DbSet<JobLevel> JobLevels { get; set; }
Теперь все будет работать правильно.