gpt4 book ai didi

c# - EF6 Code First 具有通用存储库和依赖注入(inject)和 SoC

转载 作者:IT王子 更新时间:2023-10-29 04:18:55 25 4
gpt4 key购买 nike

经过大量阅读和尝试后 Entity Framework最新的稳定版本 (6.1.1)。

我正在阅读很多关于是否使用 EF6 的存储库的矛盾。或 EF一般来说,因为它是 DbContext已经提供了一个存储库和 DbSet UoW , 盒子外面。

让我首先解释我的解决方案在项目方面包含的内容,然后我将回到矛盾。

它有一个类库项目,还有一个 asp.net-mvc项目。类 lib 项目是数据访问,其中 MigrationsCode First 启用.

在我的类 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我在 asp-net-mvc 中设置了以下内容项目使用 Ninject :
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}

这将注入(inject) ApplicationDbContext在适用的情况下,通过构造函数注入(inject)到上层类。

现在回到矛盾。

如果我们不再需要存储库,因为 EF已经提供了开箱即用的,我们怎么做 Separation of Concern (在标题中缩写为 SoC)?

现在纠正我,如果我错了,但在我看来我只需要做所有的数据访问逻辑/计算(比如添加、获取、更新、删除和一些自定义逻辑/计算在这里和那里(特定于实体))在 asp.net-mvc项目,如果我不添加存储库。

对此问题的任何了解都非常感谢。

最佳答案

一些解释有望消除您的困惑。存储库模式的存在是为了抽象出数据库连接和查询逻辑。 ORM(对象关系映射器,如 EF)已经以一种或另一种形式存在了很长时间,以至于许多人已经忘记或从未享受过处理散布着 SQL 查询和语句的意大利面条式代码的巨大乐趣和乐趣。时间是,如果你想查询一个数据库,你实际上要负责一些疯狂的事情,比如启动一个连接和实际从以太构建 SQL 语句。存储库模式的重点是给你一个地方来放置所有这些肮脏的东西,远离你美丽的原始应用程序代码。

快进到 2014 年, Entity Framework 和其他 ORM 是您的存储库。所有的 SQL 逻辑都被整齐地打包,远离你的窥探,取而代之的是你有一个很好的编程 API 可以在你的代码中使用。一方面,这已经足够抽象了。它唯一没有涵盖的是对 ORM 本身的依赖。如果您后来决定要为 NHibernate 甚至 Web API 之类的东西切换出 Entity Framework ,则必须对应用程序进行手术才能这样做。因此,添加另一层抽象仍然是一个好主意,但不是存储库,或者至少可以说是典型的存储库。

您拥有的存储库是一个典型的存储库。它只是为 Entity Framework API 方法创建代理。您拨打 repo.Add并且存储库调用 context.Add .坦率地说,这是荒谬的,这就是为什么包括我自己在内的许多人说不要将存储库与 Entity Framework 一起使用。

那你该怎么办?创建服务,或者最好将其称为“类服务类”。当开始讨论与 .NET 相关的服务时,突然间你在谈论与我们在这里讨论的内容完全无关的各种事情。类服务类类似于服务,因为它具有返回特定数据集或对某些数据集执行非常特定功能的端点。例如,对于典型的存储库,您会发现自己在做以下事情:

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

您的服务类的工作方式如下:
service.GetPublishedArticles();

看,所有符合“已发表文章”条件的逻辑都整齐地包含在端点方法中。此外,使用存储库,您仍然会暴露底层 API。因为基本数据存储是抽象的,所以切换到其他东西更容易,但是如果用于查询该数据存储的 API 发生变化,您仍然会陷入困境。

更新

设置将非常相似;区别主要在于您如何使用服务与存储库。也就是说,我什至不会让它依赖于实体。换句话说,您基本上每个上下文都有一个服务,而不是每个实体。

和往常一样,从一个接口(interface)开始:
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界面。然后,通过您的依赖注入(inject)容器,您可以替换 EntityFrameworkService<YourContext>为了那个原因。这开启了创建其他服务提供者的能力,比如也许 WebApiService , 等等。

现在,我喜欢使用单个 protected 方法,该方法返回我所有服务方法都可以使用的可查询对象。这摆脱了很多麻烦,比如必须初始化 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;
}

请注意,此方法首先是 protected 。子类可以使用它,但这绝对不应该是公共(public) API 的一部分。本练习的重点是不公开可查询。其次,它是通用的。换句话说,它可以处理你扔给它的任何类型,只要它的上下文中有一些东西。

然后,在我们的小示例方法中,您最终会执行以下操作:
public IEnumerable<Article> GetPublishedArticles()
{
return GetQueryable<Article>(
m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
m => m.OrderByDescending(o => o.PublishDate)
).ToList();
}

这种方法的另一个巧妙技巧是使用接口(interface)的通用服务方法的能力。假设我希望能够有一种方法来发布任何内容。我可以有一个界面,如:
public interface IPublishable
{
PublishStatus Status { get; set; }
DateTime PublishDate { get; set; }
}

然后,任何可发布的实体都将实现此接口(interface)。有了它,您现在可以执行以下操作:
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>();

关于c# - EF6 Code First 具有通用存储库和依赖注入(inject)和 SoC,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27527757/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com