Абстрагируйте DAL от реализации Entity Framework

Прежде всего, извините, если это будет длинный пост, но я не знаю, как правильно объяснить проблему без необходимых деталей.

У меня проблемы с поиском способа абстрагировать мой DAL от реализации Entity Framework. Проект, над которым я работаю, очень мал, но если в будущем я захочу переключиться на другой ORM, такой как NHibernate, или просто на ADO.NET, я бы хотел написать код только для реализации, а не весь DAL .

Скажем, у меня есть эти объекты в моем MyWallet.DAL:

public interface IWallet {
    long Id { get; set; }
    float TotalAmountOfMoney { get; set; }
    long CurrencyId { get; set; }
    ICurrency Currency { get; set; }
    DateTime RecordedOn { get; set; }
    ICollection<IMoneyMovement> MoneyMovements { get; set; }
}

public interface ICurrency {
    long Id { get; set; }
    char Symbol { get; set; }
    string Code { get; set; }
    string Description { get; set; }
}

public interface IMoneyMovement {
    long Id { get; set; }
    float Amount { get; set; }
    string Description { get; set; }
    long WalletId { get; set; }
    IWallet Wallet { get; set; }
    DateTime RecordedOn { get; set; }
    DateTime MovedOn { get; set; }
}

Как видите, это простые интерфейсы, которые я планирую реализовать в другой библиотеке, которая будет содержать фактическую реализацию Entity Framework (скажем, MyWallet.DAL.EntityFramework). Конечно, я собираюсь украсить реализацию сущностей специфическими атрибутами Entity Framework, такими как [Key] или [ForeignKey], и тому подобное.

Я также определил некоторый репозиторий в MyWallet.DAL, например IWalletRepository, IMoneyMovementRepository, ICurrencyRepository, чтобы получить доступ к объектам. На самом деле я не знаю, правильный ли это способ создания доступа к сущностям. Конечно, я также определил фабрики, чтобы получить конкретную реализацию сущностей.

На своем бизнес-уровне я определил службы для обработки запроса объекта, работы с сущностями DAL и возврата бизнес-объекта, например:

public class WalletService {
    private readonly IWalletRepository _walletRepository;
    private readonly IWalletFactory _walletFactory;

    public WalletService(IWalletRepository walletRepository, 
        IWalletFactory walletFactory) {

        _walletRepository = walletRepository;
        _walletFactory = walletFactory;
    }

    public CreatedWallet CreateWallet(CreateWalletRequest request) {
        var wallet = _walletFactory.Create();

        wallet.CurrencyId = request.CurrencyId;
        wallet.TotalAmountOfMoney = request.TotalAmountOfMoney;
        wallet.RecordedOn = DateTime.Now;

        _walletRepository.Create(wallet);
        _walletRepository.SaveChanges();

        return new CreatedWallet {
            Id = wallet.Id
        }
    }
}

Я думал, что это будет работать без проблем, или в худшем случае - в ситуации, когда у меня более одного репозитория - я мог бы поделиться DataContext, поэтому мне нужно было бы запустить метод SaveChanges только на одном, чтобы отразить изменения в база данных.

Проблема в реализации репозитория, в этом случае я продолжу использовать Entity Framework:

public class EFDataContext : DbContext {
    public EFDataContext() : base ("name=MyConnectionString") {
    }

    public virtual DbSet<EFWallet> Wallets { get; set; }
    public virtual DbSet<EFMoneyMovement> MoneyMovements { get; set; }
    public virtual DbSet<EFCurrency> Currencies { get; set; }
}

public class EFWalletRepository : IWalletRepository {
    private readonly EFDbContext _dataContext;

    public EFWalletRepository(EFDbContext dataContext) {
        _dataContext = dataContext ?? new EFDbContext();
    }

    public int SaveChanges() {
        return _dataContext.SaveChanges();
    }

    public void Dispose() {
        _dataContext.Dispose();
    }

    public void Create(IWallet wallet) {
        ...???
    }
}

Вот в чем проблема: как мне работать с интерфейсами, когда DataContext знает только о конкретных реализациях? Я все делаю неправильно?

ОБНОВЛЕНИЕ:

Хорошо, в общем, как заявил @TomTom, зачем бороться с Entity Framework, если можно просто воспользоваться его мощью? Думаю, я просто позволю EF быть абстракцией. Фактически, позволяя EF действовать как DAL, вы можете просто сосредоточиться на бизнес-логике вашего проекта.

И чтобы собрать все это воедино и ответить на @tdragon относительно проблемы репозиториев / единицы работы: да, я мог бы либо обернуть несколько репозиториев внутри единицы работы, либо просто позволить DbContext быть единицей работы:

public class EFWalletRepository : IWalletRepository {
    private readonly EFDbContext _dataContext;

    public EFWalletRepository() {
        _dataContext = new EFDbContext();
    }

    public void Dispose() {
        _dataContext.Dispose();
    }

    public IEnumerable<Wallet> Wallets {
        get { return _dataContext.Wallets; }
    }

    public void SaveWallet(Wallet wallet) {
        if (wallet.Id == 0) {
            _dataContext.Wallets.Add(wallet);
        } else {
            var databaseEntry = _dataContext.Wallets.Find(wallet.Id);
            //update properties
        }

        _dataContext.SaveChanges();
    }
}

person lucacelenza    schedule 08.03.2016    source источник
comment
Вы также абстрагируете данные; вы должны только абстрактную логику. Сохраняйте POCO на бизнес-уровне.   -  person Maarten    schedule 08.03.2016


Ответы (2)


Проще говоря: да, вы делаете это неправильно. Вы вводите плохую абстракцию (которая дорого обходится вам в функциональности) «из-за». EF уже является абстракцией.

Любая абстракция поверх нее будет стоить вам с точки зрения используемой функциональности, что с точки зрения баз данных оказывает большое влияние на производительность. Хотите пример? «Включить» для предварительной загрузки свойств навигации (вместо ленивой загрузки). Вам придется обойти это и многое другое, более подробное поведение, специфичное для ORM, - чтобы получить что? И если вы откажетесь от этих более высоких, более специфических функций, ваша производительность БУДЕТ страдать.

person TomTom    schedule 08.03.2016
comment
Я думаю, это просто зависит от его требований. Я бы не стал рассказывать об этих функциях, пока они мне не понадобятся YAGNI. Главный вопрос: где находится самая большая ценность бизнеса? Имея более простой способ изменить базы данных или иметь более легкое открытое окно для функций EF. EF - это лишь половина абстракции, поскольку она сильно зависит от самой технологии EF. По умолчанию это неплохо, но это зависимость. Я думаю, что оба пути возможны и должны быть проверены на соответствие требованиям. - person Boas Enkler; 08.03.2016
comment
Я так не считаю. в моем случае мне никогда не требовалась эта функция EF и, скорее всего, они мне никогда не понадобятся. Если эта функция очевидна, ваше право. но так ли они в его случае? Но ваш грубый пост кажется мне немного странным и не очень профессиональным ... Очень плохо реализовывать все, просто опасаясь, что, может быть, через 10 лет мне это может понадобиться. Если вы знаете, что он вам понадобится, тогда реализуйте его. Но вы не можете знать, не зная его контекста. Лучше посоветовать подумать, очень ли ему понадобится эта функция, или спросить у бизнесмена! - person Boas Enkler; 08.03.2016
comment
Хорошо, возможно, мне следовало сказать об этом раньше, но этот проект, над которым я работаю, - это просто домашнее задание, просто способ улучшить дизайнерские навыки, создавая что-то с очень маленькой отправной точки. Конечно, этому проекту совсем не нужна такая абстракция, более того, в этом случае, когда EF является абстракцией как таковой, даже если не так, как я представляю себе абстракцию. - person lucacelenza; 08.03.2016
comment
@BoasEnkler Что ж, Боас, это, скорее всего, пример некомпетентности, потому что я каким-то образом использую эту особенность в более чем 50% своих запросов. Итак, либо я работаю только с ОЧЕНЬ сложными запросами, либо вы попадаете в ловушку, которую я видел в ТОННЕ проектных команд: люди не знают, что они делают, а затем удивляются, когда кто-то показывает им надлежащую производительность. Кажется, есть МНОГО базовых функций, которые вы просто никогда не используете. Что ж, приятно для вас. Некоторые из нас ДОЛЖНЫ работать профессионально, потому что этого требует приложение. - person TomTom; 08.03.2016

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

Но если вы хотите сохранить это таким образом, вы можете сделать свои интерфейсы репозитория универсальными и передать конкретный тип объекта при определении репозитория, чтобы в итоге вы получили:

public class EFWalletRepository : IWalletRepository<EFWallet>
{
    public void Create(EFWallet wallet)
    {
        _dataContext.Add(wallet);
    }
}

Другие предложения:

  1. Вы не должны предоставлять наборы свойств вашей модели. Это противоречит правилам ООП - вам лучше предоставить некоторые методы для управления объектами, состояние должно быть более внутренним.
  2. Вероятно, вам не следует добавлять SaveChanges() метод в свой репозиторий - это должна быть «единица работы» для фиксации всех изменений в базе данных.
  3. Вы столкнетесь с проблемой, если будете использовать более одного репозитория на уровне обслуживания, когда вы создаете новый DbContext репозиторий, тогда как у вас должен быть один репозиторий для одной «единицы работы».
person tdragon    schedule 08.03.2016
comment
Я абстрагирую сущности, потому что у EF есть собственные декораторы, которые я не хотел включать в свою библиотеку DAL. [Table("wallet")] public class Wallet { [Key] [Column("id")] public long Id { get; set; } .... } - person lucacelenza; 09.03.2016
comment
Вместо использования атрибутов для украшения ваших объектов вы можете использовать файлы конфигурации. Тогда ваши сущности останутся чистыми и свободными от EF, см. Здесь: entityframeworktutorial.net/code-first/ - person tdragon; 09.03.2016