Подтвердить что ты не робот

EF: в поисках стратегии проектирования для DatabaseFirst DbContext модульного приложения

Я ищу предложения о том, как подойти с использованием ORM (в данном случае EF5) при разработке модульных немонолитных приложений с модулями Core и 3rd Party, где Core не имеет прямой ссылки на сторонние модули и модули имеют только ссылку на основные и общие таблицы и классы.

Для аргументов достаточно близкой аналогией будет DNN.

CodeFirst:

С CodeFirst подход, который я использовал, заключался в создании модели Db с помощью отражения: на этапе Core DbContext DbInitialation я использовал Reflection для поиска любого класса в любой dll (например, Core или различных модулей), украшенных IDbInitializer (пользовательский контракт, содержащий метод Execute()) для определения только структуры dll. Каждая dll добавила в DbModel то, что она знала о себе. Любое последующее сеяние также обрабатывалось одним и тем же ва (поиск конкретного контракта IDbSeeder и его выполнение).

Pro: * Подход работает пока. * Тот же основной DbContext может использоваться во всех репозиториях, если каждый репо использует dbContext.GetSet(), а не ожидает, что он будет свойством dbContext. Нет, biggie. Минусы:  * он работает только при запуске (т.е. для добавления новых модулей потребуется обновление AppPool).  * CodeFirst отлично подходит для POC. Но в EF5 он еще недостаточно зрел для работы Enterprise (и я не могу дождаться EF6 для StoredProcs и других функций, которые будут добавлены).  * Мой администратор базы данных ненавидит CodeFirst, по крайней мере для Core, желая как можно больше оптимизировать эту часть с Stored Procs... Мы команда, поэтому я должен попытаться найти способ понравиться ему, если смогу найти способ...

База-первых:

Фаза DbModel, по-видимому, происходит до конструктора DbContext (чтение из встроенного файла ресурсов .edmx). DbInitialization никогда не вызывается (поскольку модель считается полной), поэтому я не могу добавить больше таблиц, чем то, о чем знает Core.

Если я не могу добавлять элементы в модель, динамически, как можно с CodeFirst, это означает, что * либо Core DbContext Model должна знать каждую таблицу в Db-Core и каждом стороннем модуле. Создание приложения Монолитное и высокосвязное, победив то, чего я пытаюсь достичь. * Или каждая сторонняя сторона должна создать свой собственный DbContext, импортируя таблицы Core, что приведет к  * проблемы с версиями (модуль не обновляет *.edmx при обновлении Core *.edmx и т.д.)  * дублирование везде, в разных контекстах памяти = трудно отследить проблемы concurrency.

На данный момент мне кажется, что подход CodeFirst - единственный способ, которым программное обеспечение Modular может быть достигнуто с помощью EF. Но, надеюсь, кто-то еще знает, как сделать DatabaseFirst shine - есть ли способ "добавления" DbSet к модели, созданной из встроенного файла .edmx?

Или любые другие идеи?

4b9b3361

Ответ 1

Я бы подумал об использовании своего рода архитектуры plugin, так как это ваш общий дизайн для самого приложения.

Вы можете выполнить основы этого, выполнив что-то вроде следующего (обратите внимание, что в этом примере используется StructureMap - вот ссылка на StructureMap Documentation):

  • Создайте интерфейс, из которого могут быть получены ваши объекты DbContext.

    public interface IPluginContext {
        IDictionary<String, DbSet> DataSets { get; }
    }
    
  • В настройке Injection Dependency (используя StructureMap) - сделайте следующее:

    Scan(s => {
        s.AssembliesFromApplicationBaseDirectory();
        s.AddAllTypesOf<IPluginContext>();
        s.WithDefaultConventions();
    });
    For<IEnumerable<IPluginContext>>().Use(x =>
        x.GetAllInstances<IPluginContext>()
    );
    
  • Для каждого из ваших плагинов либо измените файл {plugin}.Context.tt, либо добавьте файл partial class, который вызывает генерирование DbContext для IPluginContext.

    public partial class FooContext : IPluginContext { }
    
  • Измените файл {plugin}.Context.tt для каждого плагина, чтобы выставить что-то вроде:

    public IDictionary<String, DbSet> DataSets {
        get {
            // Here is where you would have the .tt file output a reference
            // to each property, keyed on its property name as the Key - 
            // in the form of an IDictionary.
        }
    }
    
  • Теперь вы можете сделать что-то вроде следующего:

    // This could be inside a service class, your main Data Context, or wherever
    // else it becomes convenient to call.
    public DbSet DataSet(String name) {
        var plugins = ObjectFactory.GetInstance<IEnumerable<IPluginContext>>();
        var dataSet = plugins.FirstOrDefault(p =>
            p.DataSets.Any(ds => ds.Key.Equals(name))
        );
    
        return dataSet;
    }
    

Простите меня, если синтаксис не идеален - я делаю это в сообщении, а не в компиляторе.

Конечный результат дает вам возможность сделать что-то вроде:

    // Inside an MVC controller...
    public JsonResult GetPluginByTypeName(String typeName) {
        var dataSet = container.DataSet(typeName);
        if (dataSet != null) {
            return Json(dataSet.Select());
        } else {
            return Json("Unable to locate that object type.");
        }
    }

Очевидно, что в долгосрочной перспективе вы хотите, чтобы элемент управления был инвертирован, где плагин - это тот, который фактически привязан к архитектуре, а не сервер, ожидающий тип. Тем не менее, вы можете выполнять те же самые вещи, используя этот тип ленивой загрузки, - где основное приложение предоставляет конечную точку, к которой привязаны все плагины.

Это будет что-то вроде:

public interface IPlugin : IDisposable {
    void EnsureDatabase();
    void Initialize();
}

Теперь вы можете разоблачить этот интерфейс для любых разработчиков приложений, которые собираются создавать плагины для вашей архитектуры (стиль DNN) - и ваша конфигурация StructureMap работает примерно так:

Scan(s => {
    s.AssembliesFromApplicationBaseDirectory(); // Upload all plugin DLLs here
    // NOTE: Remember that this gives people access to your system!!! 
    //       Given what you are developing, though, I am assuming you
    //       already get that.
    s.AddAllTypesOf<IPlugin>();
    s.WithDefaultConventions();
});
For<IEnumerable<IPlugin>>().Use(x => x.GetAllInstances<IPlugin>());

Теперь, когда вы инициализируете свое приложение, вы можете сделать что-то вроде:

// Global.asax
public static IEnumerable<IPlugin> plugins = 
    ObjectFactory.GetInstance<IEnumerable<IPlugin>>();

public void Application_Start() {
    foreach(IPlugin plugin in plugins) {
        plugin.EnsureDatabase();
        plugin.Initialize();
    }
}

Каждый из ваших объектов IPlugin теперь может содержать свой собственный контекст базы данных, управлять процессом установки (при необходимости) собственного экземпляра базы данных/таблиц и изящно распоряжаться собой.

Ясно, что это не полное решение, но я надеюсь, что он начнет вас в полезном направлении.:) Если я могу помочь прояснить что-либо здесь, сообщите мне.

Ответ 2

В то время как это подход CodeFirst, а не самое чистое решение, о том, как заставить инициализацию запускать даже после запуска, как в ответе, опубликованном здесь: Принудительно кодовое инициализация всегда несуществующей базы данных? (я точно ничего не знаю о CodeFirst, но, видя, что это месяц, без сообщений, это стоит того)