簡體   English   中英

EF6 Code First具有通用存儲庫和依賴注入和SoC

[英]EF6 Code First with generic repository and Dependency Injection and SoC

經過大量閱讀並嘗試使用Entity Framework最新的穩定版本(6.1.1)。

我讀了大量關於是否要使用與庫矛盾EF6EF一般,因為它DbContext已經提供了一個資源庫和DbSetUoW ,開箱即用。

讓我首先解釋一下我的解決方案在項目方面所包含的內容,然后我將回到這個矛盾中。

它有一個類庫項目和一個asp.net-mvc項目。 類lib項目是數據訪問,並且為Code First啟用了Migrations

在我的類lib項目中,我有一個通用的存儲庫:

public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> Get();

    TEntity GetByID(object id);

    void Insert(TEntity entity);

    void Delete(object id);

    void Update(TEntity entityToUpdate);
}

以下是它的實現:

public class Repository<TEntity> where TEntity : class
{
    internal ApplicationDbContext context;
    internal DbSet<TEntity> dbSet;

    public Repository(ApplicationDbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get()
    {
        IQueryable<TEntity> query = dbSet;
        return query.ToList();
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
}

這里有幾個實體:

public DbSet<User> User{ get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<UserOrder> UserOrders { get; set; }
public DbSet<Shipment> Shipments { get; set; }

我不重復自己,但是,使用EF6你不再傳遞存儲庫,而是使用DbContext 所以對於DI我在使用Ninjectasp-net-mvc項目中設置了以下內容:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}

這將通過構造函數注入將ApplicationDbContext注入適用的上層類。

現在又回到了矛盾之中。

如果我們不再需要存儲庫,因為EF已經開箱即用,我們如何進行Separation of Concern (標題中縮寫為SoC)?

現在糾正我,如果我錯了,但聽起來像我只需要做所有數據訪問邏輯/計算(如添加,獲取,更新,刪除和一些自定義邏輯/計算(特定於實體))在asp.net-mvc項目中,如果我不添加存儲庫。

關於此事的任何啟示都非常感激。

一點點解釋有望解決你的困惑。 存儲庫模式用於抽象出數據庫連接和查詢邏輯。 ORM(對象關系映射器,如EF)已經以這樣或那樣的形式存在,以至於許多人已經忘記或者從未有過處理充斥着SQL查詢和語句的意大利面條代碼的巨大喜悅和樂趣。 時間是,如果你想查詢數據庫,你實際上負責瘋狂的事情,如啟動連接和實際從以太構造SQL語句。 存儲庫模式的重點是為您提供一個單獨的位置來放置所有這些骯臟,遠離您美麗的原始應用程序代碼。

快進到2014年,Entity Framework和其他ORM 您的存儲庫。 所有的SQL邏輯都整齊地遠離你的窺探,而你的代碼中有一個很好的編程API。 在一個方面,這是足夠的抽象。 它唯一沒有涉及的是對ORM本身的依賴。 如果您以后決定要為NHibernate甚至Web API之類的東西切換實體框架,那么您必須對您的應用程序進行手術。 因此,添加另一層抽象仍然是一個好主意,但不是一個存儲庫,或者至少讓我們說一個典型的存儲庫。

您擁有的存儲庫是典型的存儲庫。 它只是為Entity Framework API方法創建代理。 您調用repo.Add並且存儲庫調用context.Add 坦率地說,這是荒謬的,這就是為什么很多人,包括我自己,都說不要在實體框架中使用存儲庫

所以,你應該怎么做? 創建服務,或者最好稱之為“類似服務的類”。 當開始討論與.NET相關的服務時,你突然談到的是與我們在這里討論的內容完全無關的各種事情。 類似服務的類就像一個服務,它具有返回特定數據集或在某些數據集上執行非常特定功能的端點。 例如,對於典型的存儲庫,您會發現自己做的事情如下:

articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)

您的服務類的工作方式如下:

service.GetPublishedArticles();

請參閱,在“端點”方法中,所有符合“已發布文章”條件的邏輯都巧妙地包含在內。 此外,使用存儲庫,您仍然會公開底層API。 由於基礎數據存儲區是抽象的,因此更容易使用其他內容進行切換,但如果用於查詢該數據存儲區的API發生了變化,那么您仍然可以使用它。

UPDATE

設置將非常相似; 差異主要在於您如何使用服務而不是存儲庫。 也就是說,我甚至不會讓它依賴於實體。 換句話說,您基本上每個上下文都有一個服務,而不是每個實體。

一如既往,從界面開始:

public interface IService
{
    IEnumerable<Article> GetPublishedArticles();

    ...
}

然后,您的實施:

public class EntityFrameworkService<TContext> : IService
    where TContext : DbContext
{
    protected readonly TContext context;

    public EntityFrameworkService(TContext context)
    {
        this.context = context;
    }

    public IEnumerable<Article> GetPublishedArticles()
    {
        ...
    }
}

然后,事情開始變得有點毛茸茸。 在示例方法中,您可以直接引用DbSet ,即context.Articles ,但這意味着有關上下文中DbSet名稱的知識。 最好使用context.Set<TEntity>() ,以獲得更大的靈活性。 在我開火車太多之前,我想指出為什么我命名這個EntityFrameworkService 在您的代碼中,您只能引用您的IService接口。 然后,通過您的依賴注入容器,您可以替換EntityFrameworkService<YourContext> 這開啟了創建其他服務提供者的能力,例如WebApiService等。

現在,我喜歡使用一個受保護的方法,它返回一個我的所有服務方法都可以使用的查詢。 通過var dbSet = context.Set<YourEntity>();每次都必須初始化DbSet實例,這就擺脫了很多var dbSet = context.Set<YourEntity>(); 這看起來有點像:

protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = null,
    int? skip = null,
    int? take = null)
    where TEntity : class
{
    includeProperties = includeProperties ?? string.Empty;
    IQueryable<TEntity> query = context.Set<TEntity>();

    if (filter != null)
    {
        query = query.Where(filter);
    }

    foreach (var includeProperty in includeProperties.Split
        (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
    {
        query = query.Include(includeProperty);
    }

    if (orderBy != null)
    {
        query = orderBy(query);
    }

    if (skip.HasValue)
    {
        query = query.Skip(skip.Value);
    }

    if (take.HasValue)
    {
        query = query.Take(take.Value);
    }

    return query;
}

請注意,此方法首先受到保護。 子類可以使用它,但這絕對不應該是公共API的一部分。 本練習的重點是不公開可查詢。 其次,它是通用的。 換句話說,它可以處理你拋出的任何類型,只要在它的上下文中存在某些東西。

然后,在我們的小示例方法中,您最終會執行以下操作:

public IEnumerable<Article> GetPublishedArticles()
{
    return GetQueryable<Article>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}

這種方法的另一個巧妙技巧是能夠使用接口的通用服務方法。 假設我希望能夠有一種方法來發布任何內容 我可以有一個像這樣的界面:

public interface IPublishable
{
    PublishStatus Status { get; set; }
    DateTime PublishDate { get; set; }
}

然后,任何可發布的實體都將實現此接口。 有了這個,你現在可以做:

public IEnumerable<TEntity> GetPublished<TEntity>()
    where TEntity : IPublishable
{
    return GetQueryable<TEntity>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}

然后在您的應用程序代碼中:

service.GetPublished<Article>();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM