mirror of https://github.com/abpframework/abp
Merge branch 'dev' of https://github.com/abpframework/abp into dev
commit
bc9f8f26a5
@ -1,3 +1,356 @@
|
||||
## Unit of Work
|
||||
# Unit of Work
|
||||
|
||||
ABP Framework's Unit Of Work (UOW) implementation provides an abstraction and control on a **database connection and transaction** scope in an application.
|
||||
|
||||
Once a new UOW started, it creates an **ambient scope** that is participated by **all the database operations** performed in the current scope and considered as a **single transaction boundary**. The operations are **committed** (on success) or **rolled back** (on exception) all together.
|
||||
|
||||
ABP's UOW system is;
|
||||
|
||||
* **Works conventional**, so most of the times you don't deal with UOW at all.
|
||||
* **Database provider independent**.
|
||||
* **Web independent**, that means you can create unit of work scopes in any type of applications beside web applications/services.
|
||||
|
||||
## Conventions
|
||||
|
||||
The following method types are considered as a unit of work:
|
||||
|
||||
* ASP.NET Core MVC **Controller Actions**.
|
||||
* ASP.NET Core Razor **Page Handlers**.
|
||||
* **Application service** methods.
|
||||
* **Repository methods**.
|
||||
|
||||
A UOW automatically begins for these methods **except** if there is already a **surrounding (ambient)** UOW in action. Examples;
|
||||
|
||||
* If you call a [repository](Repositories.md) method and there is no UOW started yet, it automatically **begins a new transactional UOW** that involves all the operations done in the repository method and **commits the transaction** if the repository method **doesn't throw any exception.** The repository method doesn't know about UOW or transaction at all. It just works on a regular database objects (`DbContext` for [EF Core](Entity-Framework-Core.md), for example) and the UOW is handled by the ABP Framework.
|
||||
* If you call an [application service](Application-Services.md) method, the same UOW system works just as explained above. If the application service method uses some repositories, the repositories **don't begin a new UOW**, but **participates to the current unit of work** started by the ABP Framework for the application service method.
|
||||
* The same is true for an ASP.NET Core controller action. If the operation has started with a controller action, then the **UOW scope is the controller action's method body**.
|
||||
|
||||
All of these are automatically handled by the ABP Framework.
|
||||
|
||||
### Database Transaction Behavior
|
||||
|
||||
While the section above explains the UOW as it is database transaction, actually a UOW doesn't have to be transactional. By default;
|
||||
|
||||
* **HTTP GET** requests don't start a transactional UOW. They still starts a UOW, but **doesn't create a database transaction**.
|
||||
* All other HTTP request types start a UOW with a database transaction, if database level transactions are supported by the underlying database provider.
|
||||
|
||||
This is because an HTTP GET request doesn't (and shouldn't) make any change in the database. You can change this behavior using the options explained below.
|
||||
|
||||
## Default Options
|
||||
|
||||
`AbpUnitOfWorkDefaultOptions` is used to configure the default options for the unit of work system. Configure the options in the `ConfigureServices` method of your [module](Module-Development-Basics.md).
|
||||
|
||||
**Example: Completely disable the database transactions**
|
||||
|
||||
````csharp
|
||||
Configure<AbpUnitOfWorkDefaultOptions>(options =>
|
||||
{
|
||||
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
|
||||
});
|
||||
````
|
||||
|
||||
### Option Properties
|
||||
|
||||
* `TransactionBehavior` (`enum`: `UnitOfWorkTransactionBehavior`). A global point to configure the transaction behavior. Default value is `Auto` and work as explained in the "*Database Transaction Behavior*" section above. You can enable (even for HTTP GET requests) or disable transactions with this option.
|
||||
* `TimeOut` (`int?`): Used to set the timeout value for UOWs. **Default value is `null`** and uses to the default of the underlying database provider.
|
||||
* `IsolationLevel` (`IsolationLevel?`): Used to set the [isolation level](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) of the database transaction, if the UOW is transactional.
|
||||
|
||||
## Controlling the Unit Of Work
|
||||
|
||||
In some cases, you may want to change the conventional transaction scope, create inner scopes or fine control the transaction behavior. The following sections cover these possibilities.
|
||||
|
||||
### IUnitOfWorkEnabled Interface
|
||||
|
||||
This is an easy way to enable UOW for a class (or a hierarchy of classes) that is not unit of work by the conventions explained above.
|
||||
|
||||
**Example: Implement `IUnitOfWorkEnabled` for an arbitrary service**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyService : ITransientDependency, IUnitOfWorkEnabled
|
||||
{
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
Then `MyService` (and any class derived from it) methods will be UOW.
|
||||
|
||||
However, there are **some rules should be followed** in order to make it working;
|
||||
|
||||
* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work).
|
||||
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted. So, sync methods can not start a UOW.
|
||||
|
||||
> Notice that if `FooAsync` is called inside a UOW scope, then it already participates to the UOW without needing to the `IUnitOfWorkEnabled` or any other configuration.
|
||||
|
||||
### UnitOfWorkAttribute
|
||||
|
||||
`UnitOfWork` attribute provides much more possibility like enabling or disabling UOW and controlling the transaction behavior.
|
||||
|
||||
`UnitOfWork` attribute can be used for a **class** or a **method** level.
|
||||
|
||||
**Example: Enable UOW for a specific method of a class**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyService : ITransientDependency
|
||||
{
|
||||
[UnitOfWork]
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
|
||||
public virtual async Task BarAsync()
|
||||
{
|
||||
//this is a method without UOW
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
**Example: Enable UOW for all the methods of a class**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
[UnitOfWork]
|
||||
public class MyService : ITransientDependency
|
||||
{
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
|
||||
public virtual async Task BarAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
Again, the **same rules** are valid here:
|
||||
|
||||
* If you are **not injecting** the service over an interface (like `IMyService`), then the methods of the service must be `virtual` (otherwise, [dynamic proxy / interception](Dynamic-Proxying-Interceptors.md) system can not work).
|
||||
* Only `async` methods (methods returning a `Task` or `Task<T>`) are intercepted. So, sync methods can not start a UOW.
|
||||
|
||||
#### UnitOfWorkAttribute Properties
|
||||
|
||||
* `IsTransactional` (`bool?`): Used to set whether the UOW should be transactional or not. **Default value is `null`**. if you leave it `null`, it is determined automatically based on the conventions and the configuration.
|
||||
* `TimeOut` (`int?`): Used to set the timeout value for this UOW. **Default value is `null`** and fallbacks to the default configured value.
|
||||
* `IsolationLevel` (`IsolationLevel?`): Used to set the [isolation level](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) of the database transaction, if the UOW is transactional. If not set, uses the default configured value.
|
||||
* `IsDisabled` (`bool`): Used to disable the UOW for the current method/class.
|
||||
|
||||
> If a method is called in an ambient UOW scope, then the `UnitOfWork` attribute is ignored and the method participates to the surrounding transaction in any way.
|
||||
|
||||
**Example: Disable UOW for a controller action**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo.Web
|
||||
{
|
||||
public class MyController : AbpController
|
||||
{
|
||||
[UnitOfWork(IsDisabled = true)]
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//...
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
## IUnitOfWorkManager
|
||||
|
||||
`IUnitOfWorkManager` is the main service that is used to control the unit of work system. The following sections explains how to directly work with this service (while most of the times you won't need).
|
||||
|
||||
### Begin a New Unit Of Work
|
||||
|
||||
`IUnitOfWorkManager.Begin` method is used to create a new UOW scope.
|
||||
|
||||
**Example: Create a new non-transactional UOW scope**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyService : ITransientDependency
|
||||
{
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public MyService(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
using (var uow = _unitOfWorkManager.Begin(
|
||||
requiresNew: true, isTransactional: false
|
||||
))
|
||||
{
|
||||
//...
|
||||
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
`Begin` method gets the following optional parameters:
|
||||
|
||||
* `requiresNew` (`bool`): Set `true` to ignore the surrounding unit of work and start a new UOW with the provided options. **Default value is `false`. If it is `false` and there is a surrounding UOW, `Begin` method doesn't actually begin a new UOW, but silently participates to the existing UOW.**
|
||||
* `isTransactional` (`bool`). Default value is `false`.
|
||||
* `isolationLevel` (`IsolationLevel?`): Used to set the [isolation level](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel) of the database transaction, if the UOW is transactional. If not set, uses the default configured value.
|
||||
* `TimeOut` (`int?`): Used to set the timeout value for this UOW. **Default value is `null`** and fallbacks to the default configured value.
|
||||
|
||||
### The Current Unit Of Work
|
||||
|
||||
UOW is ambient, as explained before. If you need to access to the current unit of work, you can use the `IUnitOfWorkManager.Current` property.
|
||||
|
||||
**Example: Get the current UOW**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyProductService : ITransientDependency
|
||||
{
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public MyProductService(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
public async Task FooAsync()
|
||||
{
|
||||
var uow = _unitOfWorkManager.Current;
|
||||
//...
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
`Current` property returns a `IUnitOfWork` object.
|
||||
|
||||
> **Current Unit Of Work can be `null`** if there is no surrounding unit of work. It won't be `null` if your class is a conventional UOW class, you manually made it UOW or it was called inside a UOW scope, as explained before.
|
||||
|
||||
#### SaveChangesAsync
|
||||
|
||||
`IUnitOfWork.SaveChangesAsync()` method can be needed to save all the changes until now to the database. If you are using EF Core, it behaves exactly same. If the current UOW is transactional, even saved changes can be rolled back on an error (for the supporting database providers).
|
||||
|
||||
**Example: Save changes after inserting an entity to get its auto-increment id**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class CategoryAppService : ApplicationService, ICategoryAppService
|
||||
{
|
||||
private readonly IRepository<Category, int> _categoryRepository;
|
||||
|
||||
public CategoryAppService(IRepository<Category, int> categoryRepository)
|
||||
{
|
||||
_categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(string name)
|
||||
{
|
||||
var category = new Category {Name = name};
|
||||
await _categoryRepository.InsertAsync(category);
|
||||
|
||||
//Saving changes to be able to get the auto increment id
|
||||
await UnitOfWorkManager.Current.SaveChangesAsync();
|
||||
|
||||
return category.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
This example uses auto-increment `int` primary key for the `Category` [entity](Entities.md). Auto-increment PKs require to save the entity to the database to get the id of the new entity.
|
||||
|
||||
This example is an [application service](Application-Services.md) derived from the base `ApplicationService` class, which already has the `IUnitOfWorkManager` service injected as the `UnitOfWorkManager` property. So, no need to inject it manually.
|
||||
|
||||
Since getting the current UOW is pretty common, there is also a `CurrentUnitOfWork` property as a shortcut to the `UnitOfWorkManager.Current`. So, the example above can be changed to use it:
|
||||
|
||||
````csharp
|
||||
await CurrentUnitOfWork.SaveChangesAsync();
|
||||
````
|
||||
|
||||
##### Alternative to the SaveChanges()
|
||||
|
||||
Since saving changes after inserting, updating or deleting an entity can be frequently needed, corresponding [repository](Repositories.md) methods has an optional `autoSave` parameter. So, the `CreateAsync` method above could be re-written as shown below:
|
||||
|
||||
````csharp
|
||||
public async Task<int> CreateAsync(string name)
|
||||
{
|
||||
var category = new Category {Name = name};
|
||||
await _categoryRepository.InsertAsync(category, autoSave: true);
|
||||
return category.Id;
|
||||
}
|
||||
````
|
||||
|
||||
If your intent is just to save the changes after creating/updating/deleting an entity, it is suggested to use the `autoSave` option instead of manually using the `CurrentUnitOfWork.SaveChangesAsync()`.
|
||||
|
||||
> **Note-1**: All changes are automatically saved when a unit of work ends without any error. So, don't call `SaveChangesAsync()` unless you really need it.
|
||||
>
|
||||
> **Note-2**: If you use `Guid` as the primary key, you never need to save changes on insert to just get the generated id, because `Guid` keys are set in the application and are immediately available once you create a new entity.
|
||||
|
||||
#### Other IUnitOfWork Properties/Methods
|
||||
|
||||
* `OnCompleted` method gets a callback action which is called when the unit of work successfully completed (where you can be sure that all changes are saved).
|
||||
* `Failed` and `Disposed` events can be used to be notified if the UOW fails or when it is disposed.
|
||||
* `Complete` and `Rollback` methods are used to complete (commit) or roll backs the current UOW, which are normally used internally by the ABP Framework but can be used if you manually start a transaction using the `IUnitOfWorkManager.Begin` method.
|
||||
* `Options` can be used to get options that was used while starting the UOW.
|
||||
* `Items` dictionary can be used to store and get arbitrary objects inside the same unit of work, which can be a point to implement custom logics.
|
||||
|
||||
## ASP.NET Core Integration
|
||||
|
||||
Unit of work system is fully integrated to the ASP.NET Core. It properly works when you use ASP.NET Core MVC Controllers or Razor Pages. It defines action filters and page filters for the UOW system.
|
||||
|
||||
> You typically do nothing to configure the UOW when you use ASP.NET Core.
|
||||
|
||||
### Unit Of Work Middleware
|
||||
|
||||
`AbpUnitOfWorkMiddleware` is a middleware that can enable UOW in the ASP.NET Core request pipeline. This might be needed if you need to enlarge the UOW scope to cover some other middleware(s).
|
||||
|
||||
**Example:**
|
||||
|
||||
````csharp
|
||||
app.UseUnitOfWork();
|
||||
app.UseConfiguredEndpoints();
|
||||
````
|
||||
|
||||
TODO
|
||||
@ -1,3 +1,355 @@
|
||||
## Unit of Work
|
||||
# 工作单元
|
||||
|
||||
待添加
|
||||
ABP框架的工作单元(UOW)实现提供了对应用程序中的**数据库连接和事务范围**的抽象和控制.
|
||||
|
||||
一旦一个新的UOW启动,它将创建一个**环境作用域**,当前作用域中执行的**所有数据库操作**都将参与该作用域并将其视为单个事务边界. 操作一起**提交**(成功时)或**回滚**(异常时).
|
||||
|
||||
ABP的UOW系统是;
|
||||
|
||||
* **按约定工作**, 所以大部分情况下你不需要处理UOW.
|
||||
* **数据库提供者独立**.
|
||||
* **Web独立**, 这意味着你可以在Web应用程序/服务之外的任何类型的应用程序中创建工作单元作用域.
|
||||
|
||||
## 约定
|
||||
|
||||
以下方法类型被认为是一个工作单元:
|
||||
|
||||
* ASP.NET Core MVC **Controller Actions**.
|
||||
* ASP.NET Core Razor **Page Handlers**.
|
||||
* **应用程序** 方法.
|
||||
* **仓储方法**.
|
||||
|
||||
UOW自动针对这些方法开始,除非**周围已经有一个(环境)**UOW在运行.示例;
|
||||
|
||||
* 如果你调用一个[仓储]方法(Repositories.md),但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理.
|
||||
* 如果调用[应用服务](Application-Services.md)方法,则相同的UOW系统将按上述说明工作. 如果应用服务方法使用某些仓储,这些仓储**不会开始新的UOW**,而是**参与由ABP框架为应用程序服务方法启动的当前工作单元中**.
|
||||
* ASP.NET Core控制器操作也是如此. 如果操作以控制器action开始,**UOW范围是控制器action的方法主体**.
|
||||
|
||||
所有这些都是由ABP框架自动处理的.
|
||||
|
||||
### 数据库事务行为
|
||||
|
||||
虽然上一节解释了UOW是数据库事务,但实际上UOW不必是事务性的. 默认情况下;
|
||||
|
||||
* **HTTP GET**请求不会启动事务性UOW. 它们仍然启动UOW,但**不创建数据库事务**.
|
||||
* 如果底层数据库提供程序支持数据库事务,那么所有其他HTTP请求类型都使用数据库事务启动UOW.
|
||||
|
||||
这是因为HTTP GET请求不会(也不应该)在数据库中进行任何更改. 你可以使用下面解释的选项来更改此行为.
|
||||
|
||||
## 默认选项
|
||||
|
||||
`AbpUnitOfWorkDefaultOptions` 用于配置工作单元系统的默认选项.在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法中配置选项.
|
||||
|
||||
**示例: 完全禁用数据库事务**
|
||||
|
||||
````csharp
|
||||
Configure<AbpUnitOfWorkDefaultOptions>(options =>
|
||||
{
|
||||
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
|
||||
});
|
||||
````
|
||||
|
||||
### 选项属性
|
||||
|
||||
* `TransactionBehavior` (`enum`: `UnitOfWorkTransactionBehavior`). 配置事务行为的全局点. 默认值为 `Auto` ,按照上面"*数据库事务行为"*一节的说明工作. 你可以使用此选项启用(甚至对于HTTP GET请求)或禁用事务.
|
||||
* `TimeOut` (`int?`): 用于设置UOW的超时值. **默认值是 `null`** 并使用基础数据库提供程序的默认值.
|
||||
* `IsolationLevel` (`IsolationLevel?`): 如果UOW是事务性的用于设置数据库事务的[隔离级别](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel).
|
||||
|
||||
## 控制工作单元
|
||||
|
||||
在某些情况下你可能希望更改常规事务作用域,创建内部作用域或精细控制事务行为. 下面几节将介绍这些可能性.
|
||||
|
||||
### IUnitOfWorkEnabled 接口
|
||||
|
||||
这是为不是按照上面解释的约定作为工作单元的类(或类的层次结构)启用UOW的一种简单方法.
|
||||
|
||||
**示例: 为任意服务实现 `IUnitOfWorkEnabled`**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyService : ITransientDependency, IUnitOfWorkEnabled
|
||||
{
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
然后 `MyService`(和它的派生类)方法都将是UOW.
|
||||
|
||||
但是为了使它工作,**有些规则应该被遵守**;
|
||||
|
||||
* 如果你不是通过接口(如`IMyService`)注入服务,则服务的方法必须是 `virtual` 的(否则[动态代理/拦截](Dynamic-Proxying-Interceptors.md)系统将无法工作).
|
||||
* 仅异步方法(返回`Task`或`Task<T>`的方法)被拦截. 因此同步方法无法启动UOW.
|
||||
|
||||
> 注意,如果 `FooAsync` 在UOW作用域内被调用,那么它已经参与了UOW,不需要 `IUnitOfWorkEnabled` 或其他配置.
|
||||
|
||||
### UnitOfWorkAttribute
|
||||
|
||||
`UnitOfWork` attribute提供了更多的可能性,比如启用或禁用UOW和控制事务行为.
|
||||
|
||||
`UnitOfWork` attribute可以用于**类**或**方法**级别.
|
||||
|
||||
**示例: 为类的特定方法启用UOW**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyService : ITransientDependency
|
||||
{
|
||||
[UnitOfWork]
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
|
||||
public virtual async Task BarAsync()
|
||||
{
|
||||
//this is a method without UOW
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
**示例: 为类的所有方法启用UOW**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
[UnitOfWork]
|
||||
public class MyService : ITransientDependency
|
||||
{
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
|
||||
public virtual async Task BarAsync()
|
||||
{
|
||||
//this is a method with a UOW scope
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
**同样的规则**也适用于此:
|
||||
|
||||
* 如果你不是通过接口(如`IMyService`)注入服务,则服务的方法必须是 `virtual` 的(否则[动态代理/拦截](Dynamic-Proxying-Interceptors.md)系统将无法工作).
|
||||
* 仅异步方法(返回`Task`或`Task<T>`的方法)被拦截. 因此同步方法无法启动UOW.
|
||||
|
||||
#### UnitOfWorkAttribute 属性
|
||||
|
||||
* `IsTransactional` (`bool?`): 用于设置UOW是否是事务性的. **默认值为 `null`**. 如果你让它为 `null`,它会通过约定和配置自动确定.
|
||||
* `TimeOut` (`int?`): 用于设置UOW的超时值. **默认值为 `null`**并回退到默认配置值.
|
||||
* `IsolationLevel` (`IsolationLevel?`): 如果UOW是事务的,用于设置数据库事务的[隔离级别](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel). 如果未设置,则使用默认值.
|
||||
* `IsDisabled` (`bool`): 用于禁用当前方法/类的UOW.
|
||||
|
||||
> 如果在环境UOW作用域内调用方法,将忽略 `UnitOfWork` 属性,并且该方法参与周围的事务.
|
||||
|
||||
**示例: 为控制器action禁用UOW**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.AspNetCore.Mvc;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo.Web
|
||||
{
|
||||
public class MyController : AbpController
|
||||
{
|
||||
[UnitOfWork(IsDisabled = true)]
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
//...
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
## IUnitOfWorkManager
|
||||
|
||||
`IUnitOfWorkManager` 是用于控制工作单元系统的主要服务. 下面的部分解释了如何使用此服务(大多数时候你并不需要).
|
||||
|
||||
### 开始新的工作单元
|
||||
|
||||
`IUnitOfWorkManager.Begin` 方法用于创建一个新的UOW作用域.
|
||||
|
||||
**示例: 创建一个新的非事务性UOW作用域**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyService : ITransientDependency
|
||||
{
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public MyService(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
public virtual async Task FooAsync()
|
||||
{
|
||||
using (var uow = _unitOfWorkManager.Begin(
|
||||
requiresNew: true, isTransactional: false
|
||||
))
|
||||
{
|
||||
//...
|
||||
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
`Begin` 方法有以下可选参数:
|
||||
|
||||
* `requiresNew` (`bool`): 设置为 `true` 可忽略周围的工作单元,并使用提供的选项启动新的UOW. **默认值为`false`. 如果为`false`,并且周围有UOW,则 `Begin` 方法实际上不会开始新的UOW,而是以静默方式参与现有的UOW**.
|
||||
* `isTransactional` (`bool`). 默认为 `false`.
|
||||
* `isolationLevel` (`IsolationLevel?`): 如果UOW是事务的,用于设置数据库事务的[隔离级别](https://docs.microsoft.com/en-us/dotnet/api/system.data.isolationlevel). 如果未设置,则使用默认值.
|
||||
* `TimeOut` (`int?`): 用于设置UOW的超时值. **默认值为 `null`**并回退到默认配置值.
|
||||
|
||||
### 当前工作单元
|
||||
|
||||
如上所述UOW是环境的. 如果需要访问当前的工作单元,可以使用 `IUnitOfWorkManager.Current` 属性.
|
||||
|
||||
**示例: 获取当前UOW**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class MyProductService : ITransientDependency
|
||||
{
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public MyProductService(IUnitOfWorkManager unitOfWorkManager)
|
||||
{
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
}
|
||||
|
||||
public async Task FooAsync()
|
||||
{
|
||||
var uow = _unitOfWorkManager.Current;
|
||||
//...
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
`Current` 属性返回一个 `IUnitOfWork` 对象.
|
||||
|
||||
> 如果没有周围的工作单元,则**当前工作单元可以为`null`**. 如上所述,如果你的类是常规的UOW类,你将其手动设置为UOW或在UOW作用域内调用它,那么该值就不会为 `null`.
|
||||
|
||||
#### SaveChangesAsync
|
||||
|
||||
`IUnitOfWork.SaveChangesAsync()` 方法将到目前为止的所有更改保存到数据库中. 如果你正在使用EF Core,它的行为完全相同. 如果当前UOW是事务性的,即使已保存的更改也可以在错误时回滚(对于支持的数据库提供程序).
|
||||
|
||||
**示例: 插入实体后保存更改以获取其自动增量ID**
|
||||
|
||||
````csharp
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace AbpDemo
|
||||
{
|
||||
public class CategoryAppService : ApplicationService, ICategoryAppService
|
||||
{
|
||||
private readonly IRepository<Category, int> _categoryRepository;
|
||||
|
||||
public CategoryAppService(IRepository<Category, int> categoryRepository)
|
||||
{
|
||||
_categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(string name)
|
||||
{
|
||||
var category = new Category {Name = name};
|
||||
await _categoryRepository.InsertAsync(category);
|
||||
|
||||
//Saving changes to be able to get the auto increment id
|
||||
await UnitOfWorkManager.Current.SaveChangesAsync();
|
||||
|
||||
return category.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
示例的 `Category` [实体](Entities.md)使用自动递增的 `int` 主键. 自动增量PK需要将实体保存到数据库中来获得新实体的ID.
|
||||
|
||||
示例是从基类 `ApplicationService` 派生的[应用服务](Application-Services.md), `IUnitOfWorkManager` 服务已经作为 `UnitOfWorkManager` 属性注入,所以无需手动注入.
|
||||
|
||||
获取当前UOW非常常见,所以还有一个 `UnitOfWorkManager.Current` 的快捷属性 `CurrentUnitOfWork`. 所以可以对上面的例子进行以下更改:
|
||||
|
||||
````csharp
|
||||
await CurrentUnitOfWork.SaveChangesAsync();
|
||||
````
|
||||
|
||||
##### SaveChanges() 的替代方法
|
||||
|
||||
由于经常需要在插入,更新或删除实体后保存更改,相应的[仓储](Repositories.md)方法有一个可选的 `autoSave` 参数. 可以将上面的 `CreateAsync` 方法按如下重写:
|
||||
|
||||
````csharp
|
||||
public async Task<int> CreateAsync(string name)
|
||||
{
|
||||
var category = new Category {Name = name};
|
||||
await _categoryRepository.InsertAsync(category, autoSave: true);
|
||||
return category.Id;
|
||||
}
|
||||
````
|
||||
|
||||
如果你的目的只是在创建/更新/删除实体后保存更改,建议你使用 `autoSave` 选项,而不是手动使用 `CurrentUnitOfWork.SaveChangesAsync()`.
|
||||
|
||||
> **Note-1**: 当工作单元结束而没有任何错误时,所有更改都会自动保存. 所以除非确实需要,否则不要调用 `SaveChangesAsync()`.
|
||||
>
|
||||
> **Note-2**: 如果你使用 `Guid` 作为主键,则无需插入时保存来获取生成的id,因为 `Guid` 主键是在应用程序中设置的,创建新实体后立即可用.
|
||||
|
||||
#### IUnitOfWork 其他属性/方法
|
||||
|
||||
* `OnCompleted` 方法获得一个回调动作,当工作单元成功完成时调用(在这里你可以确保所有更改都保存了).
|
||||
* `Failed` 和 `Disposed` 事件可以用于UOW失败和被销毁的通知.
|
||||
* `Complete` 和 `Rollback` 方法用于完成(提交)或回滚当前 UOW, 通常ABP框架在内部使用,如果你使用 `IUnitOfWorkManager.Begin` 方法手动启动事务,那么你可以手动使用这些方法.
|
||||
* `Options` 可用于获取启动UOW时使用的选项.
|
||||
* `Items` 字典可用于在同一工作单元内存储和获取任意对象,可以实现自定义逻辑.
|
||||
|
||||
## ASP.NET Core 集成
|
||||
|
||||
工作单元系统已完全集成到ASP.NET Core. 它为UOW系统定义了动作过滤器和页面过滤器. 当你使用ASP.NET Core MVC控制器或Razor页面时,它可以正常工作.
|
||||
|
||||
> 使用ASP.NET Core时,通常你不需要做任何操作配置UOW.
|
||||
|
||||
### 工作单元中间件
|
||||
|
||||
`AbpUnitOfWorkMiddleware` 是可以在ASP.NET Core请求管道中启用UOW的中间件. 如果你需要扩大UOW范围以涵盖其他一些中间件,可以这样做.
|
||||
|
||||
**示例:**
|
||||
|
||||
````csharp
|
||||
app.UseUnitOfWork();
|
||||
app.UseConfiguredEndpoints();
|
||||
````
|
||||
@ -1,25 +1,398 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Volo.Abp.Linq
|
||||
{
|
||||
public interface IAsyncQueryableExecuter
|
||||
{
|
||||
#region Contains
|
||||
|
||||
Task<bool> ContainsAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] T item,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Any/All
|
||||
|
||||
Task<bool> AnyAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> AllAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Count/LongCount
|
||||
|
||||
Task<int> CountAsync<T>(
|
||||
IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<int> CountAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<long> LongCountAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<long> LongCountAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region First/FirstOrDefault
|
||||
|
||||
Task<T> FirstAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T> FirstAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<T>> ToListAsync<T>(
|
||||
IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
Task<T> FirstOrDefaultAsync<T>(
|
||||
IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T> FirstOrDefaultAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Last/LastOrDefault
|
||||
|
||||
Task<T> LastAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T> LastAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T> LastOrDefaultAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T> LastOrDefaultAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Single/SingleOrDefault
|
||||
|
||||
Task<T> SingleAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T> SingleAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<T> SingleOrDefaultAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<T> SingleOrDefaultAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, bool>> predicate,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Min
|
||||
|
||||
Task<T> MinAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<TResult> MinAsync<T, TResult>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, TResult>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Max
|
||||
|
||||
Task<T> MaxAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<TResult> MaxAsync<T, TResult>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, TResult>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sum
|
||||
|
||||
Task<decimal> SumAsync(
|
||||
[NotNull] IQueryable<decimal> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<decimal?> SumAsync(
|
||||
[NotNull] IQueryable<decimal?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<decimal> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, decimal>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<decimal?> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, decimal?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<int> SumAsync(
|
||||
[NotNull] IQueryable<int> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<int?> SumAsync(
|
||||
[NotNull] IQueryable<int?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<int> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, int>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<int?> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, int?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<long> SumAsync(
|
||||
[NotNull] IQueryable<long> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<long?> SumAsync(
|
||||
[NotNull] IQueryable<long?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<long> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, long>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<long?> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, long?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> SumAsync(
|
||||
[NotNull] IQueryable<double> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> SumAsync(
|
||||
[NotNull] IQueryable<double?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, double>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, double?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float> SumAsync(
|
||||
[NotNull] IQueryable<float> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float?> SumAsync(
|
||||
[NotNull] IQueryable<float?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, float>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float?> SumAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, float?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Average
|
||||
|
||||
Task<decimal> AverageAsync(
|
||||
[NotNull] IQueryable<decimal> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<decimal?> AverageAsync(
|
||||
[NotNull] IQueryable<decimal?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<decimal> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, decimal>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<decimal?> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, decimal?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> AverageAsync(
|
||||
[NotNull] IQueryable<int> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> AverageAsync(
|
||||
[NotNull] IQueryable<int?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, int>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<double?> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, int?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<double> AverageAsync(
|
||||
[NotNull] IQueryable<long> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> AverageAsync(
|
||||
[NotNull] IQueryable<long?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, long>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, long?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> AverageAsync(
|
||||
[NotNull] IQueryable<double> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> AverageAsync(
|
||||
[NotNull] IQueryable<double?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, double>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<double?> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, double?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float> AverageAsync(
|
||||
[NotNull] IQueryable<float> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float?> AverageAsync(
|
||||
[NotNull] IQueryable<float?> source,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, float>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<float?> AverageAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
[NotNull] Expression<Func<T, float?>> selector,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region ToList/Array
|
||||
|
||||
Task<List<T>> ToListAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
Task<T[]> ToArrayAsync<T>(
|
||||
[NotNull] IQueryable<T> queryable,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\..\configureawait.props" />
|
||||
<Import Project="..\..\..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyName>Volo.Blogging.Admin.Application.Contracts</AssemblyName>
|
||||
<PackageId>Volo.Blogging.Admin.Application.Contracts</PackageId>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Volo.Blogging.Domain.Shared\Volo.Blogging.Domain.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\cs.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\de.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\en.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\nl.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\pl-PL.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\pt-BR.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\sl.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\tr.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\vi.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\zh-Hans.json" />
|
||||
<EmbeddedResource Include="Volo\Blogging\Admin\Localization\Resources\Blogging\Admin\ApplicationContracts\zh-Hant.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Volo.Blogging.Admin.Blogs
|
||||
{
|
||||
public class BlogDto : FullAuditedEntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string ShortName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "cs",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogy",
|
||||
"Permission:Management": "Správa",
|
||||
"Permission:Edit": "Upravit",
|
||||
"Permission:Create": "Vytvořit",
|
||||
"Permission:Delete": "Smazat"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "de",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogs",
|
||||
"Permission:Management": "Verwaltung",
|
||||
"Permission:Edit": "Bearbeiten",
|
||||
"Permission:Create": "Erstellen",
|
||||
"Permission:Delete": "Löschen"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "en",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blogging",
|
||||
"Permission:Blogs": "Blogs",
|
||||
"Permission:Management": "Management",
|
||||
"Permission:Edit": "Edit",
|
||||
"Permission:Create": "Create",
|
||||
"Permission:Delete": "Delete"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "nl",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogs",
|
||||
"Permission:Management": "Beheer",
|
||||
"Permission:Edit": "Bewerk",
|
||||
"Permission:Create": "Maak aan",
|
||||
"Permission:Delete": "Verwijder"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "pl-PL",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogi",
|
||||
"Permission:Management": "Zarządzanie",
|
||||
"Permission:Edit": "Edytuj",
|
||||
"Permission:Create": "Utwórz",
|
||||
"Permission:Delete": "Usuń"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "pt-BR",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogs",
|
||||
"Permission:Management": "Gerenciamento",
|
||||
"Permission:Edit": "Editar",
|
||||
"Permission:Create": "Criar",
|
||||
"Permission:Delete": "Excluir"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "sl",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogi",
|
||||
"Permission:Management": "Upravljanje",
|
||||
"Permission:Edit": "Urejanje",
|
||||
"Permission:Create": "Ustvarjanje",
|
||||
"Permission:Delete": "Brisanje"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "vi",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "Blog",
|
||||
"Permission:Blogs": "Blogs",
|
||||
"Permission:Management": "Quản lý",
|
||||
"Permission:Edit": "Sửa",
|
||||
"Permission:Create": "Tạo",
|
||||
"Permission:Delete": "Xóa"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "zh-Hans",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "博客",
|
||||
"Permission:Blogs": "博客",
|
||||
"Permission:Management": "管理",
|
||||
"Permission:Edit": "编辑",
|
||||
"Permission:Create": "创建",
|
||||
"Permission:Delete": "删除"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"culture": "zh-Hant",
|
||||
"texts": {
|
||||
"Permission:BloggingAdmin": "部落格",
|
||||
"Permission:Blogs": "部落格",
|
||||
"Permission:Management": "管理",
|
||||
"Permission:Edit": "標及",
|
||||
"Permission:Create": "新增",
|
||||
"Permission:Delete": "刪除"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\..\configureawait.props" />
|
||||
<Import Project="..\..\..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyName>Volo.Blogging.Admin.Application</AssemblyName>
|
||||
<PackageId>Volo.Blogging.Admin.Application</PackageId>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
|
||||
<ProjectReference Include="..\Volo.Blogging.Admin.Application.Contracts\Volo.Blogging.Admin.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Volo.Blogging.Domain\Volo.Blogging.Domain.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\..\configureawait.props" />
|
||||
<Import Project="..\..\..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyName>Volo.Blogging.Admin.HttpApi.Client</AssemblyName>
|
||||
<PackageId>Volo.Blogging.Admin.HttpApi.Client</PackageId>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Volo.Blogging.Admin.Application.Contracts\Volo.Blogging.Admin.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\..\configureawait.props" />
|
||||
<Import Project="..\..\..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Volo.Blogging.Admin.HttpApi</AssemblyName>
|
||||
<PackageId>Volo.Blogging.Admin.HttpApi</PackageId>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Volo.Blogging.Admin.Application.Contracts\Volo.Blogging.Admin.Application.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,5 @@
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
|
||||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
|
||||
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
|
||||
@addTagHelper *, Volo.Blogging.Web
|
||||
@ -0,0 +1,27 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:50000/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Volo.Blogging.Web": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:50014/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Import Project="..\..\..\..\configureawait.props" />
|
||||
<Import Project="..\..\..\..\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Volo.Blogging.Admin.Web</AssemblyName>
|
||||
<PackageId>Volo.Blogging.Admin.Web</PackageId>
|
||||
<TypeScriptToolsVersion>2.8</TypeScriptToolsVersion>
|
||||
<OutputType>Library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
<RootNamespace>Volo.Blogging.Admin</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Packages\Volo.Abp.AspNetCore.Mvc.UI.Packages.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc.UI.Bundling\Volo.Abp.AspNetCore.Mvc.UI.Bundling.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
|
||||
<ProjectReference Include="..\Volo.Blogging.Admin.HttpApi\Volo.Blogging.Admin.HttpApi.csproj" />
|
||||
<PackageReference Include="Markdig.Signed" Version="0.18.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Pages\**\*.js" />
|
||||
<EmbeddedResource Include="Pages\**\*.css" />
|
||||
<Content Remove="Pages\**\*.js" />
|
||||
<Content Remove="Pages\**\*.css" />
|
||||
<Content Remove="compilerconfig.json" />
|
||||
<Content Remove="Properties\launchSettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,49 @@
|
||||
{
|
||||
"compilers": {
|
||||
"less": {
|
||||
"autoPrefix": "",
|
||||
"cssComb": "none",
|
||||
"ieCompat": true,
|
||||
"strictMath": false,
|
||||
"strictUnits": false,
|
||||
"relativeUrls": true,
|
||||
"rootPath": "",
|
||||
"sourceMapRoot": "",
|
||||
"sourceMapBasePath": "",
|
||||
"sourceMap": false
|
||||
},
|
||||
"sass": {
|
||||
"includePath": "",
|
||||
"indentType": "space",
|
||||
"indentWidth": 2,
|
||||
"outputStyle": "nested",
|
||||
"Precision": 5,
|
||||
"relativeUrls": true,
|
||||
"sourceMapRoot": "",
|
||||
"sourceMap": false
|
||||
},
|
||||
"stylus": {
|
||||
"sourceMap": false
|
||||
},
|
||||
"babel": {
|
||||
"sourceMap": false
|
||||
},
|
||||
"coffeescript": {
|
||||
"bare": false,
|
||||
"runtimeMode": "node",
|
||||
"sourceMap": false
|
||||
}
|
||||
},
|
||||
"minifiers": {
|
||||
"css": {
|
||||
"enabled": true,
|
||||
"termSemicolons": true,
|
||||
"gzip": false
|
||||
},
|
||||
"javascript": {
|
||||
"enabled": true,
|
||||
"termSemicolons": true,
|
||||
"gzip": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue