[英]EF6 Code First with generic repository and Dependency Injection and SoC
經過大量閱讀並嘗試使用Entity Framework
最新的穩定版本(6.1.1)。
我讀了大量關於是否要使用與庫矛盾EF6
或EF
一般,因為它DbContext
已經提供了一個資源庫和DbSet
的UoW
,開箱即用。
讓我首先解釋一下我的解決方案在項目方面所包含的內容,然后我將回到這個矛盾中。
它有一個類庫項目和一個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
我在使用Ninject
的asp-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.