mirror of https://github.com/abpframework/abp.git
committed by
GitHub
49 changed files with 926 additions and 178 deletions
@ -0,0 +1,104 @@ |
|||
# Distributed Locking |
|||
Distributed locking is a technique to manage many applications that try to access the same resource. |
|||
The main purpose is to allow only one of many applications to access the same resource at the same time. |
|||
Otherwise, accessing the same object from various applications may corrupt the value of resources. |
|||
|
|||
> ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library. |
|||
|
|||
## Installation |
|||
|
|||
You can open a command-line terminal and type the following command to install the [Volo.Abp.DistributedLocking](https://www.nuget.org/packages/Volo.Abp.DistributedLocking) package into your project: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.DistributedLocking |
|||
```` |
|||
|
|||
This package provides the necessary API to use the distributed locking system, however, you should configure a provider before using it. |
|||
|
|||
### Configuring a provider |
|||
|
|||
The [DistributedLock](https://github.com/madelson/DistributedLock) library provides [various of implementations](https://github.com/madelson/DistributedLock#implementations) for the locking, like [Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md) and [ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md). |
|||
|
|||
For example, if you want to use the [Redis provider](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md), you should add [DistributedLock.Redis](https://www.nuget.org/packages/DistributedLock.Redis) NuGet package to your project, then add the following code into the `ConfigureServices` method of your ABP [module](Module-Development-Basics.md) class: |
|||
|
|||
````csharp |
|||
using Medallion.Threading; |
|||
using Medallion.Threading.Redis; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class MyModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
context.Services.AddSingleton<IDistributedLockProvider>(sp => |
|||
{ |
|||
var connection = ConnectionMultiplexer |
|||
.Connect(configuration["Redis:Configuration"]); |
|||
return new |
|||
RedisDistributedSynchronizationProvider(connection.GetDatabase()); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
This code gets the Redis connection string from the [configuration](Configuration.md), so you can add the following lines to your `appsettings.json` file: |
|||
|
|||
````json |
|||
"Redis": { |
|||
"Configuration": "127.0.0.1" |
|||
} |
|||
```` |
|||
|
|||
## Usage |
|||
|
|||
There are two ways to use the distributed locking API: ABP's `IAbpDistributedLock` abstraction and [DistributedLock](https://github.com/madelson/DistributedLock) library's API. |
|||
|
|||
### Using the IAbpDistributedLock service |
|||
|
|||
`IAbpDistributedLock` is a simple service provided by the ABP framework for simple usage of distributed locking. |
|||
|
|||
**Example: Using the `IAbpDistributedLock.TryAcquireAsync` method** |
|||
|
|||
````csharp |
|||
using Volo.Abp.DistributedLocking; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IAbpDistributedLock _distributedLock; |
|||
public MyService(IAbpDistributedLock distributedLock) |
|||
{ |
|||
_distributedLock = distributedLock; |
|||
} |
|||
|
|||
public async Task MyMethodAsync() |
|||
{ |
|||
await using (var handle = |
|||
await _distributedLock.TryAcquireAsync("MyLockName")) |
|||
{ |
|||
if (handle != null) |
|||
{ |
|||
// your code that access the shared resource |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`TryAcquireAsync` may not acquire the lock. It returns `null` if the lock could not be acquired. In this case, you shouldn't access the resource. If the handle is not `null`, it means that you've obtained the lock and can safely access the resource. |
|||
|
|||
`TryAcquireAsync` method gets the following parameters: |
|||
|
|||
* `name` (`string`, required): Unique name of your lock. Different named locks are used to access different resources. |
|||
* `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application. |
|||
* `cancellationToken`: A cancellation token that can be triggered later to cancel the operation. |
|||
|
|||
### Using DistributedLock library's API |
|||
|
|||
ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details. |
|||
@ -1,3 +1,52 @@ |
|||
# ABP Version 5.2 Migration Guide |
|||
|
|||
TODO |
|||
This document is a guide for upgrading ABP v5.x solutions to ABP v5.2. Please read them all since v5.2 has some changes you should take care. |
|||
|
|||
|
|||
## MongoDB |
|||
|
|||
- `IMongoDbRepositoryFilterer.AddGlobalFilters()` method is replaced with async one `IMongoDbRepositoryFilterer.AddGlobalFiltersAsync()` |
|||
|
|||
## Blazor UI |
|||
If you use Blazor WASM or Blazor Server UI, you should follow this section. |
|||
|
|||
### Blazorise 1.0 |
|||
We've upgraded to Blazorise 1.0 stable version. So there is some breaking changes that you have to apply in your project. |
|||
|
|||
Also You can review that pull request [#11649 - Blazorise 1.0 Migration](https://github.com/abpframework/abp/pull/11649) |
|||
|
|||
- `NumericEdit` is now made around the native `input type="number"` so a lot of its formating features are moved to the new `NumericPicker` component. Replace NumericEdit with NumericPicker. |
|||
- Rename `DecimalsSeparator` to `DecimalSeparator` on the `DataGridColumn` and `NumericPicker`. |
|||
- Rename `MaxMessageSize` to `MaxChunkSize`. |
|||
- Remove `Fullscreen` parameter on `<ModalContent>` and replace it with `Size="ModalSize.Fullscreen"` parameter. |
|||
- Remove `NotificationType`, `Message`, and `Title` parameter from `<NotificationAlert>` component. |
|||
- Move `RightAligned` parameter from `<BarDropdownMenu>` to `<BarDropdown>` component. |
|||
- Rename any usage of the `ChangeTextOnKeyPress` parameter into `Immediate`. |
|||
- Rename any usage of `DelayTextOnKeyPress` parameter into `Debounce` and `DelayTextOnKeyPressInterval` into DebounceInterval. |
|||
- Replace all `Left` and `Right` enums with `Start` and `End` for the following enum types: `Direction`, `Float`, `Placement`, `NotificationLocation`, `Side`, `SnackbarLocation`, `SnackbarStackLocation`, `TabPosition`, and `TextAlignment`. |
|||
- Replace all `FromLeft`, `FromRight`, `RoundedLeft`, and `RoundedRight` enums with `FromStart`, `FromEnd`, `RoundedStart`, and `RoundedEnd` for the `Border` utilities. |
|||
- Replace all `FromLeft` and `FromRight` with `FromStart`, `FromEnd` for the Margin and `Padding` utilities. |
|||
- Replace all `AddLabel` with `AddLabels` method on chart instance. |
|||
- Change enum value from `None` to `Default` for the following enum types: `Color`, `Background`, `TextColor`, `Alignment`, `BorderRadius`, `BorderSize`, `Direction`, `DisplayDirection`, `FigureSize`, `IconSize`, `JustifyContent`, `OverflowType`, `SnackbarColor`, `Target`, `TextAlignment`, `TextOverflow`, `TextTransform`, `TextWeight`, `VerticalAlignment`, `Visibility`, `Size`, and `SnackbarLocation`. |
|||
- Obsolete typography parameters `Alignment`, `Color`, `Transform`, and `Weight` are removed in favor of `TextAlignment`, `TextColor`, `TextTransform`, and `TextWeight`. |
|||
- Remove any use of an obsolete component `<InlineField>`. |
|||
- The Datagrid's obsolete `Direction` parameter has now been removed. Instead, please use the `SortDirection` parameter if you weren't already.. |
|||
- Rename `<Tabs>` `Mode` parameter into `RenderMode`. |
|||
|
|||
> _Check out [Blazorise Release Notes](https://preview.blazorise.com/news/release-notes/100) for more information._ |
|||
|
|||
## MVC - Razor Pages UI |
|||
|
|||
If you use MVC Razor Pages UI, you should follow this section. |
|||
|
|||
### Client libraries |
|||
The `libs` folder no longer exists in templates after v5.2. That change greatly reduced the size of templates and brought some other advantages. |
|||
|
|||
You can use `abp install-libs` command for installing or updating client libraries. You should run this command after updating v5.2. |
|||
|
|||
> If you're creating a new project, you don't have to be concerned about it, ABP CLI installs client libraries after automatically. |
|||
|
|||
## See Also |
|||
|
|||
* [Official blog post for the 5.2 release](https://blog.abp.io/abp/ABP.IO-Platform-5-2-RC-Has-Been-Published) |
|||
|
|||
|
|||
@ -0,0 +1,71 @@ |
|||
# ASP.NET Core MVC / Razor Pages: Auto-Complete Select |
|||
A simple select component sometimes isn't useful with a huge amount of data. ABP Provides a select implementation that works with pagination and server-side search via using [Select2](https://select2.org/). It works with single or multiple choices well. |
|||
|
|||
A screenshot can be shown below. |
|||
|
|||
| Single | Multiple | |
|||
| --- | --- | |
|||
|  | | |
|||
|
|||
## Getting Started |
|||
|
|||
This is a core feature and it's used by the ABP Framework. There is no custom installation or additional packages required. |
|||
|
|||
## Usage |
|||
|
|||
A simple usage is presented below. |
|||
|
|||
```html |
|||
<select asp-for="Book.AuthorId" |
|||
class="auto-complete-select" |
|||
data-autocomplete-api-url="/api/app/author" |
|||
data-autocomplete-display-property="name" |
|||
data-autocomplete-value-property="id" |
|||
data-autocomplete-items-property="items" |
|||
data-autocomplete-filter-param-name="filter"> |
|||
|
|||
<!-- You can define selected option(s) here --> |
|||
<option selected value="@SelectedAuthor.Id">@SelectedAuthor.Name</option> |
|||
</select> |
|||
``` |
|||
|
|||
The select must have the `auto-complete-select` class and the following attributes: |
|||
|
|||
- `data-autocomplete-api-url`: * API Endpoint url to get select items. **GET** request will be sent to this url. |
|||
- `data-autocomplete-display-property`: * Property name to display. _(For example: `name` or `title`. Property name of entity/dto.)_. |
|||
- `data-autocomplete-value-property`: * Identifier property name. _(For example: `id`)_. |
|||
- `data-autocomplete-items-property`: * Property name of collection in response object. _(For example: `items`)_ |
|||
- `data-autocomplete-filter-param-name`: * Filter text property name. _(For example: `filter`)_. |
|||
- `data-autocomplete-selected-item-name`: Text to display as selected item. |
|||
- `data-autocomplete-parent-selector`: jQuery selector expression for parent DOM. _(If it's in a modal, it's suggested to send the modal selector as this parameter)_. |
|||
|
|||
Also, selected value(s) should be defined with the `<option>` tags inside select, since pagination is applied and the selected options might haven't loaded yet. |
|||
|
|||
|
|||
### Multiple Choices |
|||
AutoComplete Select supports multiple choices. If the select tag has a `multiple` attribute, it'll allow to choose multiple options. |
|||
|
|||
```html |
|||
<select asp-for="Book.TagIds" |
|||
class="auto-complete-select" |
|||
multiple="multiple" |
|||
data-autocomplete-api-url="/api/app/tags" |
|||
data-autocomplete-display-property="name" |
|||
data-autocomplete-value-property="id" |
|||
data-autocomplete-items-property="items" |
|||
data-autocomplete-filter-param-name="filter"> |
|||
@foreach(var tag in SelectedTags) |
|||
{ |
|||
<option selected value="@tag.Id">@tag.Name</option> |
|||
} |
|||
</select> |
|||
``` |
|||
|
|||
It'll be automatically bound to a collection of defined value type. |
|||
```csharp |
|||
public List<Guid> TagIds { get; set; } |
|||
``` |
|||
|
|||
## Notices |
|||
If the authenticated user doesn't have permission on the given URL, the user will get an authorization error. Be careful while designing this kind of UIs. |
|||
You can create a specific, [unauthorized](../../Authorization.md) endpoint/method to get the list of items, so the page can retrieve lookup data of dependent entity without giving the entire read permission to users. |
|||
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@ -1,3 +1,55 @@ |
|||
# Background Jobs Module |
|||
# 后台作业模块 |
|||
|
|||
待添加 |
|||
后台作业模块实现了 `IBackgroundJobStore` 接口,并且可以使用ABP框架的默认后台作业管理.如果你不想使用这个模块,那么你需要自己实现 `IBackgroundJobStore` 接口. |
|||
|
|||
> 本文档仅介绍后台作业模块,该模块将后台作业持久化到数据库.有关后台作业系统的更多信息,请参阅[后台作业](../Background-Jobs.md)文档. |
|||
|
|||
## 如何使用 |
|||
|
|||
当你使用ABP框架[创建一个新的解决方案](https://abp.io/get-started)时,这个模块是(作为NuGet/NPM包)预先安装的.你可以继续将其作为软件包使用并轻松获取更新,也可以将其源代码包含到解决方案中(请参阅 `get-source` [CLI](../CLI.md)命令)以开发自定义模块. |
|||
|
|||
### 源代码 |
|||
|
|||
此模块的源代码可在[此处](https://github.com/abpframework/abp/tree/dev/modules/background-jobs)访问.源代码是由[MIT](https://choosealicense.com/licenses/mit/)授权的,所以你可以自由使用和定制它. |
|||
|
|||
## 内部结构 |
|||
|
|||
### 领域层 |
|||
|
|||
#### 聚合 |
|||
|
|||
- `BackgroundJobRecord` (聚合根): 表示后台工作记录. |
|||
|
|||
#### 仓储 |
|||
|
|||
为该模块定义了以下自定义仓储: |
|||
|
|||
- `IBackgroundJobRepository` |
|||
|
|||
### 数据库提供程序 |
|||
|
|||
#### 通用 |
|||
|
|||
##### 表/集合的前缀与架构 |
|||
|
|||
默认情况下,所有表/集合都使用 `Abp` 前缀.如果需要更改表前缀或设置架构名称(如果数据库提供程序支持),请在 `BackgroundJobsDbProperties` 类上设置静态属性. |
|||
|
|||
##### 连接字符串 |
|||
|
|||
此模块使用 `AbpBackgroundJobs` 作为连接字符串名称.如果不使用此名称定义连接字符串,它将返回 `Default` 连接字符串.有关详细信息,请参阅[连接字符串](https://docs.abp.io/en/abp/latest/Connection-Strings)文档. |
|||
|
|||
#### Entity Framework Core |
|||
|
|||
##### 表 |
|||
|
|||
- **AbpBackgroundJobs** |
|||
|
|||
#### MongoDB |
|||
|
|||
##### 集合 |
|||
|
|||
- **AbpBackgroundJobs** |
|||
|
|||
## 另请参阅 |
|||
|
|||
* [后台作业系统](../Background-Jobs.md) |
|||
|
|||
@ -1,3 +1,257 @@ |
|||
## 规约 |
|||
|
|||
TODO.. |
|||
规约模式用于为实体和其他业务对象定义 **命名、可复用、可组合和可测试的过滤器** . |
|||
|
|||
> 规约是领域层的一部分. |
|||
|
|||
## 安装 |
|||
|
|||
> 这个包 **已经安装** 在启动模板中.所以,大多数时候你不需要手动去安装. |
|||
|
|||
添加 [Volo.Abp.Specifications](https://abp.io/package-detail/Volo.Abp.Specifications) 包到你的项目. 如果当前文件夹是你的项目的根目录(`.csproj`)时,你可以在命令行终端中使用 [ABP CLI](CLI.md) *add package* 命令: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.Specifications |
|||
```` |
|||
|
|||
## 定义规约 |
|||
|
|||
假设你定义了如下的顾客实体: |
|||
|
|||
````csharp |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class Customer : AggregateRoot<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public byte Age { get; set; } |
|||
|
|||
public long Balance { get; set; } |
|||
|
|||
public string Location { get; set; } |
|||
} |
|||
} |
|||
```` |
|||
|
|||
你可以创建一个由 `Specification<Customer>` 派生的新规约类. |
|||
|
|||
**例如:规定选择一个18岁以上的顾客** |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Linq.Expressions; |
|||
using Volo.Abp.Specifications; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class Age18PlusCustomerSpecification : Specification<Customer> |
|||
{ |
|||
public override Expression<Func<Customer, bool>> ToExpression() |
|||
{ |
|||
return c => c.Age >= 18; |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
你只需通过定义一个lambda[表达式](https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions)来定义规约. |
|||
|
|||
> 你也可以直接实现`ISpecification<T>`接口,但是基类`Specification<T>`做了大量简化. |
|||
|
|||
## 使用规约 |
|||
|
|||
这里有两种常见的规约用例. |
|||
|
|||
### IsSatisfiedBy |
|||
|
|||
`IsSatisfiedBy` 方法可以用于检查单个对象是否满足规约. |
|||
|
|||
**例如:如果顾客不满足年龄规定,则抛出异常** |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class CustomerService : ITransientDependency |
|||
{ |
|||
public async Task BuyAlcohol(Customer customer) |
|||
{ |
|||
if (!new Age18PlusCustomerSpecification().IsSatisfiedBy(customer)) |
|||
{ |
|||
throw new Exception( |
|||
"这位顾客不满足年龄规定!" |
|||
); |
|||
} |
|||
|
|||
//TODO... |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
### ToExpression & Repositories |
|||
|
|||
`ToExpression()` 方法可用于将规约转化为表达式.通过这种方式,你可以使用规约在**数据库查询时过滤实体**. |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Domain.Services; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class CustomerManager : DomainService, ITransientDependency |
|||
{ |
|||
private readonly IRepository<Customer, Guid> _customerRepository; |
|||
|
|||
public CustomerManager(IRepository<Customer, Guid> customerRepository) |
|||
{ |
|||
_customerRepository = customerRepository; |
|||
} |
|||
|
|||
public async Task<List<Customer>> GetCustomersCanBuyAlcohol() |
|||
{ |
|||
var queryable = await _customerRepository.GetQueryableAsync(); |
|||
var query = queryable.Where( |
|||
new Age18PlusCustomerSpecification().ToExpression() |
|||
); |
|||
|
|||
return await AsyncExecuter.ToListAsync(query); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> 规约被正确地转换为SQL/数据库查询语句,并且在DBMS端高效执行.虽然它与规约无关,但如果你想了解有关 `AsyncExecuter` 的更多信息,请参阅[仓储](Repositories.md)文档. |
|||
|
|||
实际上,没有必要使用 `ToExpression()` 方法,因为规约会自动转换为表达式.这也会起作用: |
|||
|
|||
````csharp |
|||
var queryable = await _customerRepository.GetQueryableAsync(); |
|||
var query = queryable.Where( |
|||
new Age18PlusCustomerSpecification() |
|||
); |
|||
```` |
|||
|
|||
## 编写规约 |
|||
|
|||
规约有一个强大的功能是,它们可以与`And`、`Or`、`Not`以及`AndNot`扩展方法组合使用. |
|||
|
|||
假设你有另一个规约,定义如下: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Linq.Expressions; |
|||
using Volo.Abp.Specifications; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class PremiumCustomerSpecification : Specification<Customer> |
|||
{ |
|||
public override Expression<Func<Customer, bool>> ToExpression() |
|||
{ |
|||
return (customer) => (customer.Balance >= 100000); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
你可以将 `PremiumCustomerSpecification` 和 `Age18PlusCustomerSpecification` 结合起来,查询优质成人顾客的数量,如下所示: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Domain.Services; |
|||
using Volo.Abp.Specifications; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class CustomerManager : DomainService, ITransientDependency |
|||
{ |
|||
private readonly IRepository<Customer, Guid> _customerRepository; |
|||
|
|||
public CustomerManager(IRepository<Customer, Guid> customerRepository) |
|||
{ |
|||
_customerRepository = customerRepository; |
|||
} |
|||
|
|||
public async Task<int> GetAdultPremiumCustomerCountAsync() |
|||
{ |
|||
return await _customerRepository.CountAsync( |
|||
new Age18PlusCustomerSpecification() |
|||
.And(new PremiumCustomerSpecification()).ToExpression() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
如果你想让这个组合成为一个可复用的规约,你可以创建这样一个组合的规约类,它派生自`AndSpecification`: |
|||
|
|||
````csharp |
|||
using Volo.Abp.Specifications; |
|||
|
|||
namespace MyProject |
|||
{ |
|||
public class AdultPremiumCustomerSpecification : AndSpecification<Customer> |
|||
{ |
|||
public AdultPremiumCustomerSpecification() |
|||
: base(new Age18PlusCustomerSpecification(), |
|||
new PremiumCustomerSpecification()) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
现在,你就可以向下面一样重新编写 `GetAdultPremiumCustomerCountAsync` 方法: |
|||
|
|||
````csharp |
|||
public async Task<int> GetAdultPremiumCustomerCountAsync() |
|||
{ |
|||
return await _customerRepository.CountAsync( |
|||
new AdultPremiumCustomerSpecification() |
|||
); |
|||
} |
|||
```` |
|||
|
|||
> 你可以从这些例子中看到规约的强大之处.如果你之后想要更改 `PremiumCustomerSpecification` ,比如将余额从 `100.000` 修改为 `200.000` ,所有查询语句和合并的规约都将受到本次更改的影响.这是减少代码重复的好方法! |
|||
|
|||
## 讨论 |
|||
|
|||
虽然规约模式通常与C#的lambda表达式相比较,算是一种更老的方式.一些开发人员可能认为不再需要它,我们可以直接将表达式传入到仓储或领域服务中,如下所示: |
|||
|
|||
````csharp |
|||
var count = await _customerRepository.CountAsync(c => c.Balance > 100000 && c.Age => 18); |
|||
```` |
|||
|
|||
自从ABP的[仓储](Repositories.md)支持表达式,这是一个完全有效的用法.你不必在应用程序中定义或使用任何规约,可以直接使用表达式. |
|||
|
|||
所以,规约的意义是什么?为什么或者应该在什么时候考虑去使用它? |
|||
|
|||
### 何时使用? |
|||
|
|||
使用规约的一些好处: |
|||
|
|||
- **可复用**:假设你在代码库的许多地方都需要用到优质顾客过滤器.如果使用表达式而不创建规约,那么如果以后更改“优质顾客”的定义会发生什么?假设你想将最低余额从100000美元更改为250000美元,并添加另一个条件,成为顾客超过3年.如果使用了规约,只需修改一个类.如果在任何其他地方重复(复制/粘贴)相同的表达式,则需要更改所有的表达式. |
|||
- **可组合**:可以组合多个规约来创建新规约.这是另一种可复用性. |
|||
- **命名**:`PremiumCustomerSpecification` 更好地解释了为什么使用规约,而不是复杂的表达式.因此,如果在你的业务中使用了一个有意义的表达式,请考虑使用规约. |
|||
- **可测试**:规约是一个单独(且易于)测试的对象. |
|||
|
|||
### 什么时侯不要使用? |
|||
|
|||
- **没有业务含义的表达式**:不要对与业务无关的表达式和操作使用规约. |
|||
- **报表**:如果只是创建报表,不要创建规约,而是直接使用 `IQueryable` 和LINQ表达式.你甚至可以使用普通SQL、视图或其他工具生成报表.DDD不关心报表,因此从性能角度来看,查询底层数据存储的方式可能很重要. |
|||
|
|||
@ -1,5 +1,8 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/codemirror/lib/*.*": "@libs/codemirror/" |
|||
"@node_modules/codemirror/lib/*.*": "@libs/codemirror/", |
|||
"@node_modules/codemirror/mode/**/*.*": "@libs/codemirror/mode/", |
|||
"@node_modules/codemirror/theme/**/*.*": "@libs/codemirror/theme/", |
|||
"@node_modules/codemirror/addon/**/*.*": "@libs/codemirror/addon/" |
|||
} |
|||
} |
|||
@ -1,6 +1,5 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/jquery-form/dist/jquery.form.min.js": "@libs/jquery-form/", |
|||
"@node_modules/jquery-form/dist/jquery.form.min.js.map": "@libs/jquery-form/" |
|||
"@node_modules/@abp/jquery-form/src/jquery.form.min.js": "@libs/jquery-form/" |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,13 @@ |
|||
@using System.Net.Http |
|||
@using Microsoft.AspNetCore.Authorization |
|||
@using Microsoft.AspNetCore.Components.Authorization |
|||
@using Microsoft.AspNetCore.Components.Forms |
|||
@using Microsoft.AspNetCore.Components.Routing |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Microsoft.AspNetCore.Components.Web.Virtualization |
|||
@using Microsoft.JSInterop |
|||
@using MyCompanyName.MyProjectName.Blazor.Server |
|||
@using Blazorise |
|||
@using Blazorise.DataGrid |
|||
@using Volo.Abp.BlazoriseUI |
|||
@using Volo.Abp.BlazoriseUI.Components |
|||
Loading…
Reference in new issue