diff --git a/docs/en/Domain-Services.md b/docs/en/Domain-Services.md new file mode 100644 index 0000000000..7eb6c917cd --- /dev/null +++ b/docs/en/Domain-Services.md @@ -0,0 +1,3 @@ +# ABP Documentation + +TODO! \ No newline at end of file diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index 1d1a5d5f9d..8758d99715 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -10,9 +10,9 @@ This document explains how to integrate EF Core as an ORM provider to ABP based Install-Package Volo.Abp.EntityFrameworkCore ```` -Then add `AbpEntityFrameworkCoreModule` module dependency to your [module](Module-Development-Basics.md): +Then add `AbpEntityFrameworkCoreModule` module dependency (`DependsOn` attribute) to your [module](Module-Development-Basics.md): -````C# +````c# using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Modularity; @@ -26,6 +26,8 @@ namespace MyCompany.MyProject } ```` +> Note: Instead, you can directly download a [startup template](https://abp.io/Templates) with EF Core pre-installed. + ### Creating DbContext You can create your DbContext as you normally do. It should be derived from `AbpDbContext` as shown below: @@ -38,7 +40,7 @@ namespace MyCompany.MyProject { public class MyDbContext : AbpDbContext { - //...your DbSet properties + //...your DbSet properties here public MyDbContext(DbContextOptions options) : base(options) @@ -74,7 +76,7 @@ namespace MyCompany.MyProject #### Add Default Repositories -ABP can automatically create repositories (TODO: link) for the entities in your DbContext. Just use `AddDefaultRepositories()` option on registration: +ABP can automatically create default [generic repositories](Repositories.md) for the entities in your DbContext. Just use `AddDefaultRepositories()` option on the registration: ````C# services.AddAbpDbContext(options => @@ -83,7 +85,7 @@ services.AddAbpDbContext(options => }); ```` -This will create a repository for each aggreate root entity (classes derived from AggregateRoot) by default. If you want to create repositories for other entities too, then set `includeAllEntities` to `true`: +This will create a repository for each [aggregate root entity](Entities.md) (classes derived from `AggregateRoot`) by default. If you want to create repositories for other entities too, then set `includeAllEntities` to `true`: ````C# services.AddAbpDbContext(options => @@ -92,16 +94,203 @@ services.AddAbpDbContext(options => }); ```` -Then you can inject and use `IRepository` or `IQueryableRepository` in your services. +Then you can inject and use `IRepository` or `IQueryableRepository` in your services. + +Assume that you have a `Book` entity with `Guid` primary key: + +```csharp +public class Book : AggregateRoot +{ + public string Name { get; set; } + + public BookType Type { get; set; } +} +``` + +And you want to create a new `Book` entity in a [domain service](Domain-Services.md): + +````csharp +public class BookManager : DomainService +{ + private readonly IRepository _bookRepository; + + public BookManager(IRepository bookRepository) //inject default repository + { + _bookRepository = bookRepository; + } + + public async Task CreateBook(string name, BookType type) + { + Check.NotNullOrWhiteSpace(name, nameof(name)); + + var book = new Book + { + Id = GuidGenerator.Create(), + Name = name, + Type = type + }; + + await _bookRepository.InsertAsync(book); //Use a standard repository method + + return book; + } +} +```` + +This sample uses `InsertAsync` method to insert a new entity to the database. #### Add Custom Repositories -TODO... +Default generic repositories are powerful enough in most cases (since they implement `IQueryable`). However, you may need to create a custom repository to add your own repository methods. + +Assume that you want to delete all books by type. It's suggested to define an interface for your custom repository: + +````csharp +public interface IBookRepository : IRepository +{ + Task DeleteBooksByType(BookType type); +} +```` + +You generally want to derive from the `IRepository` to inherit standard repository methods. However, you don't have to. Repository interfaces are defined in the domain layer of a layered application. They are implemented in the data/infrastructure layer (`EntityFrameworkCore` project in a [startup template](https://abp.io/Templates)). + +Example implementation of the `IBookRepository` interface: + +````csharp +public class BookRepository : EfCoreRepository, IBookRepository +{ + public BookRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task DeleteBooksByType(BookType type) + { + await DbContext.Database.ExecuteSqlCommandAsync( + $"DELETE FROM Books WHERE Type = {(int)type}" + ); + } +} +```` + +Now, it's possible to [inject](Dependency-Injection.md) the `IBookRepository` and use the `DeleteBooksByType` method when needed. + +##### Override Default Generic Repository + +Even if you create a custom repository, you can still inject the default generic repository (`IRepository` for this example). Default repository implementation will not use the class you have created. + +If you want to replace default repository implementation with your custom repository, do it inside `AddAbpDbContext` options: + +````csharp +context.Services.AddAbpDbContext(options => +{ + options.AddDefaultRepositories(); + options.AddRepository(); +}); +```` + +This is especially important when you want to **override a base repository method** to customize it. For instance, you may want to override `DeleteAsync` method to delete an entity in a more efficient way: + +````csharp +public override async Task DeleteAsync( + Guid id, + bool autoSave = false, + CancellationToken cancellationToken = default) +{ + //TODO: Custom implementation of the delete method +} +```` + +#### Advanced Topics + +##### Set Default Repository Classes + +Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for default repository implementation. + +First, define your repository classes like that: + +```csharp +public class MyRepositoryBase + : EfCoreRepository + where TEntity : class, IEntity +{ + public MyRepositoryBase(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} + +public class MyRepositoryBase + : EfCoreRepository + where TEntity : class, IEntity +{ + public MyRepositoryBase(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} +``` + +First one is for [entities with composite keys](Entities.md), second one is for entities with single primary key. -#### Set Base DbContext Class or Interface for Default Repositories +It's suggested to inherit from the `EfCoreRepository` class and override methods if needed. Otherwise, you will have to implement all standard repository methods manually. -... +Now, you can use `SetDefaultRepositoryClasses` option: + +```csharp +context.Services.AddAbpDbContext(options => +{ + options.SetDefaultRepositoryClasses( + typeof(MyRepositoryBase<,>), + typeof(MyRepositoryBase<>) + ); + //... +}); +``` + +##### Set Base DbContext Class or Interface for Default Repositories + +If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example: + +````csharp +public interface IBookStoreDbContext : IEfCoreDbContext +{ + DbSet Books { get; } +} +```` + +`IBookStoreDbContext` is implemented by the `BookStoreDbContext` class. Then you can use generic overload of the `AddDefaultRepositories`: + +````csharp +context.Services.AddAbpDbContext(options => +{ + options.AddDefaultRepositories(); + //... +}); +```` + +Now, your custom `BookRepository` can also use the `IBookStoreDbContext` interface: + +````csharp +public class BookRepository : EfCoreRepository, IBookRepository +{ + //... +} +```` + +One advantage of using interface for a DbContext is then it becomes replaceable by another implementation. + +##### Replace Other DbContextes + +Once you properly define and use an interface for DbContext, then any other implementation can replace it using the `ReplaceDbContext` option: + +````csharp +context.Services.AddAbpDbContext(options => +{ + //... + options.ReplaceDbContext(); +}); +```` -#### Replace Other Repository +In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime. -... \ No newline at end of file