-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from netcorepal/dev
Add RowVersion Type as ConcurrencyToken for Entity
- Loading branch information
Showing
13 changed files
with
357 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# RowVersion | ||
|
||
## 什么是RowVersion? | ||
|
||
`RowVersion`是一种用于解决并发问题的机制。在并发环境中,多个事务可能同时访问同一行数据,如果不加以限制,可能会导致数据不一致的问题。行版本号就是为了解决这个问题而设计的。 | ||
|
||
## RowVersion的实现原理 | ||
|
||
`RowVersion`的实现原理是在每一行数据中增加一个版本号字段,每次对这一行数据进行更新操作时,版本号加1。在事务开始时,事务会读取当前行的版本号,当事务提交时,会检查当前行的版本号是否与事务开始时读取的版本号一致,如果一致,则提交事务,否则回滚事务。 | ||
|
||
## RowVersion的使用场景 | ||
|
||
`RowVersion`主要用于解决并发问题,例如在订单系统中,当多个用户同时对同一订单进行操作时,可能会导致订单状态不一致的问题。通过使用行版本号,可以避免这种问题的发生。 | ||
|
||
## 定义行版本号 | ||
|
||
在领域模型中,定义一个`NetCorePal.Extensions.Domain.RowVersion`类型的`public`可读的属性,即可实现行版本号的功能,框架会自动处理行版本号的更新和并发检查逻辑。 | ||
|
||
下面是一个示例: | ||
|
||
```csharp | ||
// 定义行版本号 | ||
using NetCorePal.Extensions.Domain; | ||
namespace YourNamespace; | ||
|
||
//为模型定义强类型ID | ||
public partial record OrderId : IInt64StronglyTypedId; | ||
|
||
//领域模型 | ||
public class Order : Entity<OrderId>, IAggregateRoot | ||
{ | ||
protected Order() { } | ||
public string OrderNo { get; private set; } = string.Empty; | ||
public bool Paid { get; private set; } | ||
//定义行版本号 | ||
public RowVersion Version { get; private set; } = new RowVersion(); | ||
|
||
public void SetPaid() | ||
{ | ||
Paid = true; | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System.ComponentModel; | ||
using System.Globalization; | ||
|
||
namespace NetCorePal.Extensions.Domain; | ||
|
||
[TypeConverter(typeof(RowVersionTypeConverter))] | ||
public record RowVersion(int VersionNumber = 0); | ||
|
||
public class RowVersionTypeConverter : TypeConverter | ||
{ | ||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => | ||
sourceType == typeof(int); | ||
|
||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => | ||
destinationType == typeof(int); | ||
|
||
|
||
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) | ||
{ | ||
if (value is int intValue) | ||
{ | ||
return new RowVersion(intValue); | ||
} | ||
|
||
throw new ArgumentException($"Cannot convert {value ?? "(null)"} to {typeof(RowVersion)}", | ||
nameof(value)); | ||
} | ||
|
||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, | ||
Type destinationType) | ||
{ | ||
if (value is RowVersion rowVersion) | ||
{ | ||
return rowVersion.VersionNumber; | ||
} | ||
|
||
throw new ArgumentException($"Cannot convert {value ?? "(null)"} to {destinationType}", | ||
nameof(destinationType)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
test/NetCorePal.Web/Application/Commands/SetOrderItemNameCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
using NetCorePal.Extensions.Primitives; | ||
|
||
namespace NetCorePal.Web.Application.Commands; | ||
|
||
public record SetOrderItemNameCommand(OrderId OrderId,string NewName) : ICommand; | ||
|
||
public class SetOrderItemNameCommandHandler(IOrderRepository orderRepository) : ICommandHandler<SetOrderItemNameCommand> | ||
{ | ||
public async Task Handle(SetOrderItemNameCommand command, CancellationToken cancellationToken) | ||
{ | ||
var order = await orderRepository.GetAsync(command.OrderId, cancellationToken); | ||
if (order == null) | ||
{ | ||
throw new KnownException("Order not found"); | ||
} | ||
|
||
order.ChangeItemName(command.NewName); | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,42 @@ | ||
namespace NetCorePal.Web.Application.Queries | ||
using Microsoft.EntityFrameworkCore; | ||
using NetCorePal.Extensions.Domain; | ||
|
||
namespace NetCorePal.Web.Application.Queries | ||
{ | ||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="applicationDbContext"></param> | ||
public class OrderQuery(ApplicationDbContext applicationDbContext) | ||
{ | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="orderId"></param> | ||
/// <param name="cancellationToken"></param> | ||
/// <returns></returns> | ||
public async Task<Order?> QueryOrder(OrderId orderId, CancellationToken cancellationToken) | ||
public async Task<OrderQueryResult?> QueryOrder(OrderId orderId, CancellationToken cancellationToken) | ||
{ | ||
return await applicationDbContext.Orders.FindAsync(new object[] { orderId }, cancellationToken); | ||
return await applicationDbContext.Orders.Where(x => x.Id == orderId) | ||
.Select(p => new OrderQueryResult(p.Id, p.Name, p.Count, p.Paid, p.CreateTime, p.RowVersion, | ||
p.OrderItems.Select(x => new OrderItemQueryResult(x.Id, x.Name, x.Count, x.RowVersion)))) | ||
.FirstOrDefaultAsync(cancellationToken); | ||
} | ||
} | ||
} | ||
|
||
public record OrderQueryResult( | ||
OrderId OrderId, | ||
string Name, | ||
int Count, | ||
bool Paid, | ||
DateTime CreateTime, | ||
RowVersion RowVersion, | ||
IEnumerable<OrderItemQueryResult> OrderItems); | ||
|
||
|
||
public record OrderItemQueryResult( | ||
OrderItemId OrderItemId, | ||
string Name, | ||
int Count, | ||
RowVersion RowVersion); | ||
} |
Oops, something went wrong.