Обертывание DbSet <TEntity>с помощью специального DbSet / IDbSet? - PullRequest
17 голосов
/ 15 сентября 2011

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

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

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

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

Сначала у нас есть «Общая» библиотека всех интерфейсов, мы назовем ее «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; }
    }
}

Контекст:

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 Ответ

45 голосов
/ 15 сентября 2011

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

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

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

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

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...