Общий шаблон хранилища Entity Framework

Мысль 1

public interface IRepository<T> : IDisposable where T : class
        {
            IQueryable<T> Fetch();        
            IEnumerable<T> GetAll();        
            IEnumerable<T> Find(Func<T, bool> predicate);        
            T Single(Func<T, bool> predicate);        
            T First(Func<T, bool> predicate);        
            void Add(T entity);        
            void Delete(T entity);        
            void SaveChanges();        
        }

с приведенным выше подходом я поеду, как репозиторий, а затем создаю репозиторий с реализацией описанных выше методов.

Я думаю, что это освобождает мои тесты от зависимости от DbContext.

Мысль 2

 public class RepositoryBase<TContext> : IDisposable where TContext : DbContext, new()
    {
            private TContext _DataContext;

            protected virtual TContext DataContext
            {
                get
                {
                    if (_DataContext == null)
                    {
                        _DataContext = new TContext();
                    }
                    return _DataContext;
                }
            }

            public virtual IQueryable<T> GetAll<T>() where T : class
            {                   
                using (DataContext)
                    {
                        return DataContext.Set<T>();
                    }                   
            }

            public virtual T FindSingleBy<T>(Expression<Func<T, bool>> predicate) where T : class
            {
                if (predicate != null)
                {
                    using (DataContext)
                    {
                        return DataContext.Set<T>().Where(predicate).SingleOrDefault();
                    }
                }
                else
                {
                    throw new ArgumentNullException("Predicate value must be passed to FindSingleBy<T>.");
                }
            }

            public virtual IQueryable<T> FindAllBy<T>(Expression<Func<T, bool>> predicate) where T : class 
            {
                if (predicate != null)
                {
                    using (DataContext)
                    {
                        return DataContext.Set<T>().Where(predicate);
                    }
                }
                else
                {
                    throw new ArgumentNullException("Predicate value must be passed to FindAllBy<T>.");
                }
            }

            public virtual IQueryable<T> FindBy<T, TKey>(Expression<Func<T, bool>> predicate,Expression<Func<T, TKey>> orderBy) where T : class 
            {
                if (predicate != null)
                    {
                        if (orderBy != null)
                        {
                            using (DataContext)
                            {
                                return FindAllBy<T>(predicate).OrderBy(orderBy).AsQueryable<T>(); ;
                            }
                        }
                        else
                        {
                            throw new ArgumentNullException("OrderBy value must be passed to FindBy<T,TKey>.");
                        }
                    }
                else
                    {
                            throw new ArgumentNullException("Predicate value must be passed to FindBy<T,TKey>.");
                    }
            }

            public virtual int Save<T>(T Entity) where T : class
            {
                return DataContext.SaveChanges();
            }    
            public virtual int Update<T>(T Entity) where T : class
            {
                return DataContext.SaveChanges();
            }    
            public virtual int Delete<T>(T entity) where T : class
            {
                DataContext.Set<T>().Remove(entity);
                return DataContext.SaveChanges();
            }        
            public void Dispose()
            {
                if (DataContext != null) DataContext.Dispose();
            }
    }

Теперь я мог бы иметь CustomerRepository : RepositoryBase<Mycontext>,ICustomerRepository ICustomerRepository дает мне возможность определять такие вещи, как GetCustomerWithOrders и т. д. ...

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

32 голоса | спросил ashutosh raina 27 22012vEurope/Moscow11bEurope/MoscowTue, 27 Nov 2012 00:58:03 +0400 2012, 00:58:03

2 ответа


25

Вы должны использовать Expression<Func<T, bool>> как предикаты на вашем интерфейсе и реализовать этот интерфейс RepositoryBase. Используя Func, вы не получите перевод на L2E и т. Д., Но вам нужно будет перечислить всю таблицу БД, прежде чем вы сможете оценить Func. Интерфейс можно издеваться, следовательно, тестировать его без физического db, а также использовать с другими ORM.

Обычно рекомендуется сохранять SaveChanges и DbContext в отдельной реализации IUnitOfWork. Вы можете передать UnitOfWork в конструктор репозитория, который может получить доступ к внутреннему свойству в UOW, отображая DbContext.

Таким образом вы можете использовать один и тот же DbContext между репозиториями и пакетные обновления для нескольких корней сущностей другого типа. У вас также остается меньше открытых подключений, которые являются самым дорогим ресурсом, который у вас есть, когда дело доходит до БД.

Не забывайте, что они могут обмениваться транзакциями. :)

Следовательно, вы не должны размещать DbContext в репозитории, репозиторий действительно не должен быть одноразовым вообще. Но UnitOfWork /DbContext должен быть удален кем-то.

Кроме того, поставьте предикат OrderBy на FindBy. Поскольку вы возвращаете IQueryable и используете Expression для предиката, вы можете продолжить создание Queryable после вызова. Например, repo.FindBy(it => it.Something == something).OrderBy(it => it.OrderProperty). Он по-прежнему будет переведен на «select [fields] from [table], где [предикат] упорядочивается [orderprop]» при перечислении.

В противном случае это выглядит хорошо.

Вот несколько хороших примеров:

http://www.martinwilley.com/net/code/data/genericrepository.html

http: //www.mattdurrant. ком /эф-кода первого-с-хранилище-и-блок-оф-работы-шаблоны /

И вот как я это делаю:

Модель /Общие /Бизнес-сборка

Нет ссылок, кроме BCL и других возможных моделей (интерфейсы /dtos)

public interface IUnitOfWork : IDisposable
{
    void Commit();
}

public interface IRepository<T>
{
    void Add(T item);
    void Remove(T item);
    IQueryable<T> Query();
}

// entity classes

Сбор бизнес-тестов

Только ссылки Модель /Общие /Бизнес-сборка

[TestFixture]
public class BusinessTests
{
    private IRepository<Entity> repo;
    private ConcreteService service;

    [SetUp]
    public void SetUp()
    {
        repo = MockRepository.GenerateStub<IRepository<Entity>>();
        service = new ConcreteService(repo);
    }

    [Test]
    public void Service_DoSomething_DoesSomething()
    {
        var expectedName = "after";
        var entity = new Entity { Name = "before" };
        var list = new List<Entity> { entity };
        repo.Stub(r => r.Query()).Return(list.AsQueryable());
        service.DoStuff();
        Assert.AreEqual(expectedName, entity.Name);
    }

}

Сборка реализации платформы Entity Framework

Ссылки Model и сборки Entity Framework /System.Data.Entity

public class EFUnitOfWork : IUnitOfWork
{
    private readonly DbContext context;

    public EFUnitOfWork(DbContext context)
    {
        this.context = context;
    }

    internal DbSet<T> GetDbSet<T>()
        where T : class
    {
        return context.Set<T>();
    }

    public void Commit()
    {
        context.SaveChanges();
    }

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

public class EFRepository<T> : IRepository<T>
        where T : class
{
    private readonly DbSet<T> dbSet;

    public EFRepository(IUnitOfWork unitOfWork)
    {
        var efUnitOfWork = unitOfWork as EFUnitOfWork;
        if (efUnitOfWork == null) throw new Exception("Must be EFUnitOfWork"); // TODO: Typed exception
        dbSet = efUnitOfWork.GetDbSet<T>();
    }

    public void Add(T item)
    {
        dbSet.Add(item);
    }

    public void Remove(T item)
    {
        dbSet.Remove(item);
    }

    public IQueryable<T> Query()
    {
        return dbSet;
    }
}

Встроенная сборка тестов

Все ссылки

[TestFixture]
[Category("Integrated")]
public class IntegratedTest
{
    private EFUnitOfWork uow;
    private EFRepository<Entity> repo;

    [SetUp]
    public void SetUp()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<YourContext>());
        uow = new EFUnitOfWork(new YourContext());
        repo = new EFRepository<Entity>(uow);
    }

    [TearDown]
    public void TearDown()
    {
        uow.Dispose();
    }

    [Test]
    public void Repository_Add_AddsItem()
    {
        var expected = new Entity { Name = "Test" };
        repo.Add(expected);
        uow.Commit();
        var actual = repo.Query().FirstOrDefault(e => e.Name == "Test");
        Assert.IsNotNull(actual);
    }

    [Test]
    public void Repository_Remove_RemovesItem()
    {
        var expected = new Entity { Name = "Test" };
        repo.Add(expected);
        uow.Commit();
        repo.Remove(expected);
        uow.Commit();
        Assert.AreEqual(0, repo.Query().Count());
    }
}
ответил Simon Forsberg 5 72017vEurope/Moscow11bEurope/MoscowSun, 05 Nov 2017 18:26:56 +0300 2017, 18:26:56
6

Переосмысливание колеса

Если вам нужен общий класс репозитория, используйте IObjectSet для базового типа и при необходимости его реализуйте (в производственном коде это не нужно, в тестах вы можете использовать IList> в качестве backend).

Лук-порей на абстракции

Хранилища не должны иметь метод сохранения, если кто-то еще находится в транзакционном моменте в общем контексте, тогда вы (или другой парень) можете обмануть друг друга. Для этого создайте независимый IUnitOfWork.

interface IUnitOfWork : IDisposable {
    void Commit();
    void Rollback();
}

interface IDataStore {
    IUnitOfWork InUnitOfWork();
    IObjectSet<T> Set<T>() where T : class;
}

using (var uow = _data.InUnitOfWork()) {
    _data.Set<Order>.AddObject(order);
    _data.Set<SomethingElse>.Remove(otherObject);

   uow.Comit();
}

Сложная часть - это то, как вы обрабатываете транзакцию. Я создал интерфейс ISession с некоторыми методами (BeginTransaction (), InTransaction, Commit (), Rollback ()) и создал реализацию по умолчанию. В моей реализации по умолчанию IUnitOfWork конструктор получает экземпляр ISession и начинает с ним транзакцию. Реализация ISession имеет внутренний счетчик, чтобы подсчитать, сколько других участников начали свою собственную транзакцию. Когда IUnitOfWork из общего хранилища Записанный счетчик уменьшается на 1, если кто-то говорит RollBack, тогда все откатывается.

РЕДАКТИРОВАТЬ (03 декабря)

Исправлено поведение IUnitOfWork Dispose: по умолчанию используется метод Rollback () not Commit ()

Таким образом, вы можете легко проверить свой код.

ответил Peter Kiss 1 SatEurope/Moscow2012-12-01T23:42:32+04:00Europe/Moscow12bEurope/MoscowSat, 01 Dec 2012 23:42:32 +0400 2012, 23:42:32

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132