Обертка DbSet <TEntity> с помощью специального DbSet/IDbSet?

Во-первых, я думаю, что это несколько смешно делать, но другие члены моей команды настаивают на этом, и я не могу придумать хороший аргумент против него, кроме "Я думаю, что это глупо"...

То, что мы пытаемся сделать, - создать полностью абстрактный слой данных, а затем выполнить различные реализации этого слоя данных. Достаточно просто, не так ли? Введите Entity Framework 4.1...

Наша конечная цель заключается в том, что программисты (я стараюсь оставаться только на слое данных) никогда не хотят, чтобы их подвергали конкретным классам. Они только когда-либо захотят использовать интерфейсы в своем коде, кроме того, что явно необходимо создать экземпляр factory.

Я хочу добиться чего-то вроде следующего:

Сначала у нас есть наша "общая" библиотека всех интерфейсов, мы будем называть ее "Common.Data":

public interface IEntity
{
    int ID { get; set; }
}

public interface IUser : IEntity
{
    int AccountID { get; set; }
    string Username { get; set; }
    string EmailAddress { get; set; }
    IAccount Account { get; set; }
}

public interface IAccount : IEntity
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]?
}

public interface IEntityFactory
{
    DbSet<IUser> Users { get; }
    DbSet<IAccount> Accounts { get; }
}

Из этого мы имеем библиотеку реализации, мы будем называть ее "Something.Data.Imp":

internal class User : IUser
{
    public int ID { get; set; }
    public string Username { get; set; }
    public string EmailAddress { get; set; }
    public IAccount Account { get; set; }

    public class Configuration : EntityTypeConfiguration<User>
    {
        public Configuration() : base()
        {
             ...
        }
    }
}

internal class Account : IAccount
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DbSet<IUser> Users { get; set; } // OR IDbSet<IUser> OR [IDbSet implementation]?

    public class Configuration : EntityTypeConfiguration<Account>
    {
        public Configuration() : base()
        {
             ...
        }
    }
}

Factory:

public class ImplEntityFactory : IEntityFactory
{
    private ImplEntityFactory(string connectionString) 
    {
        this.dataContext = new MyEfDbContext(connectionString);
    }
    private MyEfDbContext dataContext;

    public static ImplEntityFactory Instance(string connectionString)
    {
        if(ImplEntityFactory._instance == null)
            ImplEntityFactory._instance = new ImplEntityFactory(connectionString);

        return ImplEntityFactory._instance;
    }
    private static ImplEntityFactory _instance;

    public DbSet<IUser> Users // OR IDbSet<IUser> OR [IDbSet implementation]?
    { 
        get { return dataContext.Users; }
    }

    public DbSet<IAccount> Accounts // OR IDbSet<IUser> OR [IDbSet implementation]?
    {
        get { return dataContext.Accounts; }
    }
}

Context:

public class MyEfDataContext : DbContext
{
    public MyEfDataContext(string connectionString)
        : base(connectionString)
    {
        Database.SetInitializer<MyEfDataContext>(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new User.Configuration());
        modelBuilder.Configurations.Add(new Account.Configuration());
        base.OnModelCreating(modelBuilder);
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Account> Accounts { get; set; }
}

Тогда интерфейсные программисты будут использовать его, например:

public class UsingIt
{
    public static void Main(string[] args)
    {
        IEntityFactory factory = new ImplEntityFactory("SQLConnectionString");
        IUser user = factory.Users.Find(5);
        IAccount usersAccount = user.Account;

        IAccount account = factory.Accounts.Find(3);
        Console.Write(account.Users.Count());
    }
}

Так что в значительной степени это... Я надеюсь, что кто-то здесь сможет указать мне в правильном направлении или помочь мне с хорошим аргументом, что я могу отстреливаться в команде разработчиков. Я просмотрел некоторые другие статьи на этом сайте о том, что EF не может работать с интерфейсами и один ответ, говоря, что вы не можете реализовать IDbSet (который я найти любопытных, почему бы им это предоставить, если вы не смогли его реализовать?), но до сих пор безрезультатно.

Заранее благодарим за помощь! J

Ответы

Ответ 1

Первый аргумент состоит в том, что EF не работает с интерфейсами. DbSet должен быть определен с реализацией реального объекта.

Второй аргумент состоит в том, что ваши объекты не должны содержать DbSet - это связанный с контекстом класс, и ваши сущности должны быть чистыми от такой зависимости, если вы не собираетесь внедрять шаблон Active record. Даже в таком случае у вас определенно не будет доступа к DbSet другого объекта в другом объекте. Даже если вы обертываете набор, вы все еще слишком близки к EF, и сущность никогда не имеет свойства, обращающегося ко всем сущностям другого типа сущности (а не только к текущему экземпляру).

Просто, чтобы было ясно, что DbSet в EF имеет особое значение - это не коллекция. Это точка входа в базу данных (например, каждый запрос LINQ в базе данных "t20 > hits" ), и она находится в обычных сценариях, которые не отображаются на объектах.

Третий аргумент состоит в том, что вы используете один контекст для каждого приложения - у вас есть отдельный частный экземпляр для одного элемента factory. Если вы не выполняете какое-то одноразовое пакетное приложение это определенно неправильно.

Последний аргумент просто практичен. Вам платят за предоставление функций, а не за трату времени на абстракцию, которая не дает вам (и вашему клиенту) никакой коммерческой ценности. Дело не в том, чтобы доказать, почему вы не должны создавать эту абстракцию. Это доказывает, почему вы должны это делать. Какую ценность вы получите от его использования? Если ваши коллеги не могут прийти с аргументами, которые имеют деловую ценность, вы можете просто пойти к менеджеру вашего продукта и позволить ему использовать свою власть - он держит бюджет.

В целом абстракция является частью хорошо спроектированного объектно-ориентированного приложения - это правильно. НО:

  • Каждая абстракция сделает ваше приложение как-то более сложным и увеличит стоимость и время разработки.
  • Не каждая абстракция сделает ваше приложение лучше или удобнее обслуживать - слишком большая абстракция имеет обратный эффект.
  • Тестирование EF затруднено. Говорить, что вы будете абстрагировать доступ к данным таким образом, что вы можете заменить его другой реализацией, является задачей для гуру доступа к данным. Прежде всего, вы должны иметь очень хороший опыт работы со многими технологиями доступа к данным, чтобы иметь возможность определять такую ​​абстракцию, которая будет работать со всеми из них (и в конце вы можете только сказать, что ваша абстракция работает с технологиями, о которых вы думали при разработке этого). Ваша абстракция будет работать только с API EF DbContext и ничем иным, потому что это не абстракция. Если вы хотите создать универсальную абстракцию, вы должны начать изучать шаблон репозитория, шаблон работы и шаблон спецификации, но это большая работа, чтобы сделать их и реализовать их универсальными. Первым шагом является скрытие всего, что связано с доступом к данным за этой абстракцией - включая LINQ!
  • Абстрагирование доступа к данным для поддержки нескольких API-интерфейсов имеет смысл только в том случае, если вам это нужно сейчас. Если вы только думаете, что это может быть полезно в будущем, чем в бизнес-проектах, совершенно неправильное решение, а разработчик, который пришел с этой идеей, не компетентен принимать решения о таргетинге на бизнес.

Когда имеет смысл делать "много" абстракции?

  • У вас есть такое требование сейчас - это переносит бремя такого решения на лицо, ответственное за бюджет/объем проекта/требования и т.д.
  • Теперь вам нужна абстракция, чтобы упростить дизайн или решить какую-либо проблему.
  • Вы делаете проект с открытым исходным кодом или хобби, и вы не руководствуетесь потребностями бизнеса, а чистотой и качеством вашего проекта.
  • Вы работаете на платформе (длинный живой розничный продукт, который будет жить в течение длительного времени) или в общедоступной структуре - это обычно возвращается к первому пункту, потому что этот тип продуктов обычно имеет такую ​​абстракцию, как требование

Если вы работаете только с целевым приложением (в основном одноцелевые приложения по требованию или сторонние решения), абстракция должна использоваться только при необходимости. Эти приложения обусловлены затратами - целью является предоставление рабочего решения для минимальных затрат и в кратчайшие сроки. Эта цель должна быть достигнута, даже если результирующее приложение не будет очень хорошим внутри - единственное, что имеет значение, - это соответствие приложения требованиям. Любая абстракция, основанная на "что, если... произойдет" или "возможно, нам понадобится...", увеличивает расходы по виртуальным (не существующим) требованиям, которые в 99% никогда не произойдут, и в большинстве случаев первоначальный контракт с клиентом не учитывается которые такие дополнительные расходы.

Btw. этот тип приложений ориентирован на MS API и дизайнерскую стратегию - MS создаст множество дизайнеров и генераторов кода, которые создадут не оптимальные, но дешевые и быстрые решения, которые могут быть созданы людьми с меньшим набором навыков и очень дешевы. Последний пример - LightSwitch.