{"id":1104,"date":"2025-05-27T14:46:08","date_gmt":"2025-05-27T06:46:08","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1104"},"modified":"2025-05-27T14:46:08","modified_gmt":"2025-05-27T06:46:08","slug":"ultimate-asp-net-core-web-api-3-onion-architecture-implementation","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1104","title":{"rendered":"Ultimate ASP.NET Core Web API  3 ONION ARCHITECTURE IMPLEMENTATION"},"content":{"rendered":"<h1>3 Onion architecture implementation<\/h1>\n<p>3 \u6d0b\u8471\u67b6\u6784\u5b9e\u73b0<\/p>\n<p>In this chapter, we are going to talk about the Onion architecture, its layers, and the advantages of using it. We will learn how to create different layers in our application to separate the different application parts and improve the application's maintainability and testability.\u200c<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba Onion \u67b6\u6784\u3001\u5b83\u7684\u5c42\u4ee5\u53ca\u4f7f\u7528\u5b83\u7684\u4f18\u52bf\u3002\u6211\u4eec\u5c06\u5b66\u4e60\u5982\u4f55\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u521b\u5efa\u4e0d\u540c\u7684\u5c42\uff0c\u4ee5\u5206\u79bb\u4e0d\u540c\u7684\u5e94\u7528\u7a0b\u5e8f\u90e8\u5206\u5e76\u63d0\u9ad8\u5e94\u7528\u7a0b\u5e8f\u7684\u53ef\u7ef4\u62a4\u6027\u548c\u53ef\u6d4b\u8bd5\u6027\u3002<\/p>\n<p>That said, we are going to create a database model and transfer it to the MSSQL database by using the code first approach. So, we are going to learn how to create entities (model classes), how to work with the DbContext class, and how to use migrations to transfer our created database model to the real database. Of course, it is not enough to just create a database model and transfer it to the database. We need to use it as well, and for that, we will create a Repository pattern as a data access layer.<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a\u6570\u636e\u5e93\u6a21\u578b\uff0c\u5e76\u4f7f\u7528 Code First \u65b9\u6cd5\u5c06\u5176\u4f20\u8f93\u5230 MSSQL \u6570\u636e\u5e93\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5c06\u5b66\u4e60\u5982\u4f55\u521b\u5efa\u5b9e\u4f53\uff08\u6a21\u578b\u7c7b\uff09\uff0c\u5982\u4f55\u4f7f\u7528 DbContext \u7c7b\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528\u8fc1\u79fb\u5c06\u6211\u4eec\u521b\u5efa\u7684\u6570\u636e\u5e93\u6a21\u578b\u4f20\u8f93\u5230\u771f\u5b9e\u6570\u636e\u5e93\u3002\u5f53\u7136\uff0c\u4ec5\u4ec5\u521b\u5efa\u4e00\u4e2a\u6570\u636e\u5e93\u6a21\u578b\u5e76\u5c06\u5176\u4f20\u8f93\u5230\u6570\u636e\u5e93\u662f\u4e0d\u591f\u7684\u3002\u6211\u4eec\u4e5f\u9700\u8981\u4f7f\u7528\u5b83\uff0c\u4e3a\u6b64\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a Repository \u6a21\u5f0f\u4f5c\u4e3a\u6570\u636e\u8bbf\u95ee\u5c42\u3002<\/p>\n<p>With the Repository pattern, we create an abstraction layer between the data access and the business logic layer of an application. By using it, we are promoting a more loosely coupled approach to access our data in the database.<br \/>\n\u4f7f\u7528 Repository \u6a21\u5f0f\uff0c\u6211\u4eec\u5728\u5e94\u7528\u7a0b\u5e8f\u7684\u6570\u636e\u8bbf\u95ee\u548c\u4e1a\u52a1\u903b\u8f91\u5c42\u4e4b\u95f4\u521b\u5efa\u4e00\u4e2a\u62bd\u8c61\u5c42\u3002\u901a\u8fc7\u4f7f\u7528\u5b83\uff0c\u6211\u4eec\u6b63\u5728\u63a8\u5e7f\u4e00\u79cd\u66f4\u677e\u6563\u8026\u5408\u7684\u65b9\u6cd5\u6765\u8bbf\u95ee\u6570\u636e\u5e93\u4e2d\u7684\u6570\u636e\u3002<\/p>\n<p>Also, our code becomes cleaner, easier to maintain, and reusable. Data access logic is stored in a separate class, or sets of classes called a repository, with the responsibility of persisting the application\u2019s business model.<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u7684\u4ee3\u7801\u53d8\u5f97\u66f4\u5e72\u51c0\u3001\u66f4\u6613\u4e8e\u7ef4\u62a4\u548c\u53ef\u91cd\u7528\u3002\u6570\u636e\u8bbf\u95ee\u903b\u8f91\u5b58\u50a8\u5728\u5355\u72ec\u7684\u7c7b\u6216\u79f0\u4e3a\u5b58\u50a8\u5e93\u7684\u7c7b\u96c6\u4e2d\uff0c\u8d1f\u8d23\u6301\u4e45\u5316\u5e94\u7528\u7a0b\u5e8f\u7684\u4e1a\u52a1\u6a21\u578b\u3002<\/p>\n<p>Additionally, we are going to create a Service layer to extract all the business logic from our controllers, thus making the presentation layer and the controllers clean and easy to maintain.<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a Service \u5c42\uff0c\u4ece\u63a7\u5236\u5668\u4e2d\u63d0\u53d6\u6240\u6709\u4e1a\u52a1\u903b\u8f91\uff0c\u4ece\u800c\u4f7f\u8868\u793a\u5c42\u548c\u63a7\u5236\u5668\u5e72\u51c0\u4e14\u6613\u4e8e\u7ef4\u62a4\u3002<\/p>\n<p>So, let\u2019s start with the Onion architecture explanation.<br \/>\n\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u4ece Onion \u67b6\u6784\u89e3\u91ca\u5f00\u59cb\u3002<\/p>\n<h2>3.1 About Onion Architecture<\/h2>\n<p>3.1 \u5173\u4e8e Onion \u67b6\u6784<\/p>\n<p>The Onion architecture is a form of layered architecture and we can visualize these layers as concentric circles. Hence the name Onion architecture. The Onion architecture was first introduced by Jeffrey Palermo, to overcome the issues of the traditional N-layered architecture approach.\u200c<br \/>\n\u6d0b\u8471\u67b6\u6784\u662f\u5206\u5c42\u67b6\u6784\u7684\u4e00\u79cd\u5f62\u5f0f\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u8fd9\u4e9b\u5c42\u53ef\u89c6\u5316\u4e3a\u540c\u5fc3\u5706\u3002\u56e0\u6b64\u5f97\u540d Onion \u67b6\u6784\u3002Onion \u67b6\u6784\u6700\u521d\u7531 Jeffrey Palermo \u5f15\u5165\uff0c\u4ee5\u514b\u670d\u4f20\u7edf N \u5c42\u67b6\u6784\u65b9\u6cd5\u7684\u95ee\u9898\u3002<\/p>\n<p>There are multiple ways that we can split the onion, but we are going to choose the following approach where we are going to split the architecture into 4 layers:<br \/>\n\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u591a\u79cd\u65b9\u5f0f\u62c6\u5206\u6d0b\u8471\uff0c\u4f46\u6211\u4eec\u5c06\u9009\u62e9\u4ee5\u4e0b\u65b9\u6cd5\uff0c\u6211\u4eec\u5c06\u67b6\u6784\u62c6\u5206\u4e3a 4 \u5c42\uff1a<\/p>\n<p>\u2022 Domain Layer \u57df\u5c42<br \/>\n\u2022 Service Layer \u670d\u52a1\u5c42<br \/>\n\u2022 Infrastructure Layer \u57fa\u7840\u8bbe\u65bd\u5c42<br \/>\n\u2022 Presentation Layer \u8868\u793a\u5c42<\/p>\n<p>Conceptually, we can consider that the Infrastructure and Presentation layers are on the same level of the hierarchy.<br \/>\n\u4ece\u6982\u5ff5\u4e0a\u8bb2\uff0c\u6211\u4eec\u53ef\u4ee5\u8ba4\u4e3a Infrastructure \u548c Presentation \u5c42\u4f4d\u4e8e\u5c42\u6b21\u7ed3\u6784\u7684\u540c\u4e00\u7ea7\u522b\u3002<\/p>\n<p>Now, let us go ahead and look at each layer with more detail to see why we are introducing it and what we are going to create inside of that layer:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u7ee7\u7eed\u66f4\u8be6\u7ec6\u5730\u4e86\u89e3\u6bcf\u4e2a\u5c42\uff0c\u4ee5\u4e86\u89e3\u6211\u4eec\u4e3a\u4ec0\u4e48\u8981\u5f15\u5165\u5b83\u4ee5\u53ca\u6211\u4eec\u5c06\u5728\u8be5\u5c42\u4e2d\u521b\u5efa\u4ec0\u4e48\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0301.jpg\" alt=\"alt text\" \/><\/p>\n<p>We can see all the different layers that we are going to build in our project.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u8981\u5728\u9879\u76ee\u4e2d\u6784\u5efa\u7684\u6240\u6709\u4e0d\u540c\u5c42<\/p>\n<h3>3.1.1 Advantages of the Onion Architecture\u200c<\/h3>\n<p>3.1.1 Onion \u67b6\u6784\u7684\u4f18\u52bf<\/p>\n<p>Let us take a look at what are the advantages of Onion architecture, and why we would want to implement it in our projects.<br \/>\n\u8ba9\u6211\u4eec\u770b\u4e00\u4e0b Onion \u67b6\u6784\u7684\u4f18\u52bf\u662f\u4ec0\u4e48\uff0c\u4ee5\u53ca\u4e3a\u4ec0\u4e48\u6211\u4eec\u60f3\u5728\u6211\u4eec\u7684\u9879\u76ee\u4e2d\u5b9e\u73b0\u5b83\u3002<\/p>\n<p>All of the layers interact with each other strictly through the interfaces defined in the layers below. The flow of dependencies is towards the core of the Onion. We will explain why this is important in the next section.<br \/>\n\u6240\u6709\u5c42\u90fd\u4e25\u683c\u901a\u8fc7\u4e0b\u9762\u5c42\u4e2d\u5b9a\u4e49\u7684\u63a5\u53e3\u76f8\u4e92\u4ea4\u4e92\u3002\u4f9d\u8d56\u9879\u7684\u6d41\u5411 Onion \u7684\u6838\u5fc3\u3002\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u8282\u4e2d\u89e3\u91ca\u4e3a\u4ec0\u4e48\u8fd9\u5f88\u91cd\u8981\u3002<\/p>\n<p>Using dependency inversion throughout the project, depending on abstractions (interfaces) and not the implementations, allows us to switch out the implementation at runtime transparently. We are depending on abstractions at compile-time, which gives us strict contracts to work with, and we are being provided with the implementation at runtime.<br \/>\n\u5728\u6574\u4e2a\u9879\u76ee\u4e2d\u4f7f\u7528\u4f9d\u8d56\u53cd\u8f6c\uff0c\u53d6\u51b3\u4e8e\u62bd\u8c61 \uff08\u63a5\u53e3\uff09 \u800c\u4e0d\u662f\u5b9e\u73b0\uff0c\u5141\u8bb8\u6211\u4eec\u5728\u8fd0\u884c\u65f6\u900f\u660e\u5730\u5207\u6362\u5b9e\u73b0\u3002\u6211\u4eec\u5728\u7f16\u8bd1\u65f6\u4f9d\u8d56\u4e8e\u62bd\u8c61\uff0c\u8fd9\u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u4e25\u683c\u7684\u5951\u7ea6\uff0c\u5e76\u4e14\u6211\u4eec\u5728\u8fd0\u884c\u65f6\u83b7\u5f97\u4e86\u5b9e\u73b0\u3002<\/p>\n<p>Testability is very high with the Onion architecture because everything depends on abstractions. The abstractions can be easily mocked with a mocking library such as Moq. We can write business logic without concern about any of the implementation details. If we need anything from an external system or service, we can just create an interface for it and consume it. We do not have to worry about how it will be implemented.The higher layers of the Onion will take care of implementing that interface transparently.<br \/>\nOnion \u67b6\u6784\u7684\u53ef\u6d4b\u8bd5\u6027\u975e\u5e38\u9ad8\uff0c\u56e0\u4e3a\u4e00\u5207\u90fd\u4f9d\u8d56\u4e8e\u62bd\u8c61\u3002\u53ef\u4ee5\u4f7f\u7528\u6a21\u62df\u5e93\uff08\u5982 Moq\uff09\u8f7b\u677e\u6a21\u62df\u62bd\u8c61\u3002\u6211\u4eec\u53ef\u4ee5\u7f16\u5199\u4e1a\u52a1\u903b\u8f91\uff0c\u800c\u65e0\u9700\u62c5\u5fc3\u4efb\u4f55\u5b9e\u73b0\u7ec6\u8282\u3002\u5982\u679c\u6211\u4eec\u9700\u8981\u6765\u81ea\u5916\u90e8\u7cfb\u7edf\u6216\u670d\u52a1\u7684\u4efb\u4f55\u5185\u5bb9\uff0c\u6211\u4eec\u53ea\u9700\u4e3a\u5b83\u521b\u5efa\u4e00\u4e2a\u63a5\u53e3\u5e76\u4f7f\u7528\u5b83\u3002\u6211\u4eec\u4e0d\u5fc5\u62c5\u5fc3\u5b83\u5c06\u5982\u4f55\u5b9e\u65bd\u3002Onion \u7684\u8f83\u9ad8\u5c42\u5c06\u8d1f\u8d23\u900f\u660e\u5730\u5b9e\u73b0\u8be5\u63a5\u53e3\u3002<\/p>\n<h3>3.1.2 Flow of Dependencies\u200c<\/h3>\n<p>3.1.2 \u4f9d\u8d56\u6d41\u7a0b<\/p>\n<p>The main idea behind the Onion architecture is the flow of dependencies, or rather how the layers interact with each other. The deeper the layer resides inside the Onion, the fewer dependencies it has.<br \/>\nOnion \u67b6\u6784\u80cc\u540e\u7684\u4e3b\u8981\u601d\u60f3\u662f\u4f9d\u8d56\u5173\u7cfb\u7684\u6d41\u52a8\uff0c\u6216\u8005\u66f4\u786e\u5207\u5730\u8bf4\u662f\u5404\u5c42\u5982\u4f55\u76f8\u4e92\u4ea4\u4e92\u3002\u8be5\u5c42\u4f4d\u4e8e Onion \u5185\u90e8\u7684\u6df1\u5ea6\u8d8a\u6df1\uff0c\u5b83\u7684\u4f9d\u8d56\u9879\u5c31\u8d8a\u5c11\u3002<\/p>\n<p>The Domain layer does not have any direct dependencies on the outside layers. It is isolated, in a way, from the outside world. The outer layers are all allowed to reference the layers that are directly below them in the hierarchy.<br \/>\nDomain \u5c42\u5bf9\u5916\u90e8\u5c42\u6ca1\u6709\u4efb\u4f55\u76f4\u63a5\u7684\u4f9d\u8d56\u5173\u7cfb\u3002\u5728\u67d0\u79cd\u7a0b\u5ea6\u4e0a\uff0c\u5b83\u4e0e\u5916\u754c\u9694\u7edd\u3002\u5916\u90e8\u5c42\u90fd\u5141\u8bb8\u5f15\u7528\u5c42\u6b21\u7ed3\u6784\u4e2d\u4f4d\u4e8e\u5176\u6b63\u4e0b\u65b9\u7684\u5c42\u3002<\/p>\n<p>We can conclude that all the dependencies in the Onion architecture flow inwards. But we should ask ourselves, why is this important?<br \/>\n\u6211\u4eec\u53ef\u4ee5\u5f97\u51fa\u7ed3\u8bba\uff0cOnion \u67b6\u6784\u4e2d\u7684\u6240\u6709\u4f9d\u8d56\u9879\u90fd\u662f\u5411\u5185\u6d41\u52a8\u7684\u3002\u4f46\u6211\u4eec\u5e94\u8be5\u95ee\u95ee\u81ea\u5df1\uff0c\u4e3a\u4ec0\u4e48\u8fd9\u5f88\u91cd\u8981\uff1f<\/p>\n<p>The flow of dependencies dictates what a certain layer in the Onion architecture can do. Because it depends on the layers below it in the hierarchy, it can only call the methods that are exposed by the lower layers.<br \/>\n\u4f9d\u8d56\u9879\u7684\u6d41\u7a0b\u51b3\u5b9a\u4e86 Onion \u67b6\u6784\u4e2d\u7684\u67d0\u4e2a\u5c42\u53ef\u4ee5\u505a\u4ec0\u4e48\u3002\u56e0\u4e3a\u5b83\u4f9d\u8d56\u4e8e\u5c42\u6b21\u7ed3\u6784\u4e2d\u4f4e\u4e8e\u5b83\u7684\u5c42\uff0c\u6240\u4ee5\u5b83\u53ea\u80fd\u8c03\u7528\u7531\u8f83\u4f4e\u5c42\u516c\u5f00\u7684\u65b9\u6cd5\u3002<\/p>\n<p>We can use lower layers of the Onion architecture to define contracts or interfaces. The outer layers of the architecture implement these interfaces. This means that in the Domain layer, we are not concerning ourselves with infrastructure details such as the database or external services.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 Onion \u67b6\u6784\u7684\u8f83\u4f4e\u5c42\u6765\u5b9a\u4e49\u5408\u7ea6\u6216\u63a5\u53e3\u3002\u4f53\u7cfb\u7ed3\u6784\u7684\u5916\u90e8\u5c42\u5b9e\u73b0\u8fd9\u4e9b\u63a5\u53e3\u3002\u8fd9\u610f\u5473\u7740\u5728\u57df\u5c42\uff0c\u6211\u4eec\u4e0d\u5173\u5fc3\u57fa\u7840\u8bbe\u65bd\u7ec6\u8282\uff0c\u4f8b\u5982\u6570\u636e\u5e93\u6216\u5916\u90e8\u670d\u52a1\u3002<\/p>\n<p>Using this approach, we can encapsulate all of the rich business logic in the Domain and Service layers without ever having to know any implementation details. In the Service layer, we are going to depend only on the interfaces that are defined by the layer below, which is the Domain layer.<br \/>\n\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u6240\u6709\u4e30\u5bcc\u7684\u4e1a\u52a1\u903b\u8f91\u5c01\u88c5\u5728 Domain \u548c Service \u5c42\u4e2d\uff0c\u800c\u65e0\u9700\u4e86\u89e3\u4efb\u4f55\u5b9e\u73b0\u7ec6\u8282\u3002\u5728 Service \u5c42\u4e2d\uff0c\u6211\u4eec\u5c06\u4ec5\u4f9d\u8d56\u4e8e\u7531\u4e0b\u9762\u7684\u5c42\u5b9a\u4e49\u7684\u63a5\u53e3\uff0c\u5373 Domain \u5c42\u3002<\/p>\n<p>So, after all the theory, we can continue with our project implementation.<br \/>\n\u6240\u4ee5\uff0c\u5728\u6240\u6709\u7406\u8bba\u4e4b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u6211\u4eec\u7684\u9879\u76ee\u5b9e\u65bd\u3002<\/p>\n<p>Let\u2019s start with the models and the Entities project.<br \/>\n\u8ba9\u6211\u4eec\u4ece\u6a21\u578b\u548c\u5b9e\u4f53\u9879\u76ee\u5f00\u59cb\u3002<\/p>\n<h2>3.2 Creating Models<\/h2>\n<p>3.2 \u521b\u5efa\u6a21\u578b<\/p>\n<p>Using the example from the second chapter of this book, we are going to extract a new Class Library project named Entities.\u200c<br \/>\n\u4f7f\u7528\u672c\u4e66\u7b2c\u4e8c\u7ae0\u4e2d\u7684\u793a\u4f8b\uff0c\u6211\u4eec\u5c06\u63d0\u53d6\u4e00\u4e2a\u540d\u4e3a Entities \u7684\u65b0\u7c7b\u5e93\u9879\u76ee\u3002<\/p>\n<p>Inside it, we are going to create a folder named Models, which will contain all the model classes (entities). Entities represent classes that Entity Framework Core uses to map our database model with the tables from the database. The properties from entity classes will be mapped to the database columns.<\/p>\n<p>So, in the Models folder we are going to create two classes and modify them:<\/p>\n<pre><code>\/\/Company.cs \n\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\n\nnamespace Entities.Models\n{\n    public class Company\n    {\n        [Column(&quot;CompanyId&quot;)]\n        public Guid Id { get; set; }\n\n        [Required(ErrorMessage = &quot;Company name is a required field.&quot;)]\n        [MaxLength(60, ErrorMessage = &quot;Maximum length for the Name is 60 characters.&quot;)]\n\n        public string? Name { get; set; }\n        [Required(ErrorMessage = &quot;Company address is a required field.&quot;)]\n        [MaxLength(60, ErrorMessage = &quot;Maximum length for the Address is 60 characters&quot;)]\n        public string? Address { get; set; }\n\n        public string? Country { get; set; }\n\n        public ICollection&lt;Employee&gt;? Employees { get; set; }\n    }\n}<\/code><\/pre>\n<pre><code>\/\/Employee.cs\n\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\n\nnamespace Entities.Models\n{\n    public class Employee\n    {\n        [Column(&quot;EmployeeId&quot;)] \n        public Guid Id { get; set; }\n\n        [Required(ErrorMessage = &quot;Employee name is a required field.&quot;)]\n        [MaxLength(30, ErrorMessage = &quot;Maximum length for the Name is 30 characters.&quot;)]\n        public string? Name { get; set; }\n\n        [Required(ErrorMessage = &quot;Age is a required field.&quot;)]\n        public int Age { get; set; }\n\n        [Required(ErrorMessage = &quot;Position is a required field.&quot;)]\n        [MaxLength(20, ErrorMessage = &quot;Maximum length for the Position is 20 characters.&quot;)]\n        public string? Position { get; set; }\n\n        [ForeignKey(nameof(Company))]\n        public Guid CompanyId { get; set; }\n\n        public Company? Company { get; set; }\n    }\n}<\/code><\/pre>\n<p>We have created two classes: the Company and Employee. Those classes contain the properties which Entity Framework Core is going to map to the columns in our tables in the database. But not all the properties will be mapped as columns. The last property of the Company class (Employees) and the last property of the Employee class (Company) are navigational properties; these properties serve the purpose of defining the relationship between our models.<br \/>\n\u6211\u4eec\u521b\u5efa\u4e86\u4e24\u4e2a\u7c7b\uff1aCompany \u548c Employee\u3002\u8fd9\u4e9b\u7c7b\u5305\u542b Entity Framework Core \u5c06\u6620\u5c04\u5230\u6570\u636e\u5e93\u4e2d\u8868\u4e2d\u5217\u7684\u5c5e\u6027\u3002\u4f46\u5e76\u975e\u6240\u6709\u5c5e\u6027\u90fd\u5c06\u6620\u5c04\u4e3a\u5217\u3002Company \u7c7b\u7684\u6700\u540e\u4e00\u4e2a\u5c5e\u6027 \uff08Employees\uff09 \u548c Employee \u7c7b\u7684\u6700\u540e\u4e00\u4e2a\u5c5e\u6027 \uff08Company\uff09 \u662f\u5bfc\u822a\u5c5e\u6027;\u8fd9\u4e9b\u5c5e\u6027\u7528\u4e8e\u5b9a\u4e49\u6a21\u578b\u4e4b\u95f4\u7684\u5173\u7cfb\u3002<\/p>\n<p>We can see several attributes in our entities. The [Column] attribute will specify that the Id property is going to be mapped with a different name in the database. The [Required] and [MaxLength] properties are here for validation purposes. The first one declares the property as mandatory and the second one defines its maximum length.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u5728\u5b9e\u4f53\u4e2d\u770b\u5230\u51e0\u4e2a\u5c5e\u6027\u3002[Column] \u5c5e\u6027\u5c06\u6307\u5b9a Id \u5c5e\u6027\u5c06\u5728\u6570\u636e\u5e93\u4e2d\u4f7f\u7528\u4e0d\u540c\u7684\u540d\u79f0\u8fdb\u884c\u6620\u5c04\u3002\u6b64\u5904\u7684 [Required] \u548c [MaxLength] \u5c5e\u6027\u7528\u4e8e\u9a8c\u8bc1\u76ee\u7684\u3002\u7b2c\u4e00\u4e2a\u9009\u9879\u5c06\u5c5e\u6027\u58f0\u660e\u4e3a mandatory \u5c5e\u6027\uff0c\u7b2c\u4e8c\u4e2a\u9009\u9879\u5b9a\u4e49\u5176\u6700\u5927\u957f\u5ea6\u3002<\/p>\n<p>Once we transfer our database model to the real database, we are going to see how all these validation attributes and navigational properties affect the column definitions.<br \/>\n\u4e00\u65e6\u6211\u4eec\u5c06\u6570\u636e\u5e93\u6a21\u578b\u4f20\u8f93\u5230\u771f\u5b9e\u6570\u636e\u5e93\uff0c\u6211\u4eec\u5c06\u770b\u5230\u6240\u6709\u8fd9\u4e9b\u9a8c\u8bc1\u5c5e\u6027\u548c\u5bfc\u822a\u5c5e\u6027\u5982\u4f55\u5f71\u54cd\u5217\u5b9a\u4e49\u3002<\/p>\n<h2>3.3 Context Class and the Database Connection<\/h2>\n<p>3.3 Context \u7c7b\u548c\u6570\u636e\u5e93\u8fde\u63a5<\/p>\n<p>Before we start with the context class creation, we have to create another\u200c .NET Class Library and name it Repository. We are going to use this project for the database context and repository implementation.<br \/>\n\u5728\u5f00\u59cb\u521b\u5efa\u4e0a\u4e0b\u6587\u7c7b\u4e4b\u524d\uff0c\u6211\u4eec\u5fc5\u987b\u521b\u5efa\u53e6\u4e00\u4e2a .NET \u7c7b\u5e93\u5e76\u5c06\u5176\u547d\u540d\u4e3a Repository\u3002\u6211\u4eec\u5c06\u4f7f\u7528\u6b64\u9879\u76ee\u8fdb\u884c\u6570\u636e\u5e93\u4e0a\u4e0b\u6587\u548c\u5b58\u50a8\u5e93\u5b9e\u73b0\u3002<\/p>\n<p>Now, let's create the context class, which will be a middleware component for communication with the database. It must inherit from the Entity Framework Core\u2019s DbContext class and it consists of DbSet properties, which EF Core is going to use for the communication with the database.Because we are working with the DBContext class, we need to install the Microsoft.EntityFrameworkCore package in the Repository project. Also, we are going to reference the Entities project from the Repository project:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u521b\u5efa context \u7c7b\uff0c\u5b83\u5c06\u662f\u4e00\u4e2a\u7528\u4e8e\u4e0e\u6570\u636e\u5e93\u901a\u4fe1\u7684\u4e2d\u95f4\u4ef6\u7ec4\u4ef6\u3002\u5b83\u5fc5\u987b\u7ee7\u627f\u81ea Entity Framework Core \u7684 DbContext \u7c7b\uff0c\u5e76\u4e14\u7531 DbSet \u5c5e\u6027\u7ec4\u6210\uff0cEF Core \u5c06\u4f7f\u7528\u8fd9\u4e9b\u5c5e\u6027\u4e0e\u6570\u636e\u5e93\u901a\u4fe1\u3002\u7531\u4e8e\u6211\u4eec\u6b63\u5728\u4f7f\u7528 DBContext \u7c7b\uff0c\u56e0\u6b64\u9700\u8981\u5728 Repository \u9879\u76ee\u4e2d\u5b89\u88c5 Microsoft.EntityFrameworkCore \u5305\u3002\u6b64\u5916\uff0c\u6211\u4eec\u8fd8\u5c06\u4ece Repository \u9879\u76ee\u4e2d\u5f15\u7528 Entities \u9879\u76ee\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0302.jpg\" alt=\"alt text\" \/><\/p>\n<p>Then, let\u2019s navigate to the root of the Repository project and create the RepositoryContext class:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u5bfc\u822a\u5230 Repository \u9879\u76ee\u7684\u6839\u76ee\u5f55\u5e76\u521b\u5efa RepositoryContext \u7c7b\uff1a<\/p>\n<pre><code>\/\/ RepositoryContext.cs \nusing Entities.Models;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace Repository\n{\n    public class RepositoryContext : DbContext\n    {\n        public RepositoryContext(DbContextOptions options) : base(options) { }\n        public DbSet&lt;Company&gt;? Companies { get; set; }\n        public DbSet&lt;Employee&gt;? Employees { get; set; }\n    }\n}<\/code><\/pre>\n<p>After the class modification, let\u2019s open the appsettings.json file, in the main project, and add the connection string named sqlconnection:<br \/>\n\u4fee\u6539\u7c7b\u540e\uff0c\u8ba9\u6211\u4eec\u5728\u4e3b\u9879\u76ee\u4e2d\u6253\u5f00 appsettings.json \u6587\u4ef6\uff0c\u5e76\u6dfb\u52a0\u540d\u4e3a sqlconnection \u7684\u8fde\u63a5\u5b57\u7b26\u4e32\uff1a<\/p>\n<pre><code>{\n    &quot;Logging&quot;: {\n        &quot;LogLevel&quot;: { &quot;Default&quot;: &quot;Warning&quot; }\n    },\n    &quot;ConnectionStrings&quot;: {\n        &quot;sqlConnection&quot;: &quot;server=.; database=CompanyEmployee; Integrated Security=true&quot;\n    },\n    &quot;AllowedHosts&quot;: &quot;*&quot;\n}<\/code><\/pre>\n<p>It is quite important to have the JSON object with the ConnectionStrings name in our appsettings.json file, and soon you will see why.<br \/>\n\u5728\u6211\u4eec\u7684 appsettings.json \u6587\u4ef6\u4e2d\u62e5\u6709\u5177\u6709 ConnectionStrings \u540d\u79f0\u7684 JSON \u5bf9\u8c61\u975e\u5e38\u91cd\u8981\uff0c\u60a8\u5f88\u5feb\u5c31\u4f1a\u660e\u767d\u539f\u56e0\u3002<\/p>\n<p>But first, we have to add the Repository project\u2019s reference into the main project.<br \/>\n\u4f46\u9996\u5148\uff0c\u6211\u4eec\u5fc5\u987b\u5c06 Repository \u9879\u76ee\u7684\u5f15\u7528\u6dfb\u52a0\u5230\u4e3b\u9879\u76ee\u4e2d\u3002<\/p>\n<p>Then, let\u2019s create a new ContextFactory folder in the main project and inside it a new RepositoryContextFactory class. Since our RepositoryContext class is in a Repository project and not in the main one, this class will help our application create a derived DbContext instance during the design time which will help us with our migrations:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u5728\u4e3b\u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 ContextFactory \u6587\u4ef6\u5939\uff0c\u5e76\u5728\u5176\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 RepositoryContextFactory \u7c7b\u3002\u7531\u4e8e\u6211\u4eec\u7684 RepositoryContext \u7c7b\u4f4d\u4e8e Repository \u9879\u76ee\u4e2d\uff0c\u800c\u4e0d\u662f\u5728\u4e3b\u9879\u76ee\u4e2d\uff0c\u56e0\u6b64\u6b64\u7c7b\u5c06\u5e2e\u52a9\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u5728\u8bbe\u8ba1\u65f6\u521b\u5efa\u4e00\u4e2a\u6d3e\u751f\u7684 DbContext \u5b9e\u4f8b\uff0c\u8fd9\u5c06\u6709\u52a9\u4e8e\u6211\u4eec\u8fdb\u884c\u8fc1\u79fb\uff1a<\/p>\n<pre><code>\/\/ RepositoryContextFactory.cs\n\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Design;\nusing Repository;\n\nnamespace CompanyEmployees.ContextFactory\n{\n    public class RepositoryContextFactory : IDesignTimeDbContextFactory&lt;RepositoryContext&gt;\n    {\n        public RepositoryContext CreateDbContext(string[] args)\n        {\n            var configuration = new ConfigurationBuilder().SetBasePath(\n                Directory.GetCurrentDirectory()).AddJsonFile(&quot;appsettings.json&quot;).Build(); \n            var builder = new DbContextOptionsBuilder&lt;RepositoryContext&gt;().\n                UseSqlServer(configuration.GetConnectionString(&quot;sqlConnection&quot;));\n            return new RepositoryContext(builder.Options);\n        }\n    }\n}\n<\/code><\/pre>\n<p>We are using the <code> IDesignTimeDbContextFactory&lt;out TContext&gt; <\/code> interface that allows design-time services to discover implementations of this interface. Of course, the TContext parameter is our RepositoryContext class.<br \/>\n\u6211\u4eec\u6b63\u5728\u4f7f\u7528<code> IDesignTimeDbContextFactory&lt;out TContext&gt; <\/code> \u63a5\u53e3\uff0c\u8be5\u63a5\u53e3\u5141\u8bb8\u8bbe\u8ba1\u65f6\u670d\u52a1\u53d1\u73b0\u6b64\u63a5\u53e3\u7684\u5b9e\u73b0\u3002\u5f53\u7136\uff0cTContext \u53c2\u6570\u662f\u6211\u4eec\u7684 RepositoryContext \u7c7b\u3002<\/p>\n<p>For this, we need to add two using directives:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u9700\u8981\u6dfb\u52a0\u4e24\u4e2a using \u6307\u4ee4\uff1a<\/p>\n<pre><code>using Microsoft.EntityFrameworkCore.Design;\nusing Repository;<\/code><\/pre>\n<p>Then, we have to implement this interface with the CreateDbContext method. Inside it, we create the configuration variable of the IConfigurationRoot type and specify the appsettings file, we want to use. With its help, we can use the GetConnectionString method to access the connection string from the appsettings.json file. Moreover, to be able to use the UseSqlServer method, we need to install the Microsoft.EntityFrameworkCore.SqlServer package in the main project and add one more using directive:<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u4f7f\u7528 CreateDbContext \u65b9\u6cd5\u5b9e\u73b0\u6b64\u63a5\u53e3\u3002\u5728\u5176\u4e2d\uff0c\u6211\u4eec\u521b\u5efa IConfigurationRoot \u7c7b\u578b\u7684\u914d\u7f6e\u53d8\u91cf\uff0c\u5e76\u6307\u5b9a\u6211\u4eec\u8981\u4f7f\u7528\u7684 appsettings \u6587\u4ef6\u3002\u5728\u5b83\u7684\u5e2e\u52a9\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 GetConnectionString \u65b9\u6cd5\u4ece appsettings.json \u6587\u4ef6\u8bbf\u95ee\u8fde\u63a5\u5b57\u7b26\u4e32\u3002\u6b64\u5916\uff0c\u4e3a\u4e86\u80fd\u591f\u4f7f\u7528 UseSqlServer \u65b9\u6cd5\uff0c\u6211\u4eec\u9700\u8981\u5728\u4e3b\u9879\u76ee\u4e2d\u5b89\u88c5 Microsoft.EntityFrameworkCore.SqlServer \u5305\uff0c\u5e76\u518d\u6dfb\u52a0\u4e00\u4e2a using \u6307\u4ee4\uff1a<\/p>\n<pre><code>using Microsoft.EntityFrameworkCore;<\/code><\/pre>\n<p>If we navigate to the GetConnectionString method definition, we will see that it is an extension method that uses the ConnectionStrings name from the appsettings.json file to fetch the connection string by the provided key:<br \/>\n\u5982\u679c\u6211\u4eec\u5bfc\u822a\u5230 GetConnectionString \u65b9\u6cd5\u5b9a\u4e49\uff0c\u6211\u4eec\u5c06\u770b\u5230\u5b83\u662f\u4e00\u4e2a\u6269\u5c55\u65b9\u6cd5\uff0c\u5b83\u4f7f\u7528 appsettings.json \u6587\u4ef6\u4e2d\u7684 ConnectionStrings \u540d\u79f0\u901a\u8fc7\u63d0\u4f9b\u7684\u952e\u83b7\u53d6\u8fde\u63a5\u5b57\u7b26\u4e32\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0303.jpg\" alt=\"alt text\" \/><\/p>\n<p>Finally, in the CreateDbContext method, we return a new instance of our RepositoryContext class with provided options.<br \/>\n\u6700\u540e\uff0c\u5728 CreateDbContext \u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u8fd4\u56de RepositoryContext \u7c7b\u7684\u65b0\u5b9e\u4f8b\uff0c\u5176\u4e2d\u5305\u542b\u63d0\u4f9b\u7684\u9009\u9879\u3002<\/p>\n<h2>3.4 Migration and Initial Data Seed<\/h2>\n<p>3.4 \u8fc1\u79fb\u548c\u521d\u59cb\u6570\u636e\u79cd\u5b50<\/p>\n<p>Migration is a standard process of creating and updating the database from our application. Since we are finished with the database model creation, we can transfer that model to the real database. But we need to modify our CreateDbContext method first:\u200c<br \/>\n\u8fc1\u79fb\u662f\u4ece\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u521b\u5efa\u548c\u66f4\u65b0\u6570\u636e\u5e93\u7684\u6807\u51c6\u8fc7\u7a0b\u3002\u7531\u4e8e\u6211\u4eec\u5df2\u7ecf\u5b8c\u6210\u4e86\u6570\u636e\u5e93\u6a21\u578b\u7684\u521b\u5efa\uff0c\u56e0\u6b64\u6211\u4eec\u53ef\u4ee5\u5c06\u8be5\u6a21\u578b\u4f20\u8f93\u5230\u771f\u5b9e\u6570\u636e\u5e93\u3002\u4f46\u662f\u6211\u4eec\u9700\u8981\u5148\u4fee\u6539\u6211\u4eec\u7684 CreateDbContext \u65b9\u6cd5\uff1a<\/p>\n<pre><code>using Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Design;\nusing Repository;\n\nnamespace CompanyEmployees.ContextFactory\n{\n    public class RepositoryContextFactory : IDesignTimeDbContextFactory&lt;RepositoryContext&gt;\n    {\n        public RepositoryContext CreateDbContext(string[] args)\n        {\n            var configuration = new ConfigurationBuilder().SetBasePath(\n                Directory.GetCurrentDirectory()).AddJsonFile(&quot;appsettings.json&quot;).Build();\n\n            \/\/var builder = new DbContextOptionsBuilder&lt;RepositoryContext&gt;().\n            \/\/    UseSqlServer(configuration.GetConnectionString(&quot;sqlConnection&quot;));\n\n            var builder = new DbContextOptionsBuilder&lt;RepositoryContext&gt;().\n                    UseSqlServer(configuration.GetConnectionString(&quot;sqlConnection&quot;),\n                    b =&gt; b.MigrationsAssembly(&quot;CompanyEmployees&quot;));\n\n            return new RepositoryContext(builder.Options);\n        }\n    }\n}\n<\/code><\/pre>\n<p>We have to make this change because migration assembly is not in our main project, but in the Repository project. So, we\u2019ve just changed the project for the migration assembly.<br \/>\n\u6211\u4eec\u5fc5\u987b\u8fdb\u884c\u6b64\u66f4\u6539\uff0c\u56e0\u4e3a\u8fc1\u79fb\u7a0b\u5e8f\u96c6\u4e0d\u5728\u6211\u4eec\u7684\u4e3b\u9879\u76ee\u4e2d\uff0c\u800c\u662f\u5728 Repository \u9879\u76ee\u4e2d\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u521a\u521a\u66f4\u6539\u4e86\u8fc1\u79fb\u7a0b\u5e8f\u96c6\u7684\u9879\u76ee\u3002<\/p>\n<p>Before we execute our migration commands, we have to install an additional ef core library: Microsoft.EntityFrameworkCore.Tools<br \/>\n\u5728\u6267\u884c\u8fc1\u79fb\u547d\u4ee4\u4e4b\u524d\uff0c\u6211\u4eec\u5fc5\u987b\u5b89\u88c5\u4e00\u4e2a\u989d\u5916\u7684 ef \u6838\u5fc3\u5e93\uff1aMicrosoft.EntityFrameworkCore.Tools<\/p>\n<p>Now, let\u2019s open the Package Manager Console window and create our first migration:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u6253\u5f00 Package Manager Console \u7a97\u53e3\u5e76\u521b\u5efa\u6211\u4eec\u7684\u7b2c\u4e00\u4e2a\u8fc1\u79fb\uff1a<\/p>\n<pre><code>PM&gt; Add-Migration DatabaseCreation<\/code><\/pre>\n<p>net8\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4<\/p>\n<pre><code>PS C:\\CompanyEmployees\\CompanyEmployees&gt; dotnet ef migrations add DatabaseCreation\nBuild started...\nBuild succeeded.\nDone. To undo this action, use &#039;ef migrations remove&#039;\nPS <\/code><\/pre>\n<p>With this command, we are creating migration files and we can find them in the Migrations folder in our main project:<br \/>\n\u4f7f\u7528\u6b64\u547d\u4ee4\uff0c\u6211\u4eec\u6b63\u5728\u521b\u5efa\u8fc1\u79fb\u6587\u4ef6\uff0c\u5e76\u4e14\u53ef\u4ee5\u5728\u4e3b\u9879\u76ee\u7684 Migrations \u6587\u4ef6\u5939\u4e2d\u627e\u5230\u5b83\u4eec\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0304.jpg\" alt=\"alt text\" \/><\/p>\n<p>With those files in place, we can apply migration:<br \/>\n\u8fd9\u4e9b\u6587\u4ef6\u5c31\u4f4d\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5e94\u7528\u8fc1\u79fb\uff1a<\/p>\n<pre><code>PM&gt; Update-Database<\/code><\/pre>\n<p>net8\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4<\/p>\n<pre><code>PS C:\\CompanyEmployees\\CompanyEmployees&gt; dotnet ef database update\nBuild started...\nBuild succeeded.\nApplying migration &#039;20250503152559_DatabaseCreation&#039;.\nDone.\n<\/code><\/pre>\n<p>Excellent. We can inspect our database now:<br \/>\n\u975e\u5e38\u597d\u3002\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u68c0\u67e5\u6211\u4eec\u7684\u6570\u636e\u5e93\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0305.jpg\" alt=\"alt text\" \/><\/p>\n<p>Once we have the database and tables created, we should populate them with some initial data. To do that, we are going to create another folder in the Repository project called Configuration and add the CompanyConfiguration class:<br \/>\n\u521b\u5efa\u6570\u636e\u5e93\u548c\u8868\u540e\uff0c\u6211\u4eec\u5e94\u8be5\u4f7f\u7528\u4e00\u4e9b\u521d\u59cb\u6570\u636e\u586b\u5145\u5b83\u4eec\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u5c06\u5728 Repository \u9879\u76ee\u4e2d\u521b\u5efa\u53e6\u4e00\u4e2a\u540d\u4e3a Configuration \u7684\u6587\u4ef6\u5939\uff0c\u5e76\u6dfb\u52a0 CompanyConfiguration \u7c7b\uff1a<\/p>\n<pre><code>\/\/ CompanyConfiguration.cs\n\nusing Entities.Models;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\n\nnamespace Repository.Configuration\n{\n    public class CompanyConfiguration : IEntityTypeConfiguration&lt;Company&gt;\n    {\n        public void Configure(EntityTypeBuilder&lt;Company&gt; builder)\n        {\n            builder.HasData(\n                new Company\n                {\n                    Id = new Guid(&quot;c9d4c053-49b6-410c-bc78-2d54a9991870&quot;),\n                    Name = &quot;IT_Solutions Ltd&quot;,\n                    Address = &quot;583 Wall Dr. Gwynn Oak, MD 21207&quot;,\n                    Country = &quot;USA&quot;\n                },\n                new Company\n                {\n                    Id = new Guid(&quot;3d490a70-94ce-4d15-9494-5248280c2ce3&quot;),\n                    Name = &quot;Admin_Solutions Ltd&quot;,\n                    Address = &quot;312 Forest Avenue, BF 923&quot;,\n                    Country = &quot;USA&quot;\n                });\n        }\n    }\n}<\/code><\/pre>\n<p>Let\u2019s do the same thing for the EmployeeConfiguration class:<br \/>\n\u8ba9\u6211\u4eec\u5bf9 EmployeeConfiguration \u7c7b\u6267\u884c\u76f8\u540c\u7684\u4f5c\uff1a<\/p>\n<pre><code>\/\/ EmployeeConfiguration.cs\n\nusing Entities.Models;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;\n\nnamespace Repository.Configuration\n{\n    public class EmployeeConfiguration : IEntityTypeConfiguration&lt;Employee&gt;\n    {\n        public void Configure(EntityTypeBuilder&lt;Employee&gt; builder)\n        {\n            builder.HasData(\n                new Employee\n                {\n                    Id = new Guid(&quot;80abbca8-664d-4b20-b5de-024705497d4a&quot;),\n                    Name = &quot;Sam Raiden&quot;,\n                    Age = 26,\n                    Position = &quot;Software developer&quot;,\n                    CompanyId = new Guid(&quot;c9d4c053-49b6-410c-bc78-2d54a9991870&quot;)\n                },\n                new Employee\n                {\n                    Id = new Guid(&quot;86dba8c0-d178-41e7-938c-ed49778fb52a&quot;),\n                    Name = &quot;Jana McLeaf&quot;,\n                    Age = 30,\n                    Position = &quot;Software developer&quot;,\n                    CompanyId = new Guid(&quot;c9d4c053-49b6-410c-bc78-2d54a9991870&quot;)\n                },\n                new Employee\n                {\n                    Id = new Guid(&quot;021ca3c1-0deb-4afd-ae94-2159a8479811&quot;),\n                    Name = &quot;Kane Miller&quot;,\n                    Age = 35,\n                    Position = &quot;Administrator&quot;,\n                    CompanyId = new Guid(&quot;3d490a70-94ce-4d15-9494-5248280c2ce3&quot;)\n                });\n        }\n    }\n}<\/code><\/pre>\n<p>To invoke this configuration, we have to change the RepositoryContext class:<br \/>\n\u8981\u8c03\u7528\u6b64\u914d\u7f6e\uff0c\u6211\u4eec\u5fc5\u987b\u66f4\u6539 RepositoryContext \u7c7b\uff1a<\/p>\n<pre><code>\/\/ \/Repository\/RepositoryContext.cs \nusing Entities.Models;\nusing Microsoft.EntityFrameworkCore;\nusing Repository.Configuration;\n\nnamespace Repository\n{\n    \/\/public class RepositoryContext : DbContext\n    \/\/{\n    \/\/    public RepositoryContext(DbContextOptions options) : base(options) { }\n    \/\/    public DbSet&lt;Company&gt;? Companies { get; set; }\n    \/\/    public DbSet&lt;Employee&gt;? Employees { get; set; }\n    \/\/}\n\n    public class RepositoryContext : DbContext { \n        public RepositoryContext(DbContextOptions options) : base(options) { } \n        protected override void OnModelCreating(ModelBuilder modelBuilder) { \n            modelBuilder.ApplyConfiguration(new CompanyConfiguration()); \n            modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); \n        }\n        public DbSet&lt;Company&gt; Companies { get; set; } \n        public DbSet&lt;Employee&gt; Employees { get; set; } }\n}\n<\/code><\/pre>\n<p>Now, we can create and apply another migration to seed these data to the database:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u5e76\u5e94\u7528\u53e6\u4e00\u4e2a\u8fc1\u79fb\uff0c\u4ee5\u5c06\u8fd9\u4e9b\u6570\u636e\u64ad\u79cd\u5230\u6570\u636e\u5e93\uff1a<\/p>\n<pre><code>dotnet ef migrations add InitialData\ndotnet ef database update<\/code><\/pre>\n<p>This will transfer all the data from our configuration files to the respective tables.<br \/>\n\u8fd9\u4f1a\u5c06\u914d\u7f6e\u6587\u4ef6\u4e2d\u7684\u6240\u6709\u6570\u636e\u4f20\u8f93\u5230\u76f8\u5e94\u7684\u8868\u4e2d\u3002<\/p>\n<h2>3.5 Repository Pattern Logic<\/h2>\n<p>3.5 \u5b58\u50a8\u5e93\u6a21\u5f0f\u903b\u8f91<\/p>\n<p>After establishing a connection to the database and creating one, it's time to create a generic repository that will provide us with the CRUD methods. As a result, all the methods can be called upon any repository class in our project.\u200c<br \/>\n\u5728\u5efa\u7acb\u4e0e\u6570\u636e\u5e93\u7684\u8fde\u63a5\u5e76\u521b\u5efa\u4e00\u4e2a\u8fde\u63a5\u540e\uff0c\u662f\u65f6\u5019\u521b\u5efa\u4e00\u4e2a\u901a\u7528\u5b58\u50a8\u5e93\u4e86\uff0c\u5b83\u5c06\u4e3a\u6211\u4eec\u63d0\u4f9b CRUD \u65b9\u6cd5\u3002\u56e0\u6b64\uff0c\u6240\u6709\u65b9\u6cd5\u90fd\u53ef\u4ee5\u5728\u6211\u4eec\u9879\u76ee\u4e2d\u7684\u4efb\u4f55\u5b58\u50a8\u5e93\u7c7b\u4e0a\u8c03\u7528\u3002<\/p>\n<p>Furthermore, creating the generic repository and repository classes that use that generic repository is not going to be the final step. We will go a step further and create a wrapper class around repository classes and inject it as a service in a dependency injection container.<br \/>\n\u6b64\u5916\uff0c\u521b\u5efa\u4f7f\u7528\u8be5\u6cdb\u578b\u5b58\u50a8\u5e93\u7684\u6cdb\u578b\u5b58\u50a8\u5e93\u548c\u5b58\u50a8\u5e93\u7c7b\u4e0d\u4f1a\u662f\u6700\u540e\u4e00\u6b65\u3002\u6211\u4eec\u5c06\u66f4\u8fdb\u4e00\u6b65\uff0c\u56f4\u7ed5\u5b58\u50a8\u5e93\u7c7b\u521b\u5efa\u4e00\u4e2a\u5305\u88c5\u5668\u7c7b\uff0c\u5e76\u5c06\u5176\u4f5c\u4e3a\u670d\u52a1\u6ce8\u5165\u5230\u4f9d\u8d56\u9879\u6ce8\u5165\u5bb9\u5668\u4e2d\u3002<\/p>\n<p>Consequently, we will be able to instantiate this class once and then call any repository class we need inside any of our controllers.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u5c06\u80fd\u591f\u5b9e\u4f8b\u5316\u8fd9\u4e2a\u7c7b\u4e00\u6b21\uff0c\u7136\u540e\u5728\u6211\u4eec\u7684\u4efb\u4f55\u63a7\u5236\u5668\u4e2d\u8c03\u7528\u6211\u4eec\u9700\u8981\u7684\u4efb\u4f55\u4ed3\u5e93\u7c7b\u3002<\/p>\n<p>The advantages of this approach will become clearer once we use it in the project.<br \/>\n\u4e00\u65e6\u6211\u4eec\u5728\u9879\u76ee\u4e2d\u4f7f\u7528\u5b83\uff0c\u8fd9\u79cd\u65b9\u6cd5\u7684\u4f18\u52bf\u5c31\u4f1a\u53d8\u5f97\u66f4\u52a0\u660e\u663e\u3002<\/p>\n<p>That said, let\u2019s start by creating an interface for the repository inside the Contracts project:<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u8ba9\u6211\u4eec\u4ece Contracts \u9879\u76ee\u4e2d\u7684\u5b58\u50a8\u5e93\u521b\u5efa\u4e00\u4e2a\u63a5\u53e3\u5f00\u59cb\uff1a<\/p>\n<pre><code>\/\/ \/Contract\/IRepositoryBase.cs\n\nusing System.Linq.Expressions;\n\nnamespace Contract\n{\n    public interface IRepositoryBase&lt;T&gt;\n    {\n        IQueryable&lt;T&gt; FindAll(bool trackChanges);\n        IQueryable&lt;T&gt; FindByCondition(Expression&lt;Func&lt;T, bool&gt;&gt; expression, bool trackChanges);\n        void Create(T entity);\n        void Update(T entity);\n        void Delete(T entity);\n    }\n}\n<\/code><\/pre>\n<p>Right after the interface creation, we are going to reference Contracts inside the Repository project. Also, in the Repository project, we are going to create an abstract class RepositoryBase \u2014 which is going to implement the IRepositoryBase interface:<br \/>\n\u5728\u521b\u5efa\u63a5\u53e3\u540e\uff0c\u6211\u4eec\u5c06\u5f15\u7528 Repository \u9879\u76ee\u4e2d\u7684 Contracts\u3002\u6b64\u5916\uff0c\u5728 Repository \u9879\u76ee\u4e2d\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a\u62bd\u8c61\u7c7b RepositoryBase \u2014 \u5b83\u5c06\u5b9e\u73b0 IRepositoryBase \u63a5\u53e3\uff1a<\/p>\n<pre><code>\/\/ \/Repository\/RepositoryBase.cs\n\nusing System.Linq.Expressions;\nusing Contract;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace Repository\n{\n    public abstract class RepositoryBase&lt;T&gt; : IRepositoryBase&lt;T&gt; where T : class\n    {\n        protected RepositoryContext RepositoryContext;\n        public RepositoryBase(RepositoryContext repositoryContext) =&gt; RepositoryContext = repositoryContext;\n        public IQueryable&lt;T&gt; FindAll(bool trackChanges) =&gt; !trackChanges ? RepositoryContext.Set&lt;T&gt;().AsNoTracking() : RepositoryContext.Set&lt;T&gt;();\n        public IQueryable&lt;T&gt; FindByCondition(Expression&lt;Func&lt;T, bool&gt;&gt; expression, bool trackChanges) =&gt; !trackChanges ? RepositoryContext.Set&lt;T&gt;().Where(expression).AsNoTracking() : RepositoryContext.Set&lt;T&gt;().Where(expression); \n        public void Create(T entity) =&gt; RepositoryContext.Set&lt;T&gt;().Add(entity); \n        public void Update(T entity) =&gt; RepositoryContext.Set&lt;T&gt;().Update(entity); \n        public void Delete(T entity) =&gt; RepositoryContext.Set&lt;T&gt;().Remove(entity);\n    }\n}<\/code><\/pre>\n<p>This abstract class as well as the IRepositoryBase interface work with the generic type T. This type T gives even more reusability to the RepositoryBase class. That means we don\u2019t have to specify the exact model (class) right now for the RepositoryBase to work with. We can do that later on.<br \/>\n\u6b64\u62bd\u8c61\u7c7b\u4ee5\u53ca IRepositoryBase \u63a5\u53e3\u4f7f\u7528\u6cdb\u578b\u7c7b\u578b T\u3002\u6b64\u7c7b\u578b T \u4e3a RepositoryBase \u7c7b\u63d0\u4f9b\u4e86\u66f4\u591a\u7684\u53ef\u91cd\u7528\u6027\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u73b0\u5728\u4e0d\u5fc5\u4e3a RepositoryBase \u6307\u5b9a\u786e\u5207\u7684\u6a21\u578b\uff08\u7c7b\uff09\u3002\u6211\u4eec\u4ee5\u540e\u518d\u505a\u3002<\/p>\n<p>Moreover, we can see the trackChanges parameter. We are going to use it to improve our read-only query performance. When it\u2019s set to false, we attach the AsNoTracking method to our query to inform EF Core that it doesn\u2019t need to track changes for the required entities. This greatly improves the speed of a query.<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230 trackChanges \u53c2\u6570\u3002\u6211\u4eec\u5c06\u4f7f\u7528\u5b83\u6765\u63d0\u9ad8\u53ea\u8bfb\u67e5\u8be2\u6027\u80fd\u3002\u5f53\u5b83\u8bbe\u7f6e\u4e3a false \u65f6\uff0c\u6211\u4eec\u5c06 AsNoTracking \u65b9\u6cd5\u9644\u52a0\u5230\u6211\u4eec\u7684\u67e5\u8be2\uff0c\u4ee5\u901a\u77e5 EF Core \u5b83\u4e0d\u9700\u8981\u8ddf\u8e2a\u6240\u9700\u5b9e\u4f53\u7684\u66f4\u6539\u3002\u8fd9\u5927\u5927\u63d0\u9ad8\u4e86\u67e5\u8be2\u7684\u901f\u5ea6\u3002<\/p>\n<h2>3.6 Repository User Interfaces and Classes<\/h2>\n<p>3.6 \u5b58\u50a8\u5e93\u63a5\u53e3\u548c\u7c7b<\/p>\n<p>Now that we have the RepositoryBase class, let\u2019s create the user classes that will inherit this abstract class.\u200c<br \/>\n\u73b0\u5728\u6211\u4eec\u6709\u4e86 RepositoryBase \u7c7b\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u5c06\u7ee7\u627f\u6b64\u62bd\u8c61\u7c7b\u7684\u7528\u6237\u7c7b\u3002<\/p>\n<p>By inheriting from the RepositoryBase class, they will have access to all the methods from it. Furthermore, every user class will have its interface for additional model-specific methods.<br \/>\n\u901a\u8fc7\u7ee7\u627f RepositoryBase \u7c7b\uff0c\u4ed6\u4eec\u5c06\u53ef\u4ee5\u8bbf\u95ee\u8be5\u7c7b\u4e2d\u7684\u6240\u6709\u65b9\u6cd5\u3002\u6b64\u5916\uff0c\u6bcf\u4e2a user class \u90fd\u5c06\u5177\u6709\u5176\u7528\u4e8e\u5176\u4ed6\u7279\u5b9a\u4e8e\u6a21\u578b\u7684\u65b9\u6cd5\u7684\u63a5\u53e3\u3002<\/p>\n<p>This way, we are separating the logic that is common for all our repository user classes and also specific for every user class itself.<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u5c06\u6240\u6709\u5b58\u50a8\u5e93\u7528\u6237\u7c7b\u901a\u7528\u7684\u903b\u8f91\u4ee5\u53ca\u6bcf\u4e2a\u7528\u6237\u7c7b\u672c\u8eab\u7684\u7279\u5b9a\u903b\u8f91\u5206\u5f00\u3002<\/p>\n<p>Let\u2019s create the interfaces in the Contracts project for the Company and Employee classes:<br \/>\n\u8ba9\u6211\u4eec\u5728 Contracts \u9879\u76ee\u4e2d\u4e3a Company \u548c Employee \u7c7b\u521b\u5efa\u63a5\u53e3\uff1a<\/p>\n<pre><code>\/\/ \/Contract\/ICompanyRepository.cs\n\nnamespace Contract\n{\n    public interface ICompanyRepository { }\n}<\/code><\/pre>\n<pre><code>\/\/ \/Contract\/IEmployeeRepository.cs\n\nnamespace Contract\n{\n    public interface IEmployeeRepository { }\n}\n<\/code><\/pre>\n<p>After this, we can create repository user classes in the Repository project.<br \/>\n\u5728\u6b64\u4e4b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5728 Repository \u9879\u76ee\u4e2d\u521b\u5efa\u5b58\u50a8\u5e93\u7528\u6237\u7c7b\u3002<\/p>\n<p>The first thing we are going to do is to create the CompanyRepository class:<br \/>\n\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u521b\u5efa CompanyRepository \u7c7b\uff1a<\/p>\n<pre><code>\/\/ \/Repository\/CompanyRepository.cs\n\nusing Contract;\nusing Entities.Models;\n\nnamespace Repository\n{\n    public class CompanyRepository : RepositoryBase&lt;Company&gt;, ICompanyRepository\n    {\n        public CompanyRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }\n    }\n}\n<\/code><\/pre>\n<p>And then, the EmployeeRepository class:<br \/>\n\u7136\u540e\uff0cEmployeeRepository \u7c7b\uff1a<\/p>\n<pre><code>\/\/ \/Repository\/EmployeeRepository.cs\n\nusing Contract;\nusing Entities.Models;\n\nnamespace Repository\n{\n    public class EmployeeRepository : RepositoryBase&lt;Employee&gt;, IEmployeeRepository\n    {\n        public EmployeeRepository(RepositoryContext repositoryContext) : base(repositoryContext) { }\n    }\n}\n<\/code><\/pre>\n<p>After these steps, we are finished creating the repository and repository- user classes. But there are still more things to do.<br \/>\n\u5b8c\u6210\u8fd9\u4e9b\u6b65\u9aa4\u540e\uff0c\u6211\u4eec\u5b8c\u6210\u4e86 repository \u548c repository- user \u7c7b\u7684\u521b\u5efa\u3002\u4f46\u8fd8\u6709\u66f4\u591a\u7684\u4e8b\u60c5\u8981\u505a\u3002<\/p>\n<h2>3.7 Creating a Repository Manager<\/h2>\n<p>3.7 \u521b\u5efa\u4ed3\u5e93\u7ba1\u7406\u5668<\/p>\n<p>It is quite common for the API to return a response that consists of data from multiple resources; for example, all the companies and just some employees older than 30. In such a case, we would have to instantiate both of our repository classes and fetch data from their resources.\u200c<br \/>\nAPI \u8fd4\u56de\u7531\u6765\u81ea\u591a\u4e2a\u8d44\u6e90\u7684\u6570\u636e\u7ec4\u6210\u7684\u54cd\u5e94\u662f\u5f88\u5e38\u89c1\u7684;\u4f8b\u5982\uff0c\u6240\u6709\u516c\u53f8\u548c\u4e00\u4e9b 30 \u5c81\u4ee5\u4e0a\u7684\u5458\u5de5\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u5c06\u4e0d\u5f97\u4e0d\u5b9e\u4f8b\u5316\u6211\u4eec\u7684\u4e24\u4e2a\u5b58\u50a8\u5e93\u7c7b\u5e76\u4ece\u5b83\u4eec\u7684\u8d44\u6e90\u4e2d\u83b7\u53d6\u6570\u636e\u3002<\/p>\n<p>Maybe it\u2019s not a problem when we have only two classes, but what if we need the combined logic of five or even more different classes? It would just be too complicated to pull that off.<br \/>\n\u5f53\u6211\u4eec\u53ea\u6709\u4e24\u4e2a\u7c7b\u65f6\uff0c\u4e5f\u8bb8\u8fd9\u4e0d\u662f\u95ee\u9898\uff0c\u4f46\u5982\u679c\u6211\u4eec\u9700\u8981\u4e94\u4e2a\u751a\u81f3\u66f4\u591a\u4e0d\u540c\u7c7b\u7684\u7ec4\u5408\u903b\u8f91\u5462\uff1f\u8981\u505a\u5230\u8fd9\u4e00\u70b9\u592a\u590d\u6742\u4e86\u3002<\/p>\n<p>With that in mind, we are going to create a repository manager class, which will create instances of repository user classes for us and then register them inside the dependency injection container. After that, we can inject it inside our services with constructor injection (supported by ASP.NET Core). With the repository manager class in place, we may call any repository user class we need.<br \/>\n\u8003\u8651\u5230\u8fd9\u4e00\u70b9\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a\u5b58\u50a8\u5e93\u7ba1\u7406\u5668\u7c7b\uff0c\u5b83\u5c06\u4e3a\u6211\u4eec\u521b\u5efa\u5b58\u50a8\u5e93\u7528\u6237\u7c7b\u7684\u5b9e\u4f8b\uff0c\u7136\u540e\u5728\u4f9d\u8d56\u9879\u6ce8\u5165\u5bb9\u5668\u4e2d\u6ce8\u518c\u5b83\u4eec\u3002\u4e4b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u6784\u9020\u51fd\u6570\u6ce8\u5165\uff08\u7531 ASP.NET Core \u652f\u6301\uff09\u5c06\u5176\u6ce8\u5165\u6211\u4eec\u7684\u670d\u52a1\u4e2d\u3002\u6709\u4e86 repository manager \u7c7b\uff0c\u6211\u4eec\u53ef\u4ee5\u8c03\u7528\u6211\u4eec\u9700\u8981\u7684\u4efb\u4f55 repository user \u7c7b\u3002<\/p>\n<p>But we are also missing one important part. We have the Create, Update, and Delete methods in the RepositoryBase class, but they won\u2019t make any change in the database until we call the SaveChanges method. Our repository manager class will handle that as well.<br \/>\n\u4f46\u6211\u4eec\u4e5f\u7f3a\u5c11\u4e00\u4e2a\u91cd\u8981\u7684\u90e8\u5206\u3002\u6211\u4eec\u5728 RepositoryBase \u7c7b\u4e2d\u6709 Create\u3001Update \u548c Delete \u65b9\u6cd5\uff0c\u4f46\u5728\u8c03\u7528 SaveChanges \u65b9\u6cd5\u4e4b\u524d\uff0c\u5b83\u4eec\u4e0d\u4f1a\u5728\u6570\u636e\u5e93\u4e2d\u8fdb\u884c\u4efb\u4f55\u66f4\u6539\u3002\u6211\u4eec\u7684\u4ed3\u5e93\u7ba1\u7406\u5668\u7c7b\u4e5f\u4f1a\u5904\u7406\u8fd9\u4e2a\u95ee\u9898\u3002<\/p>\n<p>That said, let\u2019s get to it and create a new interface in the Contract project:<br \/>\n\u4e5f\u5c31\u662f\u8bf4\uff0c\u8ba9\u6211\u4eec\u5f00\u59cb\u5728 Contract \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u754c\u9762\uff1a<\/p>\n<pre><code>\/\/ \/Contract\/IRepositoryManager.cs\n\nnamespace Contract\n{\n    public interface IRepositoryManager\n    {\n        ICompanyRepository Company { get; }\n        IEmployeeRepository Employee { get; }\n        void Save();\n    }\n}<\/code><\/pre>\n<p>And add a new class to the Repository project:<br \/>\n\u5e76\u5c06\u4e00\u4e2a\u65b0\u7c7b\u6dfb\u52a0\u5230 Repository \u9879\u76ee\u4e2d\uff1a<\/p>\n<pre><code>\/\/ \/Repository\/RepositoryManager.cs\n\nusing Contract;\n\nnamespace Repository\n{\n    public sealed class RepositoryManager : IRepositoryManager\n    {\n        private readonly RepositoryContext _repositoryContext;\n        private readonly Lazy&lt;ICompanyRepository&gt; _companyRepository;\n        private readonly Lazy&lt;IEmployeeRepository&gt; _employeeRepository;\n        public RepositoryManager(RepositoryContext repositoryContext)\n        {\n            _repositoryContext = repositoryContext;\n            _companyRepository = new Lazy&lt;ICompanyRepository&gt;(() =&gt; new CompanyRepository(repositoryContext));\n            _employeeRepository = new Lazy&lt;IEmployeeRepository&gt;(() =&gt; new EmployeeRepository(repositoryContext));\n        }\n        public ICompanyRepository Company =&gt; _companyRepository.Value;\n        public IEmployeeRepository Employee =&gt; _employeeRepository.Value;\n        public void Save() =&gt; _repositoryContext.SaveChanges();\n    }\n}<\/code><\/pre>\n<p>As you can see, we are creating properties that will expose the concrete repositories and also we have the Save() method to be used after all the modifications are finished on a certain object. This is a good practice because now we can, for example, add two companies, modify two employees, and delete one company \u2014 all in one action \u2014 and then just call the Save method once. All the changes will be applied or if something fails, all the changes will be reverted:<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u6b63\u5728\u521b\u5efa\u5c06\u516c\u5f00\u5177\u4f53\u5b58\u50a8\u5e93\u7684\u5c5e\u6027\uff0c\u5e76\u4e14\u6211\u4eec\u8fd8\u6709 Save\uff08\uff09 \u65b9\u6cd5\uff0c\u53ef\u5728\u5b8c\u6210\u5bf9\u67d0\u4e2a\u5bf9\u8c61\u7684\u6240\u6709\u4fee\u6539\u540e\u4f7f\u7528\u3002\u8fd9\u662f\u4e00\u79cd\u5f88\u597d\u7684\u505a\u6cd5\uff0c\u56e0\u4e3a\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u4e24\u4e2a\u516c\u53f8\uff0c\u4fee\u6539\u4e24\u4e2a\u5458\u5de5\uff0c\u5220\u9664\u4e00\u4e2a\u516c\u53f8 - \u6240\u6709\u8fd9\u4e9b\u90fd\u5728\u4e00\u4e2a\u4f5c\u4e2d\u5b8c\u6210 - \u7136\u540e\u53ea\u9700\u8c03\u7528 Save \u65b9\u6cd5\u4e00\u6b21\u3002\u5c06\u5e94\u7528\u6240\u6709\u66f4\u6539\uff0c\u6216\u8005\u5982\u679c\u5931\u8d25\uff0c\u5219\u6240\u6709\u66f4\u6539\u90fd\u5c06\u88ab\u8fd8\u539f\uff1a<\/p>\n<pre><code>_repository.Company.Create(company); \n_repository.Company.Create(anotherCompany); _repository.Employee.Update(employee); \n_repository.Employee.Update(anotherEmployee); _repository.Company.Delete(oldCompany); \n_repository.Save();<\/code><\/pre>\n<p>The interesting part with the RepositoryManager implementation is that we are leveraging the power of the Lazy class to ensure the lazy initialization of our repositories. This means that our repository instances are only going to be created when we access them for the first time, and not before that.<br \/>\nRepositoryManager \u5b9e\u73b0\u7684\u6709\u8da3\u4e4b\u5904\u5728\u4e8e\uff0c\u6211\u4eec\u5229\u7528 Lazy \u7c7b\u7684\u5f3a\u5927\u529f\u80fd\u6765\u786e\u4fdd\u5b58\u50a8\u5e93\u7684\u5ef6\u8fdf\u521d\u59cb\u5316\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u7684\u5b58\u50a8\u5e93\u5b9e\u4f8b\u53ea\u4f1a\u5728\u6211\u4eec\u7b2c\u4e00\u6b21\u8bbf\u95ee\u5b83\u4eec\u65f6\u521b\u5efa\uff0c\u800c\u4e0d\u662f\u5728\u6b64\u4e4b\u524d\u3002<\/p>\n<p>After these changes, we need to register our manager class in the main project. So, let\u2019s first modify the ServiceExtensions class by adding this code:<br \/>\n\u5b8c\u6210\u8fd9\u4e9b\u66f4\u6539\u540e\uff0c\u6211\u4eec\u9700\u8981\u5728\u4e3b\u9879\u76ee\u4e2d\u6ce8\u518c\u6211\u4eec\u7684 manager \u7c7b\u3002\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u9996\u5148\u901a\u8fc7\u6dfb\u52a0\u4ee5\u4e0b\u4ee3\u7801\u6765\u4fee\u6539 ServiceExtensions \u7c7b\uff1a<\/p>\n<pre><code>\/\/ \/CompanyEmployees.Extensions\/ServiceExtensions.cs\n\nusing Contract;\nusing LoggerService;\nusing Repository;\n\nnamespace CompanyEmployees.Extensions\n{\n    public static class ServiceExtensions\n    {\n        public static void ConfigureCors(this IServiceCollection services) =&gt;\n            services.AddCors(options =&gt;\n            {\n                options.AddPolicy(&quot;CorsPolicy&quot;, builder =&gt;\n                builder.AllowAnyOrigin()\n                       .AllowAnyMethod()\n                       .AllowAnyHeader());\n            });\n\n        public static void ConfigureIISIntegration(this IServiceCollection services) =&gt;\n            services.Configure&lt;IISOptions&gt;(options =&gt;\n            {\n            });\n\n        public static void ConfigureLoggerService(this IServiceCollection services) =&gt; \n            services.AddSingleton&lt;ILoggerManager, LoggerManager&gt;();\n\n        public static void ConfigureRepositoryManager(this IServiceCollection services) =&gt; \n            services.AddScoped&lt;IRepositoryManager, RepositoryManager&gt;();\n    }\n}<\/code><\/pre>\n<p>And in the Program class above the AddController() method, we have to add this code:<\/p>\n<pre><code>builder.Services.ConfigureCors();\nbuilder.Services.ConfigureIISIntegration();\nbuilder.Services.ConfigureRepositoryManager();\n\nbuilder.Services.AddControllers();<\/code><\/pre>\n<p>Excellent.<br \/>\n\u975e\u5e38\u597d\u3002<\/p>\n<p>As soon as we add some methods to the specific repository classes, and add our service layer, we are going to be able to test this logic.<br \/>\n\u4e00\u65e6\u6211\u4eec\u5c06\u4e00\u4e9b\u65b9\u6cd5\u6dfb\u52a0\u5230\u7279\u5b9a\u7684\u5b58\u50a8\u5e93\u7c7b\u4e2d\uff0c\u5e76\u6dfb\u52a0\u6211\u4eec\u7684\u670d\u52a1\u5c42\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u6d4b\u8bd5\u8fd9\u4e2a\u903b\u8f91\u3002<\/p>\n<p>So, we did an excellent job here. The repository layer is prepared and ready to be used to fetch data from the database.<br \/>\n\u6240\u4ee5\uff0c\u6211\u4eec\u5728\u8fd9\u91cc\u505a\u5f97\u5f88\u597d\u3002\u5b58\u50a8\u5e93\u5c42\u5df2\u51c6\u5907\u5c31\u7eea\uff0c\u53ef\u7528\u4e8e\u4ece\u6570\u636e\u5e93\u4e2d\u83b7\u53d6\u6570\u636e\u3002<\/p>\n<p>Now, we can continue towards creating a service layer in our application.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u5728\u6211\u4eec\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u521b\u5efa\u4e00\u4e2a\u670d\u52a1\u5c42\u3002<\/p>\n<h2>3.8 Adding a Service Layer<\/h2>\n<p>3.8 \u6dfb\u52a0\u670d\u52a1\u5c42<\/p>\n<p>The Service layer sits right above the Domain layer (the Contracts project is the part of the Domain layer), which means that it has a reference to the Domain layer. The Service layer will be split into two\u200c projects, Service.Contracts and Service.<br \/>\nService \u5c42\u4f4d\u4e8e Domain \u5c42\u7684\u6b63\u4e0a\u65b9\uff08Contracts \u9879\u76ee\u662f Domain \u5c42\u7684\u4e00\u90e8\u5206\uff09\uff0c\u8fd9\u610f\u5473\u7740\u5b83\u5177\u6709\u5bf9 Domain \u5c42\u7684\u5f15\u7528\u3002Service \u5c42\u5c06\u62c6\u5206\u4e3a\u4e24\u4e2a\u9879\u76ee\uff1aService.Contract \u548c Service\u3002<\/p>\n<p>So, let\u2019s start with the Service.Contracts project creation (.NET Core Class Library) where we will hold the definitions for the service interfaces that are going to encapsulate the main business logic. In the next section, we are going to create a presentation layer and then, we will see the full use of this project.<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u4ece Service.Contracts \u9879\u76ee\u521b\u5efa\uff08.NET Core \u7c7b\u5e93\uff09\u5f00\u59cb\uff0c\u6211\u4eec\u5c06\u5728\u5176\u4e2d\u4fdd\u5b58\u5c06\u5c01\u88c5\u4e3b\u4e1a\u52a1\u903b\u8f91\u7684\u670d\u52a1\u63a5\u53e3\u7684\u5b9a\u4e49\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a\u8868\u793a\u5c42\uff0c\u7136\u540e\uff0c\u6211\u4eec\u5c06\u770b\u5230\u6b64\u9879\u76ee\u7684\u5b8c\u6574\u4f7f\u7528\u3002<\/p>\n<p>Once the project is created, we are going to add three interfaces inside it.<br \/>\n\u521b\u5efa\u9879\u76ee\u540e\uff0c\u6211\u4eec\u5c06\u5728\u5176\u4e2d\u6dfb\u52a0\u4e09\u4e2a\u63a5\u53e3\u3002<\/p>\n<p>ICompanyService:<\/p>\n<pre><code>\/\/ \/Service.Contracts\/ICompanyService.cs\n\nnamespace Service.Contracts\n{\n    public interface ICompanyService { }\n}\n<\/code><\/pre>\n<p>IEmployeeService:<\/p>\n<pre><code>\/\/ \/Service.Contracts\/IEmployeeService.cs\n\nnamespace Service.Contracts\n{\n    public interface IEmployeeService { }\n}\n<\/code><\/pre>\n<p>And IServiceManager:<\/p>\n<pre><code>\/\/ \/Service.Contracts\/IServiceManager.cs\n\nnamespace Service.Contracts\n{\n    public interface IServiceManager\n    {\n        ICompanyService CompanyService { get; }\n        IEmployeeService EmployeeService { get; }\n    }\n}<\/code><\/pre>\n<p>As you can see, we are following the same pattern as with the repository contracts implementation.<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u9075\u5faa\u4e0e repository contracts \u5b9e\u73b0\u76f8\u540c\u7684\u6a21\u5f0f\u3002<\/p>\n<p>Now, we can create another project, name it Service, and reference the Service.Contracts and Contracts projects inside it:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u521b\u5efa\u53e6\u4e00\u4e2a\u9879\u76ee\uff0c\u5c06\u5176\u547d\u540d\u4e3a Service\uff0c\u5e76\u5f15\u7528Service.Contracts \u548c Contracts \u9879\u76ee\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/0306.jpg\" alt=\"alt text\" \/><\/p>\n<p>After that, we are going to create classes that will inherit from the interfaces that reside in the Service.Contracts project.<br \/>\n\u4e4b\u540e\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u5c06\u4ece\u9a7b\u7559\u5728 Service.Contracts \u9879\u76ee\u4e2d\u7684\u63a5\u53e3\u7ee7\u627f\u7684\u7c7b\u3002<\/p>\n<p>So, let\u2019s start with the CompanyService class:<br \/>\n\u56e0\u6b64\uff0c\u8ba9\u6211\u4eec\u4ece CompanyService \u7c7b\u5f00\u59cb\uff1a<\/p>\n<pre><code>\/\/ \/Service\/CompanyService.cs\n\nusing Contract;\nusing Service.Contracts;\n\nnamespace Service\n{\n    internal sealed class CompanyService : ICompanyService\n    {\n        private readonly IRepositoryManager _repository;\n        private readonly ILoggerManager _logger;\n        public CompanyService(IRepositoryManager repository, ILoggerManager logger)\n        {\n            _repository = repository; _logger = logger;\n        }\n    }\n}<\/code><\/pre>\n<p>As you can see, our class inherits from the ICompanyService interface, and we are injecting the IRepositoryManager and ILoggerManager interfaces. We are going to use IRepositoryManager to access the repository methods from each user repository class (CompanyRepository or EmployeeRepository), and ILoggerManager to access the logging methods we\u2019ve created in the second section of this book.<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u7684\u7c7b\u7ee7\u627f\u81ea ICompanyService \u63a5\u53e3\uff0c\u5e76\u4e14\u6211\u4eec\u6b63\u5728\u6ce8\u5165 IRepositoryManager \u548c ILoggerManager \u63a5\u53e3\u3002\u6211\u4eec\u5c06\u4f7f\u7528 IRepositoryManager \u8bbf\u95ee\u6bcf\u4e2a\u7528\u6237\u5b58\u50a8\u5e93\u7c7b\uff08CompanyRepository \u6216 EmployeeRepository\uff09\u4e2d\u7684\u5b58\u50a8\u5e93\u65b9\u6cd5\uff0c\u5e76\u4f7f\u7528 ILoggerManager \u8bbf\u95ee\u6211\u4eec\u5728\u672c\u4e66\u7684\u7b2c\u4e8c\u90e8\u5206\u4e2d\u521b\u5efa\u7684\u65e5\u5fd7\u8bb0\u5f55\u65b9\u6cd5\u3002<\/p>\n<p>To continue, let\u2019s create a new EmployeeService class:<br \/>\n\u8981\u7ee7\u7eed\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 EmployeeService \u7c7b\uff1a<\/p>\n<pre><code>\/\/ \/Service\/EmployeeService.cs\n\nusing Contract;\nusing Service.Contracts;\n\nnamespace Service\n{\n    internal sealed class EmployeeService : IEmployeeService\n    {\n        private readonly IRepositoryManager _repository; \n        private readonly ILoggerManager _logger; \n        public EmployeeService(IRepositoryManager repository, ILoggerManager logger)\n        {\n            _repository = repository; _logger = logger;\n        }\n    }\n}<\/code><\/pre>\n<p>Finally, we are going to create the ServiceManager class:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u5c06\u521b\u5efa ServiceManager \u7c7b\uff1a<\/p>\n<pre><code>\/\/ \/Service\/ServiceManager.cs\n\nusing Contract;\nusing Service.Contracts;\nusing Service;\n\npublic sealed class ServiceManager : IServiceManager\n{\n    private readonly Lazy&lt;ICompanyService&gt; _companyService; private readonly Lazy&lt;IEmployeeService&gt; _employeeService;\n    public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger)\n    {\n        _companyService = new Lazy&lt;ICompanyService&gt;(() =&gt; new CompanyService(repositoryManager, logger));\n        _employeeService = new Lazy&lt;IEmployeeService&gt;(() =&gt; new EmployeeService(repositoryManager, logger));\n    }\n    public ICompanyService CompanyService =&gt; _companyService.Value;\n    public IEmployeeService EmployeeService =&gt; _employeeService.Value;\n}<\/code><\/pre>\n<p>Here, as we did with the RepositoryManager class, we are utilizing the Lazy class to ensure the lazy initialization of our services.<br \/>\n\u5728\u8fd9\u91cc\uff0c\u6b63\u5982\u6211\u4eec\u5bf9 RepositoryManager \u7c7b\u6240\u505a\u7684\u90a3\u6837\uff0c\u6211\u4eec\u5229\u7528 Lazy \u7c7b\u6765\u786e\u4fdd\u670d\u52a1\u7684\u5ef6\u8fdf\u521d\u59cb\u5316\u3002<\/p>\n<p>Now, with all these in place, we have to add the reference from the Service project inside the main project. Since Service is already referencing Service.Contracts, our main project will have the same reference as well.<br \/>\n\u73b0\u5728\uff0c\u5b8c\u6210\u6240\u6709\u8fd9\u4e9b\u4f5c\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u5728\u4e3b\u9879\u76ee\u4e2d\u6dfb\u52a0\u6765\u81ea Service \u9879\u76ee\u7684\u5f15\u7528\u3002\u7531\u4e8e Service \u5df2\u7ecf\u5f15\u7528\u4e86 Service.Contracts\uff0c\u56e0\u6b64\u6211\u4eec\u7684\u4e3b\u9879\u76ee\u4e5f\u5c06\u5177\u6709\u76f8\u540c\u7684\u5f15\u7528\u3002<\/p>\n<p>Now, we have to modify the ServiceExtensions class:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539 ServiceExtensions \u7c7b\uff1a<\/p>\n<pre><code>using Contract;\nusing LoggerService;\nusing Repository;\nusing Service.Contracts;\n\nnamespace CompanyEmployees.Extensions\n{\n    public static class ServiceExtensions\n    {\n        public static void ConfigureCors(this IServiceCollection services) =&gt;\n            services.AddCors(options =&gt;\n            {\n                options.AddPolicy(&quot;CorsPolicy&quot;, builder =&gt;\n                builder.AllowAnyOrigin()\n                       .AllowAnyMethod()\n                       .AllowAnyHeader());\n            });\n\n        public static void ConfigureIISIntegration(this IServiceCollection services) =&gt;\n            services.Configure&lt;IISOptions&gt;(options =&gt;\n            {\n            });\n\n        public static void ConfigureLoggerService(this IServiceCollection services) =&gt; \n            services.AddSingleton&lt;ILoggerManager, LoggerManager&gt;();\n\n        public static void ConfigureRepositoryManager(this IServiceCollection services) =&gt; \n            services.AddScoped&lt;IRepositoryManager, RepositoryManager&gt;();\n\n        public static void ConfigureServiceManager(this IServiceCollection services) =&gt; \n            services.AddScoped&lt;IServiceManager, ServiceManager&gt;();\n    }\n}<\/code><\/pre>\n<p>And we have to add using directives:<br \/>\n\u6211\u4eec\u5fc5\u987b\u6dfb\u52a0 using \u6307\u4ee4\uff1a<\/p>\n<pre><code>using Service; \nusing Service.Contracts;<\/code><\/pre>\n<p>Then, all we have to do is to modify the Program class to call this extension method:<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u4fee\u6539 Program \u7c7b\u4ee5\u8c03\u7528\u6b64\u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.ConfigureIISIntegration();\nbuilder.Services.ConfigureRepositoryManager();\nbuilder.Services.ConfigureServiceManager();\n\nbuilder.Services.AddControllers();<\/code><\/pre>\n<h2>3.9 Registering RepositoryContext at a Runtime<\/h2>\n<p>3.9 \u5728\u8fd0\u884c\u65f6\u6ce8\u518c RepositoryContext<\/p>\n<p>With the RepositoryContextFactory class, which implements the IDesignTimeDbContextFactory interface, we have registered our RepositoryContext class at design time. This helps us find the RepositoryContext class in another project while executing migrations.\u200c<br \/>\n\u4f7f\u7528\u5b9e\u73b0 IDesignTimeDbContextFactory \u63a5\u53e3\u7684 RepositoryContextFactory \u7c7b\uff0c\u6211\u4eec\u5728\u8bbe\u8ba1\u65f6\u6ce8\u518c\u4e86 RepositoryContext \u7c7b\u3002\u8fd9\u6709\u52a9\u4e8e\u6211\u4eec\u5728\u6267\u884c\u8fc1\u79fb\u65f6\u5728\u53e6\u4e00\u4e2a\u9879\u76ee\u4e2d\u627e\u5230 RepositoryContext \u7c7b\u3002<\/p>\n<p>But, as you could see, we have the RepositoryManager service registration, which happens at runtime, and during that registration, we must have RepositoryContext registered as well in the runtime, so we could inject it into other services (like RepositoryManager service). This might be a bit confusing, so let\u2019s see what that means for us.<br \/>\n\u4f46\u662f\uff0c\u6b63\u5982\u4f60\u6240\u770b\u5230\u7684\uff0c\u6211\u4eec\u6709 RepositoryManager \u670d\u52a1\u6ce8\u518c\uff0c\u8fd9\u53d1\u751f\u5728\u8fd0\u884c\u65f6\uff0c\u5728\u6ce8\u518c\u671f\u95f4\uff0c\u6211\u4eec\u4e5f\u5fc5\u987b\u5728\u8fd0\u884c\u65f6\u6ce8\u518c RepositoryContext\uff0c\u4ee5\u4fbf\u6211\u4eec\u53ef\u4ee5\u5c06\u5176\u6ce8\u5165\u5230\u5176\u4ed6\u670d\u52a1\uff08\u5982 RepositoryManager \u670d\u52a1\uff09\u4e2d\u3002\u8fd9\u53ef\u80fd\u6709\u70b9\u4ee4\u4eba\u56f0\u60d1\uff0c\u6240\u4ee5\u8ba9\u6211\u4eec\u770b\u770b\u8fd9\u5bf9\u6211\u4eec\u610f\u5473\u7740\u4ec0\u4e48\u3002<\/p>\n<p>Let\u2019s modify the ServiceExtensions class:<br \/>\n\u8ba9\u6211\u4eec\u4fee\u6539 ServiceExtensions \u7c7b\uff1a<\/p>\n<pre><code>using Contract;\nusing LoggerService;\nusing Microsoft.EntityFrameworkCore;\nusing Repository;\nusing Service.Contracts;\n\nnamespace CompanyEmployees.Extensions\n{\n    public static class ServiceExtensions\n    {\n        public static void ConfigureCors(this IServiceCollection services) =&gt;\n            services.AddCors(options =&gt;\n            {\n                options.AddPolicy(&quot;CorsPolicy&quot;, builder =&gt;\n                builder.AllowAnyOrigin()\n                       .AllowAnyMethod()\n                       .AllowAnyHeader());\n            });\n\n        public static void ConfigureIISIntegration(this IServiceCollection services) =&gt;\n            services.Configure&lt;IISOptions&gt;(options =&gt;\n            {\n            });\n\n        public static void ConfigureLoggerService(this IServiceCollection services) =&gt; \n            services.AddSingleton&lt;ILoggerManager, LoggerManager&gt;();\n\n        public static void ConfigureRepositoryManager(this IServiceCollection services) =&gt; \n            services.AddScoped&lt;IRepositoryManager, RepositoryManager&gt;();\n\n        public static void ConfigureServiceManager(this IServiceCollection services) =&gt; \n            services.AddScoped&lt;IServiceManager, ServiceManager&gt;();\n\n        public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration configuration) =&gt; \n            services.AddDbContext&lt;RepositoryContext&gt;(opts =&gt; opts.UseSqlServer(configuration.GetConnectionString(&quot;sqlConnection&quot;)));\n    }\n}<\/code><\/pre>\n<p>We are not specifying the MigrationAssembly inside the UseSqlServer method. We don\u2019t need it in this case.<br \/>\n\u6211\u4eec\u6ca1\u6709\u5728 UseSqlServer \u65b9\u6cd5\u4e2d\u6307\u5b9a MigrationAssembly\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u4e0d\u9700\u8981\u5b83\u3002<\/p>\n<p>As the final step, we have to call this method in the Program class:<br \/>\n\u4f5c\u4e3a\u6700\u540e\u4e00\u6b65\uff0c\u6211\u4eec\u5fc5\u987b\u5728 Program \u7c7b\u4e2d\u8c03\u7528\u6b64\u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.ConfigureRepositoryManager();\nbuilder.Services.ConfigureServiceManager();\nbuilder.Services.ConfigureSqlContext(builder.Configuration);\n\nbuilder.Services.AddControllers();<\/code><\/pre>\n<p>With this, we have completed our implementation, and our service layer is ready to be used in our next chapter where we are going to learn about handling GET requests in ASP.NET Core Web API.<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u5df2\u7ecf\u5b8c\u6210\u4e86\u6211\u4eec\u7684\u5b9e\u73b0\uff0c\u6211\u4eec\u7684\u670d\u52a1\u5c42\u5df2\u51c6\u5907\u597d\u5728\u4e0b\u4e00\u7ae0\u4e2d\u4f7f\u7528\uff0c\u6211\u4eec\u5c06\u5728\u4e0b\u4e00\u7ae0\u4e2d\u5b66\u4e60\u5982\u4f55\u5728 ASP.NET Core Web API \u4e2d\u5904\u7406 GET \u8bf7\u6c42\u3002<\/p>\n<p>One additional thing. From .NET 6 RC2, there is a shortcut method AddSqlServer, which can be used like this:<br \/>\n\u8fd8\u6709\u4e00\u4ef6\u4e8b\u3002\u4ece .NET 6 RC2 \u5f00\u59cb\uff0c\u6709\u4e00\u4e2a\u5feb\u6377\u65b9\u6cd5 AddSqlServer\uff0c\u53ef\u4ee5\u50cf\u8fd9\u6837\u4f7f\u7528\uff1a<\/p>\n<pre><code>public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration configuration) =&gt; services.AddSqlServer&lt;RepositoryContext&gt;((configuration.GetConnectionString(&quot;sqlConnection&quot;)));<\/code><\/pre>\n<p>This method replaces both AddDbContext and UseSqlServer methods and allows an easier configuration. But it doesn\u2019t provide all of the features the AddDbContext method provides. So for more advanced options, it is recommended to use AddDbContext. We will use it throughout the rest of the project.<br \/>\n\u6b64\u65b9\u6cd5\u66ff\u6362\u4e86 AddDbContext \u548c UseSqlServer \u65b9\u6cd5\uff0c\u5e76\u5141\u8bb8\u66f4\u8f7b\u677e\u5730\u8fdb\u884c\u914d\u7f6e\u3002\u4f46\u5b83\u4e0d\u63d0\u4f9b AddDbContext \u65b9\u6cd5\u63d0\u4f9b\u7684\u6240\u6709\u529f\u80fd\u3002\u56e0\u6b64\uff0c\u5bf9\u4e8e\u66f4\u9ad8\u7ea7\u7684\u9009\u9879\uff0c\u5efa\u8bae\u4f7f\u7528 AddDbContext\u3002\u6211\u4eec\u5c06\u5728\u9879\u76ee\u7684\u5176\u4f59\u90e8\u5206\u4f7f\u7528\u5b83\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>3 Onion architecture implementation 3 \u6d0b\u8471\u67b6\u6784\u5b9e\u73b0 In this chapter, we are going to talk about the Onion architecture, its layers, and the advantages of using it. We will learn how to create different layers in our application to separate the different application parts and improve the application&#8217;s maintainability and testability.\u200c \u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u8ba8\u8bba Onion \u67b6\u6784\u3001\u5b83\u7684\u5c42\u4ee5\u53ca\u4f7f\u7528\u5b83\u7684\u4f18\u52bf\u3002\u6211\u4eec\u5c06\u5b66\u4e60\u5982\u4f55\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u521b\u5efa\u4e0d\u540c\u7684\u5c42\uff0c\u4ee5\u5206\u79bb\u4e0d\u540c\u7684\u5e94\u7528\u7a0b\u5e8f\u90e8\u5206\u5e76\u63d0\u9ad8\u5e94\u7528\u7a0b\u5e8f\u7684\u53ef\u7ef4\u62a4\u6027\u548c\u53ef\u6d4b\u8bd5\u6027\u3002 That said, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1104","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1104","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1104"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1104\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1104"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1104"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1104"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}