首先,使用实体框架代码抽象数据访问层

发布于 2025-02-06 11:50:38 字数 1067 浏览 2 评论 0 原文

我有两个单独的数据库。数据库A很小,包含6个表和一些存储的过程,数据库B较大,并且包含52个表,159个视图和一些存储过程。我的任务是创建一个在这两个数据库上进行操作的应用程序。已经编写了访问这些数据库的代码,但是这是不可测试的,我想从头开始创建DAL代码。因此,我建立了一个代码第一实体框架连接,并让它为我以及上下文类创建所有POCOS。现在我有3个DataBaseContext类。 1对于数据库A,仅是6个单独的表。 1对于数据库B的表和1的表,对于数据库B的视图。我将数据库B的上下文类分为两个单独的上下文类,因为表和视图在2个不同的方案中使用。

我已经在一个解决方案中创建了2个项目。 domainModel和dataAccesslayer。 DomainModel项目持有实体框架代码首先生成的所有POCO。 dataAccesslayer是DataBaseContexts所在的位置,也是我想创建其他项目可以用来访问数据库的代码的位置。我的目标是制作可重复使用的可重复测试数据访问层代码。

问题是:我在DataAccesslayer项目中如何创建什么,以使DAL在其他希望能够抽象的方式与数据库交谈的项目中可以解析,什么是构造它的最佳方法是什么?

我做了什么: 在我在Internet上的搜索中,我已经看到人们推荐存储库模式(有时使用Unitofwork),这似乎是可以进行的,因此我创建了一个称为IgenericRepository的界面和一个称为generentityframeworkerworkitory的实现。该接口具有诸如GetByID,插入,删除和更新之类的方法。实现将DBContext作为构造函数中的参数,并实现所有方法。下一步可能是为不同的表和视图创建特定的实现,但这将是一件非常乏味的事情,因为有很多表格和视图。 我还读到实体框架是DAL。然后,创建另一个拥有业务逻辑并返回Ienumerables的项目可能就足够了。但这似乎很难测试。

我正在尝试实现的是一个结构良好且可测试的基础,可以访问我们的数据库,随着其他项目开始需要数据库的其他功能,可以进行彻底测试并扩展。

该项目的文件夹结构可以在以下图片中看到:

I have two separate databases. Database A is small and contains 6 tables and some stored procedures, database B is larger and contains 52 tables, 159 views, and some stored procedures. I have been tasked with creating an application that does operations on both of these databases. There is already code that has been written that accesses these databases but it is not testable and I would like to create the DAL code from scratch. So I made a code first Entity Framework connection and let it create all of the POCOs for me as well as the Context classes. Now I have 3 DatabaseContext classes. 1 for database A which is just the 6 separate tables. 1 for the tables from database B and 1 for the views from database B. I split the Context class for database B into 2 separate context classes because the tables and the views are used in 2 different scenarios.

I have created 2 projects in a single solution. DomainModel and DataAccessLayer. The DomainModel project holds all of the POCOs which the Entity Framework code first generated for me. DataAccessLayer is where the DatabaseContexts are and where I would like to create the code which other projects can use to access the database. My goal being to make a reusable and testable data access layer code.

The QUESTION is: What do I create in the DataAccessLayer project to make the DAL resusable in other projects that want to be able to talk to the database in an abstract way and what is the best way to structure it?

What I have done:
In my search on the internet I have seen that people recommend the repository pattern (sometimes with UnitOfWork) and it seems like that is the way to go, so I created an interface called IGenericRepository and an implementation called GenericEntityFrameworkRepository. The interface has methods such as GetByID, Insert, Delete, and Update. The implementation takes a DbContext as a parameter in the constructor and implements all of the methods. The next step might be to create specific implementations for the different tables and views but that is going to be a very tedious thing to do since there are so many tables and views.
I have also read that Entity Framework IS the DAL. Then it might be enough to just create another project that holds the business logic and returns IEnumerables. But that seems like it will be hard to test.

What I am trying to achieve is a well structured and testable foundation to access our database that can be tested thoroughly and expanded as other projects start to require other functionality to the database.

The folder structure for the project can be seen in the following picture:

Project folder structure.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

眼眸里的快感 2025-02-13 11:50:39

设计和架构都是关于权衡的。要考虑为重用目的抽象数据访问时,请小心。在应用程序中,这会导致过度复杂且性能不佳的解决方案,可以是一个非常简单且快速的解决方案。

例如,如果我想构建一个业务域层作为一组服务集,该服务将被许多不同的应用程序(将为移动应用程序或第三方兴趣的Web应用程序和服务/API)消费这定义了将会输入哪些信息,以及将出现哪些信息。进出该抽象的所有内容都应该是反映我通过我提供的操作提供的域的DTO。在该边界内,应利用EF来执行产生相关DTO或基于传递的具体详细信息更新实体所需的投影。(经过验证后)权衡取舍,每个消费者都需要与此通用相关访问点,因此,除非您构建它,否则几乎没有自定义的空间来自定义发生的东西。这比让所有事情都可以访问数据库并运行自定义查询或调用存储过程。这是可取的。

我最经常看到的是,在单个解决方案中,开发人员希望抽象系统不“知道”数据域是由EF提供​​的。该应用程序通过POCO实体或DTO进行通过,并且它们实现了使用GetByID,Getall,Update,UpSert等的通用存储库等的事情。这是一个非常非常非常糟糕的设计决定,因为这意味着您的数据域无法利用诸如投影,投影,,分页,自定义过滤,订购,急切的加载相关数据等。抽象EF DBContext无疑是启用单元测试的有效目标,但我强烈建议您避免使用通用存储库模式的性能陷阱。 IMO只是为了隐藏或使其可交换而试图抽象EF是表现不佳和/或过于复杂的混乱。

以典型的getall()方法为例。

public IEnumerable<T> GetAll()
{
    return _context.Set<T>().ToList();
}

如果您只想过去三个月的订单,会发生什么?

var orders = orderRepository.GetAll()
    .Where(x => x.OrderDate >= startDate).ToList();

这里的问题是getall()将始终返回所有行。如果您有400万个订单,您可以欣赏这是不可取的。

您可以制作一个订单重新设备,该订单将扩展存储库来实现诸如getordersfter(dateTime)之类的方法,但是很快,通用存储库的重点是什么?下一个选项是传递 expression&lt; func&lt; tentity&gt;&gt; ,其中sar in the getall()方法。那里有很多例子。但是,这样做是将泄漏到消费代码中。其中的子句表达式必须符合EF规则。例如通过 order =&gt; order.someunmapperproperty == someValue 将导致查询失败,因为EF无法将其转换为SQL。即使您对getall方法也需要开始看起来像:

IEnumerable<TEntity> GetAll(Expression<Func<TEntity>> whereClause = null, 
    IEnumerable<OrderByClause> orderByClauses = null, 
    IEnumerable<string> includes = null, 
    int? pageNumber = null, int? pageSize = null )

或某些类似的怪兽来处理过滤,订购,急切的加载和分页,这甚至无法涵盖投影。为此,您最终会采用其他方法,例如:

IEnumerable<TEntity, TDTO> GetAll(Expression<Func<TEntity>> whereClause = null, 
    IEnumerable<OrderByClause> orderByClauses = null, 
    IEnumerable<string> includes = null, 
    int? pageNumber = null, int? pageSize = null )

将调用另一个getall,然后执行映射器。映射以返回所需的DTO/ViewModel,希望在映射依赖项中注册。然后,您需要考虑 async 与该方法的同步口味。 (或强迫所有呼叫是同步的或 async

总的来说,我的建议是避免使用通用的存储库模式,而不只是为了抽象而抽象EF,而是寻找方法来利用最多的方法您可以摆脱它。开发人员的大多数问题都遇到了EF的性能和奇特的行为,源于使其抽象的努力。他们非常担心需要将其抽象为能够在需要的情况下取代它,以至于他们完全遇到了这些表现和复杂性问题,这完全是由于抽象施加的局限性。 (一个自我实现的预言)当您试图过早地优化解决您当前没有的问题的解决方案时,“体系结构”可能是一个肮脏的词,并且可能永远不会面对。始终保持Yagni并亲吻所有设计注意事项的最前沿。

Design and architecture is all about trade-offs. Be careful when you want to consider abstracting your data access for purposes of reuse. Within an application this leads to overly complex and poorly performing solutions to what can be a very simple and fast solution.

For instance, if I want to build a business domain layer as a set of services to be consumed by a number of different applications (web applications and services/APIs that will serve mobile apps or third party interests) then that domain layer becomes a boundary that defines what information will come in, and what information will go out. Everything in and out of that abstraction should be DTOs that reflect the domain that I am making available via the actions I make available. Within that boundary, EF should be leveraged to perform the projection needed to produce the relevant DTOs, or update entities based on the specific details passed in. (After verification) The trade-off is that every consumer will need to make due with this common access point, so there is little wiggle room to customize what comes out unless you build it in. This is preferable to having everything just do their own thing accessing the database and running custom queries or calling Stored Procedures.

What I see most often though is that within a single solution, developers want to abstract the system not to "know" that the data domain is provided by EF. The application passes around POCO Entities or DTOs and they implement things like Generic Repositories with GetById, GetAll, Update, Upsert, etc. This is a very, very poor design decision because it means that your data domain cannot take advantage of things like projection, pagination, custom filtering, ordering, eager loading related data, etc. Abstracting the EF DbContext is certainly a valid objective for enabling unit testing, but I strongly recommend avoiding the performance pitfall of a Generic Repository pattern. IMO attempting to abstract away EF just for the sake of hiding it or making it swappable is either a poor performing and/or an overly complex mess.

Take for example a typical GetAll() method.

public IEnumerable<T> GetAll()
{
    return _context.Set<T>().ToList();
}

What happens if you only want orders from the last 3 months?

var orders = orderRepository.GetAll()
    .Where(x => x.OrderDate >= startDate).ToList();

The issue here is that GetAll() will always return all rows. If you have 4 million orders you can appreciate that is not desirable.

You could make an OrderRepository that extends Repository to implement a method like GetOrdersAfter(DateTime) but soon, what is the point of the Generic Repository? The next option would be to pass something like an Expression<Func<TEntity>> Where clause into the GetAll() method. There are plenty of examples of that out there. However, doing that is leaking EF-isms into the consuming code. The Where clause expression has to conform to EF rules. For instance passing order => order.SomeUnmappedProperty == someValue would cause the query to fail as EF won't be able to convert that down to SQL. Even if you're Ok with that the GetAll method will need to start looking like:

IEnumerable<TEntity> GetAll(Expression<Func<TEntity>> whereClause = null, 
    IEnumerable<OrderByClause> orderByClauses = null, 
    IEnumerable<string> includes = null, 
    int? pageNumber = null, int? pageSize = null )

or some similar monstrosity to handle filtering, ordering, eager loading, and pagination, and this doesn't even cover projection. For that you end up with additional methods like:

IEnumerable<TEntity, TDTO> GetAll(Expression<Func<TEntity>> whereClause = null, 
    IEnumerable<OrderByClause> orderByClauses = null, 
    IEnumerable<string> includes = null, 
    int? pageNumber = null, int? pageSize = null )

which will call the other GetAll then perform a Mapper.Map to return a desired DTO/ViewModel that is hopefully registered in a mapping dependency. Then you need to consider async vs. synchronous flavours of the methods. (or forcing all calls to be synchronous or async)

Overall my advice would be to avoid Generic Repository patterns and don't abstract away EF just for the sake of abstraction, instead look for ways to leverage the most you can out of it. Most of the problems developers run into with performance and odd behaviour with EF stem from efforts to abstract it. They are so worried about needing to abstract it to be able to replace it if they need to, that they end up with those performance and complexity problems entirely because of the limitations imposed by the abstraction. (A self-fulfilling prophecy) "Architecture" can be a dirty word when you try to prematurely optimize a solution to solve a problem you don't currently, and probably will never face. Always keep YAGNI and KISS at the forefront of all design considerations.

弱骨蛰伏 2025-02-13 11:50:39

我想您在为给定解决方案选择正确的架构之间跌倒。必须为满足业务需求而创建软件。因此,根据业务,域的复杂性,我们必须选择写架构。

Microsoft详细介绍了此处PDF,您会在这里找到它

您希望数据与BL随后的“ N层”体系结构之间的抽象水平并不是最好的解决方案。即使我没有告诉N层已经过时了,但是对于这个问题,还有更多合适的解决方案。

而且,如果您使用EF或EF Core,则无需实现存储库本身包含UOW和存储库。

干净的体系结构引入了层之间更高水平的抽象水平。它将您的数据层抽象化,并使其可以在不同的项目或不同的解决方案上重复使用。干净的体系结构是我在这里无法描述的一个大主题。但是我从他那里学到了这个话题。

Jason Taylor-清洁建筑

https://wwww.youtube.com/watch?v = dk4yb6 -lxak

https://github.com/jasontaylordev/cleanarchitection

I guess you tumbled between selecting right architecture for a given solution. a software must be created for meet the business needs. so depending on the business, complexity of the domain, we have to choose the write architecture.

Microsoft elaborated this in a detail PDF you will find it here
https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/

You want some level of abstraction between your Data and the BL then "N-Layer" architecture is not the best solution. even though i am not telling the N-Layer entirely out dated but there is more suitable solutions for this problem .

And if you using EF or EF core then there is no need to implement Repository cus EF itself contain UOW and Repositories.

Clean Architecture introduce higher level of abstraction between layers. it abstract away your data layer and make it reusable over different project or different solutions. Clean architecture is a big topic i can't describe here in more details. but i learn the subject from him.

Jason Taylor- Clean Architecture

https://www.youtube.com/watch?v=dK4Yb6-LxAk

https://github.com/jasontaylordev/CleanArchitecture

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文