Entity Framework - Как реализовать репозиторий, используя пользовательский DbContext? - PullRequest
1 голос
/ 15 ноября 2011

Мы начинаем разрабатывать небольшую инфраструктуру в нашей компании для обмена кодом между различными приложениями.Для доступа к данным мы используем EF4.У нас есть собственный класс DbContext и универсальный репозиторий:

public class RMDbContext : DbContext
{
    // ....
}

public interface IRepository 
{
    IQueryable<T> All();
    void Delete(T entity) where T : class;
    void Add(T entity) where T : class;
    void Update(T entity) where T : class;
    int SaveChanges();
    void RollbackChanges();
}

Проблема заключается в том, как реализовать репозиторий, используя наш собственный класс DbContext (RMDbContext).Мой коллега считает, что лучше всего позволить RMDbContext реализовать интерфейс IRepository:

public class RMDbContext : DbContext, IRepository
{
    // ....
}

Если честно, мне не нравится этот подход, поскольку контекст привязан к конкретному контракту (IRepository),IMO, лучше создать реализацию репозитория, которая использует RMDbContext, что-то вроде этого:

public class Repository<T> : IRepository where T : RMDbContext, new()
{
    protected readonly RMDbContext context;

    public class Repository()
    {
         context = new T();
    }

    // ....
}

Что вы думаете об этих двух подходах?Какой бы вы выбрали и почему?

Ответы [ 4 ]

4 голосов
/ 15 ноября 2011

Лично я бы посоветовал вам, ребята, ничего не создавать, просто используйте dbContext, в нем есть все необходимые методы.

Я сам реализовал # 1 (реализацию IRepository), но в итоге вам пришлось бы заняться каким-нибудь интересным программированием, чтобы получить правильный ObjectSet или EntitySet для добавления или удаления из ваших методов Add / Delete.

Этот код будет становиться все более сложным по мере добавления иерархий наследования в вашу объектную модель.

3 голосов
/ 15 ноября 2011

На работе мы реализовали шаблон, подобный следующему:

interface ICRUD<T> : ICreatable<T>, IRetrievable<T>, IUpdatable<T>, IDeletable<T>
{
}

interface ICreatable<T>
{
    T Create();
}

interface IRetrieve<T>
{
    T Retrieve(params object[] keys);
}

interface IUpdatable<T>
{
    void Update(T existing);
}

interface ICreatable<T>
{
    void Delete(T existing);
}

И затем мы создали базовый репозиторий на основе Entity:

public abstract class BaseRepository<TModel, TEntities> where TEntities : IDbSet<TModel>
{
    protected TEntities Entities {get; set;}
    protected DbContext Db {get; set;}

    public BaseRepository (DbContext db, TEntities entities)
    {
        Db = db;
        Entities = entities;
    }

    public virtual TModel Create() { return Entities.Create (); }
    public virtual TModel Retrieve (params object[] keys) { return Entities.Find (keys); }
    public virtual void Update (TModel existing) { Db.Entry(existing).State = Modified; }
    public virtual void Delete (TModel existing) { Db.Entry(existing).State = Removed; }
}

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

2 голосов
/ 02 мая 2014

Лучшая практика EF Repositry ...
Entity Framework - подход модели Code First, создает объекты POCO для таблицы базы данных.
В этой модели могут использоваться либо данные контракта WCF, либо пользовательские атрибуты.
это мой очаровательный вариант с использованием инъекции зависимости

IRepository Interface

/// <summary>
/// Repository
/// </summary>
public partial interface IRepository<T> where T : BaseEntity
{
    /// <summary>
    /// Returns the queryable entity set for the given type {T}.
    /// </summary>
    IQueryable<T> Table { get; }

    /// <summary>
    /// Creates a new instance of an entity of type {T}
    /// </summary>
    /// <returns>The new entity instance.</returns>
    T Create();

    /// <summary>
    /// Gets an entity by id from the database or the local change tracker.
    /// </summary>
    /// <param name="id">The id of the entity. This can also be a composite key.</param>
    /// <returns>The resolved entity</returns>
    T GetById(object id);

    /// <summary>
    /// Marks the entity instance to be saved to the store.
    /// </summary>
    /// <param name="entity">An entity instance that should be saved to the database.</param>
    /// <remarks>Implementors should delegate this to the current <see cref="IDbContext" /></remarks>
    void Insert(T entity);

    /// <summary>
    /// Marks multiple entities to be saved to the store.
    /// </summary>
    /// <param name="entities">The list of entity instances to be saved to the database</param>
    /// <param name="batchSize">The number of entities to insert before saving to the database (if <see cref="AutoCommitEnabled"/> is true)</param>
    void InsertRange(IEnumerable<T> entities, int batchSize = 100);

    /// <summary>
    /// Marks the changes of an existing entity to be saved to the store.
    /// </summary>
    /// <param name="entity">An instance that should be updated in the database.</param>
    /// <remarks>Implementors should delegate this to the current <see cref="IDbContext" /></remarks>
    void Update(T entity);

    /// <summary>
    /// Marks an existing entity to be deleted from the store.
    /// </summary>
    /// <param name="entity">An entity instance that should be deleted from the database.</param>
    /// <remarks>Implementors should delegate this to the current <see cref="IDbContext" /></remarks>
    void Delete(T entity);


    /// <summary>
    /// Returns the data context associated with the repository.
    /// </summary>
    /// <remarks>
    /// The context is likely shared among multiple repository types.
    /// So committing data or changing configuration also affects other repositories. 
    /// </remarks>
    IDbContext Context { get; }

    /// <summary>
    /// Gets or sets a value indicating whether database write operations
    /// such as insert, delete or update should be committed immediately.
    /// </summary>
    bool AutoCommitEnabled { get; set; }
}

Осуществление

 /// <summary>
/// Entity Framework repository
/// </summary>
public partial class EfRepository<T> : IRepository<T> where T : BaseEntity
{

    #region Fields

    private readonly IDbContext _context;
    private IDbSet<T> _entities;

    #endregion

    #region Ctor

    public EfRepository(IDbContext context)
    {
        this._context = context;
        this.AutoCommitEnabled = true;
    }

    #endregion

    #region interface members

    public virtual IQueryable<T> Table
    {
        get
        {
            return this.Entities;
        }
    }

    public T Create()
    {
        return this.Entities.Create();
    }

    public T GetById(object id)
    {
        return this.Entities.Find(id);
    }

    public void Insert(T entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        this.Entities.Add(entity);

        if (this.AutoCommitEnabled)
            _context.SaveChanges();
    }

    public void InsertRange(IEnumerable<T> entities, int batchSize = 100)
    {
        try
        {
            if (entities == null)
                throw new ArgumentNullException("entities");

            if (entities.HasItems())
            {
                if (batchSize <= 0)
                {
                    // insert all in one step
                    entities.Each(x => this.Entities.Add(x));
                    if (this.AutoCommitEnabled)
                        _context.SaveChanges();
                }
                else
                {
                    int i = 1;
                    bool saved = false;
                    foreach (var entity in entities)
                    {
                        this.Entities.Add(entity);
                        saved = false;
                        if (i % batchSize == 0)
                        {
                            if (this.AutoCommitEnabled)
                                _context.SaveChanges();
                            i = 0;
                            saved = true;
                        }
                        i++;
                    }

                    if (!saved)
                    {
                        if (this.AutoCommitEnabled)
                            _context.SaveChanges();
                    }
                }
            }
        }
        catch (DbEntityValidationException ex)
        {
            throw ex;
        }
    }

    public void Update(T entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        if (this.AutoCommitEnabled)
        {
            _context.SaveChanges();
        }
        else
        {
            try
            {
                this.Entities.Attach(entity);
                InternalContext.Entry(entity).State = System.Data.EntityState.Modified;
            }
            finally { }
        }
    }

    public void Delete(T entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        if (InternalContext.Entry(entity).State == System.Data.EntityState.Detached)
        {
            this.Entities.Attach(entity);
        }

        this.Entities.Remove(entity);

        if (this.AutoCommitEnabled)
            _context.SaveChanges();
    }


    public IDbContext Context
    {
        get { return _context; }
    }

    public bool AutoCommitEnabled { get; set; }

    #endregion

    #region Helpers

    protected internal ObjectContextBase InternalContext
    {
        get { return _context as ObjectContextBase; }
    }

    private DbSet<T> Entities
    {
        get
        {
            if (_entities == null)
            {
                _entities = _context.Set<T>();
            }
            return _entities as DbSet<T>;
        }
    }

    #endregion

}

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

2 голосов
/ 15 ноября 2011

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

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

Репозиторий публичного класса {

protected readonly IRMDbContext context;

public class Repository(IRMDbContext rMDbContext)
{
    this.context = rMDbContext;
}

А класс Unit of Work, который создает экземпляр контекста и отправляет его в хранилище, находится по следующей ссылке

Единица работы с EF

...