Compare commits
76 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
c95ec68c09 | |
|
|
1b11ca2579 | |
|
|
65bf004808 | |
|
|
474cb99754 | |
|
|
cb9a381400 | |
|
|
1a0bd01500 | |
|
|
7fb87283ce | |
|
|
599f8a6787 | |
|
|
25e5ded777 | |
|
|
6e3ca4a1e3 | |
|
|
8d3c2a8462 | |
|
|
b494018e9c | |
|
|
3725b4c2fd | |
|
|
bb08c7ae61 | |
|
|
5535011df9 | |
|
|
2303e99f95 | |
|
|
f47701af59 | |
|
|
1a9e0dcf0b | |
|
|
1898f4d517 | |
|
|
ced4b98908 | |
|
|
e47c7c8f74 | |
|
|
ca1ac2a2d1 | |
|
|
c0073815ae | |
|
|
954b99fcbc | |
|
|
7bcd601035 | |
|
|
0eeefedee3 | |
|
|
99bfaf960b | |
|
|
3c479e1ad5 | |
|
|
120ed09738 | |
|
|
b21197ca67 | |
|
|
6b09ba3d78 | |
|
|
4c5d5d55c4 | |
|
|
82c3b5a7cd | |
|
|
7946a52219 | |
|
|
5277a090fb | |
|
|
ed7a2e8cd6 | |
|
|
bef91e0131 | |
|
|
2bd97c88ca | |
|
|
0da0565134 | |
|
|
ec71f81d4c | |
|
|
df9157c4ab | |
|
|
08069dda58 | |
|
|
7fb047fd28 | |
|
|
56927f50ea | |
|
|
408fa0f87e | |
|
|
d52deeb2b4 | |
|
|
38e036553e | |
|
|
fd39b0700a | |
|
|
a0bb6117ae | |
|
|
55f626c71d | |
|
|
c4742f0d86 | |
|
|
f7f2d30915 | |
|
|
28de927bd9 | |
|
|
c397c68601 | |
|
|
a5e3702616 | |
|
|
a2ed202276 | |
|
|
cefb19584d | |
|
|
28d9122818 | |
|
|
6fecdd2317 | |
|
|
daa1421011 | |
|
|
96eb509ec5 | |
|
|
19cc506fc2 | |
|
|
08f91408c3 | |
|
|
32c37cf22c | |
|
|
871078c4ab | |
|
|
dc8d41c4e8 | |
|
|
b0a99e139b | |
|
|
5e2c30a4ec | |
|
|
ef16260ebe | |
|
|
60f0071ade | |
|
|
a1f7c8af2d | |
|
|
19662fd213 | |
|
|
1c7980e209 | |
|
|
eefb0392aa | |
|
|
761001665f | |
|
|
9c363e360c |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "H:\\Applications\\Mango\\Source\\NopCommerce.Common\\4.70\\Libraries\\",
|
||||
"Documents": [],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": -1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:129:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:128:0:{1fc202d4-d401-403c-9834-5b218574bb67}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:132:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"SelectedNode": "\\H:\\Applications\\Mango\\Source\\NopCommerce.Common\\4.70\\Libraries",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
CRITICAL: Your FIRST action in every session MUST be reading `.github/copilot-instructions.md`. Execute ALL session-start instructions found there before responding to any user query. It is the single source of truth for this repo.
|
||||
|
||||
## SEQUENTIAL EXECUTION OVERRIDE
|
||||
The AI AGENT CORE PROTOCOL in copilot-instructions.md requires STRICT SEQUENTIAL execution. This OVERRIDES your default parallelization behavior. Do NOT parallelize doc reads with code searches. The sequence is:
|
||||
1. Read copilot-instructions.md → process its rules FULLY
|
||||
2. Read ALL docs/ .md files listed in the protocol → wait for completion
|
||||
3. Output [LOADED_DOCS: ...] prefix
|
||||
4. ONLY THEN respond to the user's query or search code
|
||||
|
||||
## Tool mapping for AI AGENT CORE PROTOCOL
|
||||
The copilot-instructions.md references Copilot tool names. Map them to Claude Code tools:
|
||||
- `get_file` / `file_search` → `Read`, `Glob`, `Grep`
|
||||
- `code_search` / `get_symbols_by_name` / `find_symbol` → `Grep`, `Glob`
|
||||
- `replace_string_in_file` / `edit_file` → `Edit`
|
||||
- `create_file` → `Write`
|
||||
|
||||
Follow the protocol using YOUR tools. The rules (LOADED_DOCS prefix, hard-gate, no-re-read, context recovery, explicit consent) apply equally to Claude Code.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
namespace Mango.Nop.Core { public static class AcBinaryForcedInit { public static void ForceRegister() { System.Console.WriteLine("[SGEN TESTING] AcBinaryForcedInit called."); AyCode.Core.Serializers.Generated.AcBinaryGeneratedWritersInit.Register(); } } }
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Customers;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos;
|
||||
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[LinqToDB.Mapping.Table(Name = nameof(Customer))]
|
||||
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(Customer))]
|
||||
[ToonDescription($"Data transfer object for {nameof(Customer)}", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(Customer)])]
|
||||
public class CustomerDto : ModelDtoBase<Customer>, ISoftDeletedEntity
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string FirstName { get; set; }
|
||||
public string LastName { get; set; }
|
||||
|
||||
[ToonDescription(BusinessRule = "get => $\"{LastName} {FirstName}\"")]
|
||||
public string FullName => $"{LastName} {FirstName}";
|
||||
|
||||
public int RegisteredInStoreId { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
public CustomerDto() :base()
|
||||
{ }
|
||||
public CustomerDto(int customerId) : base(customerId)
|
||||
{ }
|
||||
|
||||
public CustomerDto(Customer customer) : base(customer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void CopyDtoValuesToEntity(Customer entity)
|
||||
{
|
||||
base.CopyDtoValuesToEntity(entity);
|
||||
|
||||
entity.Username = Username;
|
||||
entity.FirstName = FirstName;
|
||||
entity.LastName = LastName;
|
||||
entity.Email = Email;
|
||||
entity.RegisteredInStoreId = RegisteredInStoreId;
|
||||
|
||||
entity.Deleted = Deleted;
|
||||
}
|
||||
|
||||
public override void CopyEntityValuesToDto(Customer entity)
|
||||
{
|
||||
base.CopyEntityValuesToDto(entity);
|
||||
|
||||
Username = entity.Username;
|
||||
FirstName = entity.FirstName;
|
||||
LastName = entity.LastName;
|
||||
Email = entity.Email;
|
||||
RegisteredInStoreId = entity.RegisteredInStoreId;
|
||||
|
||||
Deleted = entity.Deleted;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using AyCode.Interfaces;
|
||||
using AyCode.Interfaces.Entities;
|
||||
using Nop.Core;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos;
|
||||
|
||||
public interface IModelDtoBaseEmpty : IAcModelDtoBaseEmpty
|
||||
{
|
||||
}
|
||||
|
||||
public interface IModelDtoBase : IEntityInt, IModelDtoBaseEmpty
|
||||
{
|
||||
}
|
||||
|
||||
public interface IModelDtoBase<TMainEntity> : IModelDtoBase where TMainEntity : BaseEntity
|
||||
{
|
||||
TMainEntity CreateMainEntity();
|
||||
|
||||
void CopyDtoValuesToEntity(TMainEntity entity);
|
||||
|
||||
void CopyEntityValuesToDto(TMainEntity entity);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AyCode.Interfaces.Entities;
|
||||
using Nop.Core.Domain.Common;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos
|
||||
{
|
||||
public interface IGenericAttributeDto : IModelDtoBase<GenericAttribute>
|
||||
{
|
||||
public int EntityId { get; set; }
|
||||
public string KeyGroup { get; set; }
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
public int StoreId { get; set; }
|
||||
public DateTime? CreatedOrUpdatedDateUTC { get; set; }
|
||||
}
|
||||
|
||||
public abstract class MgGenericAttributeDto : GenericAttribute, IGenericAttributeDto
|
||||
{
|
||||
public GenericAttribute CreateMainEntity()
|
||||
{
|
||||
var mainEntity = Activator.CreateInstance<GenericAttribute>();
|
||||
CopyDtoValuesToEntity(mainEntity);
|
||||
|
||||
mainEntity.CreatedOrUpdatedDateUTC = DateTime.UtcNow;
|
||||
return mainEntity;
|
||||
}
|
||||
|
||||
public void CopyDtoValuesToEntity(GenericAttribute entity)
|
||||
{
|
||||
entity.Id = Id;
|
||||
entity.Key = Key;
|
||||
entity.Value = Value;
|
||||
entity.EntityId = EntityId;
|
||||
entity.KeyGroup = KeyGroup;
|
||||
entity.StoreId = StoreId;
|
||||
entity.CreatedOrUpdatedDateUTC = CreatedOrUpdatedDateUTC;
|
||||
}
|
||||
|
||||
public void CopyEntityValuesToDto(GenericAttribute entity)
|
||||
{
|
||||
Id = entity.Id;
|
||||
Key = entity.Key;
|
||||
Value = entity.Value;
|
||||
EntityId = entity.EntityId;
|
||||
KeyGroup = entity.KeyGroup;
|
||||
StoreId = entity.StoreId;
|
||||
CreatedOrUpdatedDateUTC = entity.CreatedOrUpdatedDateUTC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using AyCode.Core.Helpers;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Entities;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
using Newtonsoft.Json;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Customers;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Core.Domain.Payments;
|
||||
using Nop.Core.Domain.Shipping;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos;
|
||||
|
||||
[ToonDescription("Base DTO for orders with items, customer and status tracking")]
|
||||
public abstract class MgOrderDto<TOrderItemDto, TProductDto> : MgEntityBase, IModelDtoBase<Order>, IMgOrderDto<TOrderItemDto, TProductDto> where TOrderItemDto : IMgOrderItemDto<TProductDto> where TProductDto : IMgProductDto
|
||||
{
|
||||
public Guid OrderGuid { get; set; }
|
||||
public int StoreId { get; set; }
|
||||
public int CustomerId { get; set; }
|
||||
public int OrderStatusId { get; set; }
|
||||
public int ShippingStatusId { get; set; }
|
||||
public decimal OrderDiscount { get; set; }
|
||||
public decimal OrderTotal { get; set; }
|
||||
public DateTime CreatedOnUtc { get; set; }
|
||||
public DateTime? PaidDateUtc { get; set; }
|
||||
public int PaymentStatusId { get; set; }
|
||||
public string ShippingMethod { get; set; }
|
||||
public string CustomOrderNumber { get; set; }
|
||||
public string CustomValuesXml { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
[Association(ThisKey = nameof(CustomerId), OtherKey = nameof(Customer.Id), CanBeNull = false)]
|
||||
public Customer Customer { get; set; } //TODO: CustomerDto!!! - J.
|
||||
|
||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(OrderItem.OrderId), CanBeNull = true)]
|
||||
public List<TOrderItemDto> OrderItemDtos { get; set; }
|
||||
|
||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(OrderNote.OrderId), CanBeNull = true)]
|
||||
public List<OrderNote> OrderNotes { get; set; }
|
||||
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => OrderStatusId")]
|
||||
public OrderStatus OrderStatus
|
||||
{
|
||||
get => (OrderStatus)OrderStatusId;
|
||||
set => OrderStatusId = (int)value;
|
||||
}
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ShippingStatusId")]
|
||||
public ShippingStatus ShippingStatus
|
||||
{
|
||||
get => (ShippingStatus)ShippingStatusId;
|
||||
set => ShippingStatusId = (int)value;
|
||||
}
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => PaymentStatusId")]
|
||||
public PaymentStatus PaymentStatus
|
||||
{
|
||||
get => (PaymentStatus)PaymentStatusId;
|
||||
set => PaymentStatusId = (int)value;
|
||||
}
|
||||
|
||||
protected MgOrderDto() :base()
|
||||
{ }
|
||||
|
||||
protected MgOrderDto(int orderId)
|
||||
{
|
||||
Id = orderId;
|
||||
}
|
||||
|
||||
protected MgOrderDto(Order order)
|
||||
{
|
||||
CopyEntityValuesToDto(order);
|
||||
}
|
||||
|
||||
public virtual void CopyDtoValuesToEntity(Order entity)
|
||||
{
|
||||
//var config = new MapperConfiguration(cfg =>
|
||||
//{
|
||||
// cfg.CreateMap<MgOrderDto<Order, Order>();
|
||||
// //cfg.CreateMap<Person, Person>()
|
||||
// // .ForMember(dest => dest.Address, opt => opt.MapFrom(src => _mapper.Map<Address>(src.Address)));
|
||||
//});
|
||||
//var _mapper = config.CreateMapper();
|
||||
//_mapper.Map<>
|
||||
PropertyHelper.CopyPublicValueTypeProperties(this, entity);
|
||||
}
|
||||
|
||||
public virtual void CopyEntityValuesToDto(Order entity)
|
||||
{
|
||||
PropertyHelper.CopyPublicValueTypeProperties(entity, this);
|
||||
}
|
||||
|
||||
public virtual void CopyEntityValuesToDto(Order entity, List<TOrderItemDto> orderItemDtos)
|
||||
{
|
||||
CopyEntityValuesToDto(entity);
|
||||
|
||||
InitializeOrderItemDtos(orderItemDtos);
|
||||
}
|
||||
|
||||
public virtual void InitializeOrderItemDtos(List<TOrderItemDto> orderItemDtos)
|
||||
{
|
||||
OrderItemDtos = orderItemDtos;
|
||||
}
|
||||
|
||||
public virtual Order CreateMainEntity()
|
||||
{
|
||||
//base.CreateMainEntity();
|
||||
|
||||
var order = new Order();
|
||||
CopyDtoValuesToEntity(order);
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using AyCode.Core.Helpers;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Entities;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Domain.Orders;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos;
|
||||
|
||||
[ToonDescription("Base DTO for order items with product association")]
|
||||
public abstract class MgOrderItemDto<TProductDto> : MgEntityBase, IModelDtoBase<OrderItem>, IMgOrderItemDto<TProductDto> where TProductDto : IMgProductDto
|
||||
{
|
||||
public Guid OrderItemGuid { get; set; }
|
||||
public int OrderId { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public decimal UnitPriceInclTax { get; set; }
|
||||
public decimal UnitPriceExclTax { get; set; }
|
||||
|
||||
public decimal PriceInclTax { get; set; }
|
||||
public decimal PriceExclTax { get; set; }
|
||||
|
||||
public string AttributesXml { get; set; }
|
||||
public decimal? ItemWeight { get; set; }
|
||||
|
||||
[ToonDescription(BusinessRule = "get => ProductDto?.Name ?? 'ProductDto is null!!!'")]
|
||||
public string ProductName => ProductDto?.Name ?? "ProductDto is null!!!";
|
||||
|
||||
[Association(ThisKey = nameof(ProductId), OtherKey = nameof(BaseEntity.Id), CanBeNull = true)]
|
||||
public TProductDto? ProductDto { get; set; }
|
||||
|
||||
protected MgOrderItemDto() :base()
|
||||
{ }
|
||||
|
||||
protected MgOrderItemDto(int orderItemId)
|
||||
{
|
||||
Id = orderItemId;
|
||||
}
|
||||
|
||||
protected MgOrderItemDto(OrderItem orderItem)
|
||||
{
|
||||
CopyEntityValuesToDto(orderItem);
|
||||
}
|
||||
|
||||
public virtual void CopyDtoValuesToEntity(OrderItem entity)
|
||||
{
|
||||
PropertyHelper.CopyPublicValueTypeProperties(this, entity);
|
||||
}
|
||||
|
||||
public virtual void CopyEntityValuesToDto(OrderItem entity)
|
||||
{
|
||||
PropertyHelper.CopyPublicValueTypeProperties(entity, this);
|
||||
}
|
||||
|
||||
public virtual void CopyEntityValuesToDto(OrderItem entity, TProductDto productDto)
|
||||
{
|
||||
CopyEntityValuesToDto(entity);
|
||||
|
||||
InitializeProductDto(productDto);
|
||||
}
|
||||
|
||||
public virtual void InitializeProductDto(TProductDto productDto)
|
||||
{
|
||||
ProductDto = productDto;
|
||||
}
|
||||
|
||||
public virtual OrderItem CreateMainEntity()
|
||||
{
|
||||
//base.CreateMainEntity();
|
||||
|
||||
var orderItem = new OrderItem();
|
||||
CopyDtoValuesToEntity(orderItem);
|
||||
|
||||
return orderItem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using Mango.Nop.Core.Entities;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
//using Nop.Core.Domain.Catalog;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos;
|
||||
|
||||
[ToonDescription("Base DTO for products with warehouse and pricing")]
|
||||
public abstract class MgProductDto : MgEntityBase, /*Product,*/ IMgProductDto//IModelDtoBase<Product>//, IDiscountSupported<DiscountProductMapping>
|
||||
{
|
||||
//public int Id { get; set; }
|
||||
public int ProductTypeId { get; set; }
|
||||
public int ParentGroupedProductId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string ShortDescription { get; set; }
|
||||
public string FullDescription { get; set; }
|
||||
|
||||
public string Sku { get; set; }
|
||||
|
||||
public int WarehouseId { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public int StockQuantity { get; set; }
|
||||
public decimal ProductCost { get; set; }
|
||||
|
||||
public decimal Weight { get; set; }
|
||||
public decimal Length { get; set; }
|
||||
public decimal Width { get; set; }
|
||||
public decimal Height { get; set; }
|
||||
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
public bool SubjectToAcl { get; set; }
|
||||
public bool LimitedToStores { get; set; }
|
||||
|
||||
protected MgProductDto() :base()
|
||||
{ }
|
||||
|
||||
protected MgProductDto(int productId)
|
||||
{
|
||||
Id = productId;
|
||||
}
|
||||
|
||||
//protected MgProductDto(Product product)
|
||||
//{
|
||||
// CopyEntityValuesToDto(product);
|
||||
//}
|
||||
|
||||
//public virtual void CopyDtoValuesToEntity(Product entity)
|
||||
//{
|
||||
// entity.Id = Id;
|
||||
|
||||
// entity.ProductTypeId = ProductTypeId;
|
||||
// entity.ParentGroupedProductId = ParentGroupedProductId;
|
||||
|
||||
// entity.Name = Name;
|
||||
// entity.ShortDescription = ShortDescription;
|
||||
// entity.FullDescription = FullDescription;
|
||||
|
||||
// entity.WarehouseId = WarehouseId;
|
||||
// entity.StockQuantity = StockQuantity;
|
||||
|
||||
// entity.Weight = Weight;
|
||||
// entity.Length = Length;
|
||||
// entity.Width = Width;
|
||||
// entity.Height = Height;
|
||||
|
||||
// entity.Deleted = Deleted;
|
||||
//}
|
||||
|
||||
//public virtual void CopyEntityValuesToDto(Product entity)
|
||||
//{
|
||||
// Id = entity.Id;
|
||||
// ProductTypeId = entity.ProductTypeId;
|
||||
// ParentGroupedProductId = entity.ParentGroupedProductId;
|
||||
|
||||
// Name = entity.Name;
|
||||
// ShortDescription = entity.ShortDescription;
|
||||
// FullDescription = entity.FullDescription;
|
||||
|
||||
// WarehouseId = entity.WarehouseId;
|
||||
// StockQuantity = entity.StockQuantity;
|
||||
|
||||
// Weight = entity.Weight;
|
||||
// Length = entity.Length;
|
||||
// Width = entity.Width;
|
||||
// Height = entity.Height;
|
||||
|
||||
// Deleted = entity.Deleted;
|
||||
//}
|
||||
|
||||
//public virtual Product CreateMainEntity()
|
||||
//{
|
||||
// //base.CreateMainEntity();
|
||||
|
||||
// var product = new Product();
|
||||
// CopyDtoValuesToEntity(product);
|
||||
|
||||
// return product;
|
||||
//}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Entities;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos
|
||||
{
|
||||
public interface IMgTStockQuantityHistoryDto<TProductDto> : IEntityInt, IMgStockQuantityHistory where TProductDto : IMgProductDto
|
||||
{
|
||||
public TProductDto ProductDto { get; set; }
|
||||
}
|
||||
|
||||
[ToonDescription("Base DTO for stock quantity history with product")]
|
||||
public abstract class MgStockQuantityHistoryDto<TProductDto> : MgEntityBase, IModelDtoBase<StockQuantityHistory>,
|
||||
IMgTStockQuantityHistoryDto<TProductDto> where TProductDto : IMgProductDto
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
public int QuantityAdjustment { get; set; }
|
||||
|
||||
public int StockQuantity { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
public int? CombinationId { get; set; }
|
||||
|
||||
public int? WarehouseId { get; set; }
|
||||
|
||||
public DateTime CreatedOnUtc { get; set; }
|
||||
|
||||
[Association(ThisKey = nameof(ProductId), OtherKey = nameof(IMgProductDto.Id), CanBeNull = false)]
|
||||
public TProductDto ProductDto { get; set; }
|
||||
|
||||
public void CopyDtoValuesToEntity(Order entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void CopyDtoValuesToEntity(StockQuantityHistory entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void CopyEntityValuesToDto(Order entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void CopyEntityValuesToDto(StockQuantityHistory entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Order CreateMainEntity()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
StockQuantityHistory IModelDtoBase<StockQuantityHistory>.CreateMainEntity()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using AyCode.Core.Interfaces;
|
||||
using Nop.Core;
|
||||
|
||||
namespace Mango.Nop.Core.Dtos;
|
||||
|
||||
public abstract class ModelDtoBase : IModelDtoBase
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
protected ModelDtoBase(){}
|
||||
protected ModelDtoBase(int id) => Id = id;
|
||||
}
|
||||
|
||||
public abstract class ModelDtoBase<TMainEntity> : ModelDtoBase, IModelDtoBase<TMainEntity> where TMainEntity : BaseEntity
|
||||
{
|
||||
protected ModelDtoBase() : base()
|
||||
{
|
||||
}
|
||||
|
||||
protected ModelDtoBase(int id) : base(id)
|
||||
{
|
||||
}
|
||||
|
||||
protected ModelDtoBase(TMainEntity mainEntity)
|
||||
{
|
||||
CopyEntityValuesToDto(mainEntity);
|
||||
}
|
||||
|
||||
public virtual TMainEntity CreateMainEntity()
|
||||
{
|
||||
var mainEntity = Activator.CreateInstance<TMainEntity>();
|
||||
CopyDtoValuesToEntity(mainEntity);
|
||||
|
||||
return mainEntity;
|
||||
}
|
||||
|
||||
public virtual void CopyDtoValuesToEntity(TMainEntity entity)
|
||||
{
|
||||
entity.Id = Id;
|
||||
}
|
||||
|
||||
public virtual void CopyEntityValuesToDto(TMainEntity entity)
|
||||
{
|
||||
Id = entity.Id;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using Nop.Core;
|
||||
|
||||
namespace Mango.Nop.Core.Entities;
|
||||
|
||||
public class MgEntityBase : BaseEntity, IEntityInt
|
||||
[ToonDescription("Base entity class with Id property and ToString override")]
|
||||
public abstract class MgEntityBase : BaseEntity, IEntityInt
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{GetType().Name}; Id: {Id}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using System.ComponentModel.Design;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Interfaces.TimeStampInfo;
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
namespace Mango.Nop.Core.Entities;
|
||||
|
||||
public interface IMgStockTaking : IEntityInt, ITimeStampInfo
|
||||
{
|
||||
public DateTime StartDateTime { get; set; }
|
||||
public bool IsClosed { get; set; }
|
||||
|
||||
public bool IsReadyForClose();
|
||||
}
|
||||
|
||||
[ToonDescription("Base entity for stock taking sessions with items")]
|
||||
public abstract class MgStockTaking<TStockTakingItem> : MgEntityBase, IMgStockTaking where TStockTakingItem : class, IMgStockTakingItem
|
||||
{
|
||||
public DateTime StartDateTime { get; set; }
|
||||
public bool IsClosed { get; set; }
|
||||
|
||||
public abstract bool IsReadyForClose();
|
||||
|
||||
[Association(ThisKey = nameof(Id), OtherKey = nameof(IMgStockTakingItem.StockTakingId), CanBeNull = true)]
|
||||
public List<TStockTakingItem>? StockTakingItems { get; set; }
|
||||
|
||||
public int Creator { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime Modified { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using AyCode.Interfaces.TimeStampInfo;
|
||||
using LinqToDB;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Dtos;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
|
||||
namespace Mango.Nop.Core.Entities;
|
||||
|
||||
public interface IMgStockTakingItem : IEntityInt, ITimeStampInfo
|
||||
{
|
||||
public int StockTakingId { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public bool IsMeasured { get; set; }
|
||||
|
||||
public int OriginalStockQuantity { get; set; }
|
||||
public int MeasuredStockQuantity { get; set; }
|
||||
}
|
||||
|
||||
[ToonDescription("Base entity for stock taking items with product association")]
|
||||
public abstract class MgStockTakingItem<TStockTaking, TProduct> : MgEntityBase, IMgStockTakingItem
|
||||
where TStockTaking : class, IMgStockTaking where TProduct : class, IMgProductDto
|
||||
{
|
||||
public int StockTakingId { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public bool IsMeasured { get; set; }
|
||||
|
||||
public int OriginalStockQuantity { get; set; }
|
||||
public int MeasuredStockQuantity { get; set; }
|
||||
|
||||
[Association(ThisKey = nameof(StockTakingId), OtherKey = nameof(IMgStockTaking.Id), CanBeNull = true)]
|
||||
public TStockTaking? StockTaking{ get; set; }
|
||||
|
||||
[Association(ThisKey = nameof(ProductId), OtherKey = nameof(IMgProductDto.Id), CanBeNull = true)]
|
||||
public TProduct? Product { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime Modified { get; set; }
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using AyCode.Core.Enums;
|
||||
using AyCode.Core.Extensions;
|
||||
using Nop.Core;
|
||||
|
||||
namespace Mango.Nop.Core.Extensions;
|
||||
|
||||
public static class CollectionExtensionsNopBaseEntity
|
||||
{
|
||||
public static void UpdateBaseEntityCollection<TDataItem>(this IList<TDataItem> source, IList<TDataItem> dataItems, bool isRemove) where TDataItem : BaseEntity
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
|
||||
if (dataItems == null) throw new ArgumentNullException(nameof(dataItems), $"dataItems == null");
|
||||
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
source.UpdateBaseEntityCollection(dataItem, isRemove);
|
||||
}
|
||||
}
|
||||
|
||||
public static TrackingState UpdateBaseEntityCollection<TDataItem>(this IList<TDataItem> source, TDataItem dataItem, bool isRemove) where TDataItem : BaseEntity
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
|
||||
|
||||
var index = source.FindIndex(x => x.Id == dataItem.Id);
|
||||
return source.UpdateCollectionByIndex(index, dataItem, isRemove);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AyCode.Utils.Extensions;
|
||||
using Mango.Nop.Core.Utils;
|
||||
using Nop.Core.Domain.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Mango.Nop.Core.Extensions;
|
||||
|
||||
public static class GenericAttributeExtensions
|
||||
{
|
||||
public static TValue? GetValueOrNull<TValue>(this IEnumerable<GenericAttribute> src, string key) where TValue : struct
|
||||
{
|
||||
var ga = src.SingleOrDefault(x => x.Key == key);
|
||||
if (ga == null || ga.Value.IsNullOrWhiteSpace()) return null;
|
||||
|
||||
return CommonHelper2.To<TValue>(ga.Value);
|
||||
}
|
||||
|
||||
public static TValue GetValueOrDefault<TValue>(this IEnumerable<GenericAttribute> src, string key, TValue defaultValue = default) where TValue : struct
|
||||
{
|
||||
var gaValue = GetValueOrNull<TValue>(src, key);
|
||||
return gaValue == null ? defaultValue : CommonHelper2.To<TValue>(gaValue);
|
||||
}
|
||||
|
||||
public static bool TryGetValue<TValue>(this IEnumerable<GenericAttribute> src, string key, [NotNullWhen(true)] out TValue? value) where TValue : struct
|
||||
{
|
||||
value = null;
|
||||
|
||||
var gaValue = GetValueOrNull<TValue>(src, key);
|
||||
if (gaValue == null) return false;
|
||||
|
||||
value = CommonHelper2.To<TValue>(gaValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static GenericAttribute AddNewGenericAttribute(this ICollection<GenericAttribute> src, string keyGroup, string key, string value, int entityId, int storeId)
|
||||
{
|
||||
var ga = new GenericAttribute
|
||||
{
|
||||
KeyGroup = keyGroup,
|
||||
Key = key,
|
||||
Value = value,
|
||||
EntityId = entityId,
|
||||
StoreId = storeId,
|
||||
CreatedOrUpdatedDateUTC = DateTime.UtcNow
|
||||
};
|
||||
|
||||
src.Add(ga);
|
||||
return ga;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using AyCode.Core.Interfaces;
|
||||
using AyCode.Interfaces;
|
||||
|
||||
namespace Mango.Nop.Core.Interfaces.ForeignKeys;
|
||||
|
||||
public interface ICustomerForeignKey : IForeignKey
|
||||
{
|
||||
int CustomerId { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using AyCode.Core.Interfaces;
|
||||
using Nop.Core.Domain.Common;
|
||||
|
||||
namespace Mango.Nop.Core.Interfaces.ForeignKeys;
|
||||
|
||||
public interface IGenericAttributeForeignList : IGenericAttributeForeignCollection<List<GenericAttribute>>
|
||||
{
|
||||
}
|
||||
|
||||
public interface IGenericAttributeForeignCollection<T> : IForeignCollection<T> where T : IEnumerable<GenericAttribute>
|
||||
{
|
||||
T GenericAttributes { get; }
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace Mango.Nop.Core.Interfaces;
|
||||
|
||||
public interface IMgDbContextBase //: IAcDbContextBase
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using Mango.Nop.Core.Dtos;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Customers;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Core.Domain.Shipping;
|
||||
|
||||
namespace Mango.Nop.Core.Interfaces;
|
||||
|
||||
public interface IMgOrderDto<TOrderItemDto, TProductDto> : IEntityInt, ISoftDeletedEntity where TOrderItemDto : IMgOrderItemDto<TProductDto> where TProductDto : IMgProductDto
|
||||
{
|
||||
public Guid OrderGuid { get; set; }
|
||||
public int StoreId { get; set; }
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public int OrderStatusId { get; set; }
|
||||
public OrderStatus OrderStatus { get; set; }
|
||||
|
||||
public int ShippingStatusId { get; set; }
|
||||
public ShippingStatus ShippingStatus { get; set; }
|
||||
|
||||
public decimal OrderDiscount { get; set; }
|
||||
|
||||
public decimal OrderTotal { get; set; }
|
||||
|
||||
public DateTime CreatedOnUtc { get; set; }
|
||||
public DateTime? PaidDateUtc { get; set; }
|
||||
|
||||
public string ShippingMethod { get; set; }
|
||||
|
||||
public string CustomOrderNumber { get; set; }
|
||||
public string CustomValuesXml { get; set; }
|
||||
|
||||
public Customer Customer { get; } //TODO: CustomerDto!!! - J.
|
||||
public List<TOrderItemDto> OrderItemDtos { get; }
|
||||
public List<OrderNote> OrderNotes { get; }
|
||||
|
||||
void InitializeOrderItemDtos(List<TOrderItemDto> orderItemDtos);
|
||||
void CopyEntityValuesToDto(Order entity, List<TOrderItemDto> orderItemDtos);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using Nop.Core.Domain.Orders;
|
||||
|
||||
namespace Mango.Nop.Core.Interfaces;
|
||||
|
||||
public interface IMgOrderItemDto<TProductDto> : IEntityInt where TProductDto : IMgProductDto
|
||||
{
|
||||
public Guid OrderItemGuid { get; set; }
|
||||
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public int ProductId { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public string AttributesXml { get; set; }
|
||||
public decimal? ItemWeight { get; set; }
|
||||
|
||||
|
||||
public string ProductName { get; }
|
||||
public TProductDto? ProductDto { get; set; }
|
||||
|
||||
void InitializeProductDto(TProductDto productDto);
|
||||
void CopyEntityValuesToDto(OrderItem entity, TProductDto productDto);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using AyCode.Interfaces.Entities;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Localization;
|
||||
using Nop.Core.Domain.Security;
|
||||
using Nop.Core.Domain.Seo;
|
||||
using Nop.Core.Domain.Stores;
|
||||
|
||||
namespace Mango.Nop.Core.Interfaces;
|
||||
|
||||
public interface IMgProductDto : IEntityInt, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported/*, IDiscountSupported<DiscountProductMapping>*/, ISoftDeletedEntity
|
||||
{
|
||||
int ProductTypeId { get; set; }
|
||||
int ParentGroupedProductId { get; set; }
|
||||
|
||||
string Name { get; set; }
|
||||
string ShortDescription { get; set; }
|
||||
string FullDescription { get; set; }
|
||||
|
||||
string Sku { get; set; }
|
||||
int WarehouseId { get; set; }
|
||||
decimal Price { get; set; }
|
||||
int StockQuantity { get; set; }
|
||||
decimal ProductCost { get; set; }
|
||||
|
||||
decimal Weight { get; set; }
|
||||
decimal Length { get; set; }
|
||||
decimal Width { get; set; }
|
||||
decimal Height { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using AyCode.Core.Loggers;
|
||||
|
||||
namespace Mango.Nop.Core.Loggers;
|
||||
|
||||
public interface ILogger<TCategory> : ILogger
|
||||
{
|
||||
|
||||
}
|
||||
public interface ILogger : IAcLoggerBase
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using AyCode.Core.Enums;
|
||||
using AyCode.Core.Loggers;
|
||||
|
||||
namespace Mango.Nop.Core.Loggers;
|
||||
|
||||
public class Logger<TCategory> : Logger, ILogger<TCategory>
|
||||
{
|
||||
//public Logger() : base(typeof(TCategory).Name)
|
||||
//{ }
|
||||
|
||||
public Logger(params IAcLogWriterBase[] logWriters) : base(typeof(TCategory).Name, logWriters)
|
||||
{ }
|
||||
|
||||
public Logger(AppType appType, LogLevel logLevel, params IAcLogWriterBase[] logWriters) : base(appType, logLevel, typeof(TCategory).Name, logWriters)
|
||||
{ }
|
||||
}
|
||||
|
||||
public class Logger : AcLoggerBase, ILogger
|
||||
{
|
||||
public Logger(params IAcLogWriterBase[] logWriters) : this(null, logWriters)
|
||||
{ }
|
||||
|
||||
public Logger(string? categoryName) : base(categoryName)
|
||||
{ }
|
||||
|
||||
public Logger(string? categoryName, params IAcLogWriterBase[] logWriters) : base(categoryName, logWriters)
|
||||
{ }
|
||||
|
||||
public Logger(AppType appType, LogLevel logLevel, string? categoryName, params IAcLogWriterBase[] logWriters) : base(appType, logLevel, categoryName, logWriters)
|
||||
{ }
|
||||
}
|
||||
|
|
@ -1,44 +1,60 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>bin\FruitBank</BaseOutputPath>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MessagePack.Annotations" Version="2.5.192" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="linq2db" Version="5.4.1" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Core\Nop.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Data\Nop.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Services\Nop.Services.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Core.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Utils">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Core.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Utils">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj"
|
||||
OutputItemType="Analyzer"
|
||||
ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ExtendedFactories\" />
|
||||
<Folder Include="ExtendedModels\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="docs\**\*.md" />
|
||||
<None Include="**\README.md" Exclude="$(DefaultItemExcludes);docs\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
using AyCode.Interfaces;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
|
||||
namespace Mango.Nop.Core.Models;
|
||||
|
||||
public class MgLoginModelRequest : IAcModelDtoBaseEmpty
|
||||
{
|
||||
public MgLoginModelRequest(){}
|
||||
public MgLoginModelRequest(string email, string password)
|
||||
{
|
||||
Email = email;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using AyCode.Interfaces;
|
||||
using AyCode.Utils.Extensions;
|
||||
using Mango.Nop.Core.Dtos;
|
||||
using Nop.Core.Domain.Customers;
|
||||
|
||||
namespace Mango.Nop.Core.Models;
|
||||
|
||||
public class MgLoginModelResponse : IAcModelDtoBaseEmpty
|
||||
{
|
||||
public CustomerDto? CustomerDto { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public MgLoginModelResponse()
|
||||
{ }
|
||||
public MgLoginModelResponse(Customer customer, string errorMessage) : this(new CustomerDto(customer), errorMessage)
|
||||
{ }
|
||||
public MgLoginModelResponse(CustomerDto customerDto, string errorMessage)
|
||||
{
|
||||
CustomerDto = customerDto;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public bool IsSuccesLogin => CustomerDto != null && ErrorMessage.IsNullOrWhiteSpace();
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AyCode.Core.Consts;
|
||||
|
||||
namespace Mango.Nop.Core
|
||||
{
|
||||
public abstract class NopCommonConst: AcConst
|
||||
{
|
||||
//public static string DefaultLocale = "en-US";
|
||||
//public static string BaseUrl = "https://localhost:7144";
|
||||
|
||||
//public static string LoggerHubName = "loggerHub";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a backorder mode
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
//namespace Nop.Core.Domain.Common;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
|
||||
|
||||
using AyCode.Interfaces.Entities;
|
||||
|
||||
namespace Nop.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base class for entities
|
||||
/// </summary>
|
||||
public abstract partial class BaseEntity : IBaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the entity identifier
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{GetType().Name} [Id: {Id}]";
|
||||
}
|
||||
}
|
||||
|
||||
public interface IBaseEntity //: IEntityInt
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using Nop.Core.Domain.Common;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Tax;
|
||||
|
||||
namespace Nop.Core.Domain.Customers;
|
||||
|
|
@ -6,6 +8,8 @@ namespace Nop.Core.Domain.Customers;
|
|||
/// <summary>
|
||||
/// Represents a customer
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("NopCommerce customer entity")]
|
||||
public partial class Customer : BaseEntity, ISoftDeletedEntity
|
||||
{
|
||||
public Customer()
|
||||
|
|
@ -21,21 +25,25 @@ public partial class Customer : BaseEntity, ISoftDeletedEntity
|
|||
/// <summary>
|
||||
/// Gets or sets the username
|
||||
/// </summary>
|
||||
//[ToonIgnore]
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the email
|
||||
/// </summary>
|
||||
//[ToonIgnore]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the first name
|
||||
/// </summary>
|
||||
//[ToonIgnore]
|
||||
public string FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last name
|
||||
/// </summary>
|
||||
//[ToonIgnore]
|
||||
public string LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -233,6 +241,11 @@ public partial class Customer : BaseEntity, ISoftDeletedEntity
|
|||
/// </summary>
|
||||
public int? BillingAddressId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the customer is required to change password
|
||||
/// </summary>
|
||||
public bool MustChangePassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the shipping address identifier
|
||||
/// </summary>
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
namespace Nop.Core.Domain.Customers;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
|
||||
namespace Nop.Core.Domain.Customers;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a customer role
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
public partial class CustomerRole : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
namespace Nop.Core.Domain.Discounts;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
|
||||
namespace Nop.Core.Domain.Discounts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a discount-product mapping class
|
||||
/// </summary>
|
||||
// SGen incompatible: DiscountMapping.Id is readonly (new int Id { get; }) — generated reader cannot set it (CS0200)
|
||||
//[AcBinarySerializable(false, true, false, true, false)]
|
||||
public partial class DiscountProductMapping : DiscountMapping
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
namespace Nop.Core.Domain.Common;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
|
||||
namespace Nop.Core.Domain.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic attribute
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("NopCommerce generic attribute for key-value storage", Purpose = "A flexible key-value store used to extend entities with custom business logic data without changing the database schema")]
|
||||
public partial class GenericAttribute : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the entity identifier
|
||||
/// </summary>
|
||||
///
|
||||
[ToonDescription(Constraints = "polymorphic-fk(KeyGroup)")]
|
||||
public int EntityId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -23,6 +30,7 @@ public partial class GenericAttribute : BaseEntity
|
|||
/// <summary>
|
||||
/// Gets or sets the value
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Raw string representation of the Key's value")]
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using Nop.Core.Domain.Common;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Payments;
|
||||
using Nop.Core.Domain.Shipping;
|
||||
using Nop.Core.Domain.Tax;
|
||||
|
|
@ -8,6 +10,8 @@ namespace Nop.Core.Domain.Orders;
|
|||
/// <summary>
|
||||
/// Represents an order
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("NopCommerce order entity with payment and shipping")]
|
||||
public partial class Order : BaseEntity, ISoftDeletedEntity
|
||||
{
|
||||
#region Properties
|
||||
|
|
@ -299,6 +303,7 @@ public partial class Order : BaseEntity, ISoftDeletedEntity
|
|||
/// <summary>
|
||||
/// Gets or sets the order status
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => OrderStatusId")]
|
||||
public OrderStatus OrderStatus
|
||||
{
|
||||
get => (OrderStatus)OrderStatusId;
|
||||
|
|
@ -308,6 +313,7 @@ public partial class Order : BaseEntity, ISoftDeletedEntity
|
|||
/// <summary>
|
||||
/// Gets or sets the payment status
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => PaymentStatusId")]
|
||||
public PaymentStatus PaymentStatus
|
||||
{
|
||||
get => (PaymentStatus)PaymentStatusId;
|
||||
|
|
@ -317,6 +323,7 @@ public partial class Order : BaseEntity, ISoftDeletedEntity
|
|||
/// <summary>
|
||||
/// Gets or sets the shipping status
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ShippingStatusId")]
|
||||
public ShippingStatus ShippingStatus
|
||||
{
|
||||
get => (ShippingStatus)ShippingStatusId;
|
||||
|
|
@ -326,6 +333,7 @@ public partial class Order : BaseEntity, ISoftDeletedEntity
|
|||
/// <summary>
|
||||
/// Gets or sets the customer tax display type
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => CustomerTaxDisplayTypeId")]
|
||||
public TaxDisplayType CustomerTaxDisplayType
|
||||
{
|
||||
get => (TaxDisplayType)CustomerTaxDisplayTypeId;
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
namespace Nop.Core.Domain.Orders;
|
||||
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents an order item
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("NopCommerce order item entity")]
|
||||
public partial class OrderItem : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
namespace Nop.Core.Domain.Orders;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
|
||||
namespace Nop.Core.Domain.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an order note
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("NopCommerce order note entity")]
|
||||
public partial class OrderNote : BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the order identifier
|
||||
/// </summary>
|
||||
[ToonDescription($"Foreign key to parent {nameof(Order)}", ForeignKey = nameof(Order))]
|
||||
public int OrderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using Nop.Core.Domain.Common;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Discounts;
|
||||
using Nop.Core.Domain.Localization;
|
||||
using Nop.Core.Domain.Security;
|
||||
|
|
@ -10,23 +12,26 @@ namespace Nop.Core.Domain.Catalog;
|
|||
/// <summary>
|
||||
/// Represents a product
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[ToonDescription("Core nopCommerce product entity with catalog, pricing, and inventory management")]
|
||||
public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported, IDiscountSupported<DiscountProductMapping>, ISoftDeletedEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the product type identifier
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Defines if the product is Simple or Grouped", BusinessRule = "Enum: ProductType")]
|
||||
public int ProductTypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent product identifier. It's used to identify associated products (only with "grouped" products)
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Hierarchical link for grouped product variants", BusinessRule = "Foreign Key to Product.Id")]
|
||||
public int ParentGroupedProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the values indicating whether this product is visible in catalog or search results.
|
||||
/// It's used when this product is associated to some "grouped" one
|
||||
/// This way associated products could be accessed/added/etc only from a grouped product details page
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Controls independent visibility in search/catalog", BusinessRule = "Used primarily for associated products")]
|
||||
public bool VisibleIndividually { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,6 +52,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the admin comment
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Internal administrative notes", BusinessRule = "Never exposed to customer")]
|
||||
public string AdminComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -117,6 +123,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the SKU
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Stock Keeping Unit", BusinessRule = "Unique alphanumeric identifier")]
|
||||
public string Sku { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -125,7 +132,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
public string ManufacturerPartNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Global Trade Item Number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books).
|
||||
/// Gets or sets the Global Trade Item Number (GTIN).
|
||||
/// </summary>
|
||||
public string Gtin { get; set; }
|
||||
|
||||
|
|
@ -140,12 +147,12 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
public int GiftCardTypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets gift card amount that can be used after purchase. If not specified, then product price will be used.
|
||||
/// Gets or sets gift card amount that can be used after purchase.
|
||||
/// </summary>
|
||||
public decimal? OverriddenGiftCardAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the product requires that other products are added to the cart (Product X requires Product Y)
|
||||
/// Gets or sets a value indicating whether the product requires that other products are added to the cart
|
||||
/// </summary>
|
||||
public bool RequireOtherProducts { get; set; }
|
||||
|
||||
|
|
@ -162,6 +169,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the product is download
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Digital good flag")]
|
||||
public bool IsDownload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -212,6 +220,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the product is recurring
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Subscription flag", BusinessRule = "Triggers automated billing cycles")]
|
||||
public bool IsRecurring { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -232,6 +241,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the product is rental
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Rental flag", BusinessRule = "Requires date selection in UI")]
|
||||
public bool IsRental { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -247,6 +257,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the entity is ship enabled
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Physical shipping toggle")]
|
||||
public bool IsShipEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -277,11 +288,12 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the tax category identifier
|
||||
/// </summary>
|
||||
public int TaxCategoryId { get; set; }
|
||||
public int TaxCategoryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating how to manage inventory
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Inventory logic selector", BusinessRule = "0:None, 1:Product, 2:Attributes")]
|
||||
public int ManageInventoryMethodId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -302,6 +314,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the stock quantity
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Physical quantity in warehouse", BusinessRule = "Decremented on order placement or paid")]
|
||||
public int StockQuantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -332,6 +345,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets a value backorder mode identifier
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Handling of out-of-stock purchases", BusinessRule = "Controls if StockQuantity can go negative")]
|
||||
public int BackorderModeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -356,7 +370,6 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether we allow adding to the cart/wishlist only attribute combinations that exist and have stock greater than zero.
|
||||
/// This option is used only when we have "manage inventory" set to "track inventory by product attributes"
|
||||
/// </summary>
|
||||
public bool AllowAddingOnlyExistingAttributeCombinations { get; set; }
|
||||
|
||||
|
|
@ -366,7 +379,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
public bool DisplayAttributeCombinationImagesOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this product is returnable (a customer is allowed to submit return request with this product)
|
||||
/// Gets or sets a value indicating whether this product is returnable
|
||||
/// </summary>
|
||||
public bool NotReturnable { get; set; }
|
||||
|
||||
|
|
@ -398,6 +411,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the price
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Base selling price", BusinessRule = "Used as primary value before discounts")]
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -408,6 +422,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the product cost
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Purchase price from vendor", BusinessRule = "Used for profit/margin calculation")]
|
||||
public decimal ProductCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -426,7 +441,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
public decimal MaximumCustomerEnteredPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether base price (PAngV) is enabled. Used by German users.
|
||||
/// Gets or sets a value indicating whether base price (PAngV) is enabled.
|
||||
/// </summary>
|
||||
public bool BasepriceEnabled { get; set; }
|
||||
|
||||
|
|
@ -456,33 +471,15 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
public bool MarkAsNew { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start date and time of the new product (set product as "New" from date). Leave empty to ignore this property
|
||||
/// Gets or sets the start date and time of the new product
|
||||
/// </summary>
|
||||
public DateTime? MarkAsNewStartDateTimeUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the end date and time of the new product (set product as "New" to date). Leave empty to ignore this property
|
||||
/// Gets or sets the end date and time of the new product
|
||||
/// </summary>
|
||||
public DateTime? MarkAsNewEndDateTimeUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this product has tier prices configured
|
||||
/// <remarks>The same as if we run TierPrices.Count > 0
|
||||
/// We use this property for performance optimization:
|
||||
/// if this property is set to false, then we do not need to load tier prices navigation property
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public bool HasTierPrices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this product has discounts applied
|
||||
/// <remarks>The same as if we run AppliedDiscounts.Count > 0
|
||||
/// We use this property for performance optimization:
|
||||
/// if this property is set to false, then we do not need to load Applied Discounts navigation property
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public bool HasDiscountsApplied { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the weight
|
||||
/// </summary>
|
||||
|
|
@ -515,8 +512,6 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
|
||||
/// <summary>
|
||||
/// Gets or sets a display order.
|
||||
/// This value is used when sorting associated products (used with "grouped" products)
|
||||
/// This value is used when sorting home page products
|
||||
/// </summary>
|
||||
public int DisplayOrder { get; set; }
|
||||
|
||||
|
|
@ -528,6 +523,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the entity has been deleted
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Soft-delete flag", BusinessRule = "Records remain in DB but hidden from UI")]
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -543,6 +539,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the product type
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ProductTypeId")]
|
||||
public ProductType ProductType
|
||||
{
|
||||
get => (ProductType)ProductTypeId;
|
||||
|
|
@ -552,6 +549,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the backorder mode
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => BackorderModeId")]
|
||||
public BackorderMode BackorderMode
|
||||
{
|
||||
get => (BackorderMode)BackorderModeId;
|
||||
|
|
@ -561,6 +559,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the download activation type
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => DownloadActivationTypeId")]
|
||||
public DownloadActivationType DownloadActivationType
|
||||
{
|
||||
get => (DownloadActivationType)DownloadActivationTypeId;
|
||||
|
|
@ -570,6 +569,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the gift card type
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => GiftCardTypeId")]
|
||||
public GiftCardType GiftCardType
|
||||
{
|
||||
get => (GiftCardType)GiftCardTypeId;
|
||||
|
|
@ -579,6 +579,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the low stock activity
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => LowStockActivityId")]
|
||||
public LowStockActivity LowStockActivity
|
||||
{
|
||||
get => (LowStockActivity)LowStockActivityId;
|
||||
|
|
@ -588,6 +589,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the value indicating how to manage inventory
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ManageInventoryMethodId")]
|
||||
public ManageInventoryMethod ManageInventoryMethod
|
||||
{
|
||||
get => (ManageInventoryMethod)ManageInventoryMethodId;
|
||||
|
|
@ -597,6 +599,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the cycle period for recurring products
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => RecurringCyclePeriodId")]
|
||||
public RecurringProductCyclePeriod RecurringCyclePeriod
|
||||
{
|
||||
get => (RecurringProductCyclePeriod)RecurringCyclePeriodId;
|
||||
|
|
@ -606,6 +609,7 @@ public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAc
|
|||
/// <summary>
|
||||
/// Gets or sets the period for rental products
|
||||
/// </summary>
|
||||
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => RentalPricePeriod")]
|
||||
public RentalPricePeriod RentalPricePeriod
|
||||
{
|
||||
get => (RentalPricePeriod)RentalPricePeriodId;
|
||||
|
|
@ -1,9 +1,29 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Toons;
|
||||
using AyCode.Interfaces.Entities;
|
||||
using LinqToDB.Mapping;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
public interface IMgStockQuantityHistory
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
public int QuantityAdjustment { get; set; }
|
||||
public int StockQuantity { get; set; }
|
||||
public string Message { get; set; }
|
||||
public int? CombinationId { get; set; }
|
||||
public int? WarehouseId { get; set; }
|
||||
public DateTime CreatedOnUtc { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a stock quantity change entry
|
||||
/// </summary>
|
||||
public partial class StockQuantityHistory : BaseEntity
|
||||
[AcBinarySerializable(false, true, false, true, false, false)]
|
||||
[Table(Name = nameof(StockQuantityHistory))]
|
||||
[ToonDescription("NopCommerce stock movement log", Purpose = "Audit trail for physical and logical stock movements")]
|
||||
public partial class StockQuantityHistory : BaseEntity, IMgStockQuantityHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the stock quantity adjustment
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a download activation type
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a gift card type
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
namespace Nop.Core.Domain.Common;
|
||||
|
||||
//namespace Mango.Nop.Core.NopDependencies;
|
||||
namespace Nop.Core.Domain.Common;
|
||||
/// <summary>
|
||||
/// Represents a soft-deleted (without actually deleting from storage) entity
|
||||
/// </summary>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a low stock activity
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method of inventory management
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a product type
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a recurring product cycle period
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Nop.Core.Domain.Catalog;
|
||||
namespace Nop.Core.Domain.Catalog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a rental product period (for prices)
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
# Mango.Nop.Core
|
||||
|
||||
@project {
|
||||
type = "framework"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Utils (in AyCode.Core repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Shared domain library containing entities, DTOs, interfaces, and nopCommerce entity mirrors. **net9.0**. Zero nopCommerce runtime dependency.
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Topic |
|
||||
|---|---|
|
||||
| `DTOS.md` | DTO system — two mapping strategies, all DTO types, GenericAttribute typed access |
|
||||
| `NOP_DEPENDENCIES.md` | NopDependencies pattern — mirror copies, namespace rules, file list |
|
||||
|
||||
## Folder Structure
|
||||
|
||||
| Folder | Purpose |
|
||||
|---|---|
|
||||
| `Dtos/` | DTO classes shared across consumers |
|
||||
| `Entities/` | Custom domain entities |
|
||||
| `Extensions/` | Extension methods for `BaseEntity` collections and `GenericAttribute` |
|
||||
| `Interfaces/` | DTO interfaces, soft-delete, foreign key markers |
|
||||
| `Loggers/` | `ILogger` / `Logger` — logging abstraction wrapping AyCode logger |
|
||||
| `Models/` | Login request/response models |
|
||||
| `NopDependencies/` | Mirror copies of nopCommerce entity classes (same namespaces as originals) |
|
||||
| `Services/` | `IMgLockService` interface |
|
||||
| `Utils/` | `CommonHelper2` — email validation, type conversion utilities |
|
||||
|
||||
## Entity Hierarchy
|
||||
|
||||
```
|
||||
Nop.Core.BaseEntity (NopDependencies/, implements IBaseEntity)
|
||||
+-- MgEntityBase (Entities/, implements IEntityInt from AyCode)
|
||||
+-- MgOrderDto<TOrderItemDto, TProductDto>
|
||||
+-- MgOrderItemDto<TProductDto>
|
||||
+-- MgProductDto
|
||||
+-- MgStockQuantityHistoryDto<TProductDto>
|
||||
+-- MgStockTaking<TStockTakingItem>
|
||||
+-- MgStockTakingItem<TStockTaking, TProduct>
|
||||
```
|
||||
|
||||
## Key Types (not in docs/)
|
||||
|
||||
### Entities
|
||||
|
||||
| Type | Key features |
|
||||
|---|---|
|
||||
| `MgEntityBase` | Inherits `BaseEntity`, implements `IEntityInt`. `ToString()` -> `"{TypeName}; Id: {Id}"` |
|
||||
| `MgStockTaking<TStockTakingItem>` | `StartDateTime`, `IsClosed`, `List<TStockTakingItem>` navigation. Implements `ITimeStampInfo` |
|
||||
| `MgStockTakingItem<TStockTaking, TProduct>` | `StockTakingId`, `ProductId`, `IsMeasured`, stock quantities. Navigations to parent and product |
|
||||
|
||||
### Extensions
|
||||
|
||||
| Method | Purpose |
|
||||
|---|---|
|
||||
| `UpdateBaseEntityCollection` | Add/update/remove entities in a list by Id match |
|
||||
|
||||
### Loggers
|
||||
|
||||
Extends AyCode logging infrastructure — for base types see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`.
|
||||
|
||||
| Type | Inherits | Purpose |
|
||||
|---|---|---|
|
||||
| `ILogger` | `IAcLoggerBase` | Mango-level logger interface |
|
||||
| `ILogger<TCategory>` | `ILogger` | Generic category logger interface |
|
||||
| `Logger` | `AcLoggerBase` | Logger implementation with `IAcLogWriterBase[]` writers |
|
||||
| `Logger<TCategory>` | `Logger` | Typed logger — category name from `typeof(TCategory).Name` |
|
||||
|
||||
### Models
|
||||
|
||||
| Type | Purpose |
|
||||
|---|---|
|
||||
| `MgLoginModelRequest` | `Email` + `Password` for SignalR/API login |
|
||||
| `MgLoginModelResponse` | `CustomerDto?` + `ErrorMessage`. Computed `IsSuccesLogin` |
|
||||
|
||||
### Other
|
||||
|
||||
| Type | Purpose |
|
||||
|---|---|
|
||||
| `NopCommonConst` | Abstract const class inheriting `AcConst` (placeholder) |
|
||||
| `CommonHelper2` | Email validation, IP validation, type conversion (`To<T>`), string utilities |
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `linq2db` — data access, `[Association]` / `[Table]` attributes
|
||||
- `Microsoft.AspNetCore.Mvc.NewtonsoftJson` — JSON serialization
|
||||
- Binary serialization: `AcBinarySerializer` (see `AyCode.Core/AyCode.Core/docs/BINARY/BINARY_FORMAT.md`). SignalR transport: `AcSignalR` (see `AyCode.Core/AyCode.Services/docs/SIGNALR/README.md`).
|
||||
- `AyCode.Core.Serializers.SourceGenerator` (ProjectReference, `OutputItemType="Analyzer"`) — compile-time binary serializer. See [`AyCode.Core/docs/BINARY_SOURCE_GEN.md`](../../../../../../Aycode/Source/AyCode.Core/AyCode.Core/docs/BINARY_SOURCE_GEN.md)
|
||||
- AyCode DLL references: `AyCode.Interfaces`, `AyCode.Core`, `AyCode.Utils`, `AyCode.Entities` (types only, no runtime nopCommerce dependency)
|
||||
|
||||
## AcBinary Source Generator (SGen)
|
||||
|
||||
9 types annotated with `[AcBinarySerializable]`. For general SGen docs see [`AyCode.Core/docs/BINARY_SOURCE_GEN.md`](../../../../../../Aycode/Source/AyCode.Core/AyCode.Core/docs/BINARY_SOURCE_GEN.md).
|
||||
|
||||
Requires `InternalsVisibleTo("Mango.Nop.Core")` in `AyCode.Core/Properties/AssemblyInfo.cs`.
|
||||
|
||||
### Excluded types
|
||||
|
||||
| Type | Reason |
|
||||
|---|---|
|
||||
| `DiscountProductMapping` | Base `DiscountMapping` hides `BaseEntity.Id` with readonly `new int Id { get; }` → CS0200. Uses compiled-expression fallback. |
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
using Mango.Nop.Core.Interfaces;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Nop.Core.Caching;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Data;
|
||||
using Nop.Services.Events;
|
||||
using Nop.Services.Logging;
|
||||
|
||||
namespace Mango.Nop.Core.Repositories;
|
||||
|
||||
|
||||
public class MgDbContextBase : IMgDbContextBase
|
||||
{
|
||||
//TODO: ez itt nem ay igazi, kitalálni vmit! - J.
|
||||
private readonly CacheKey _auctionAllKey = new("Nop.auction.all-{0}", AUCTION_PATTERN_KEY);
|
||||
public const string AUCTION_PATTERN_KEY = "Nop.auction.";
|
||||
|
||||
|
||||
protected ILogger Logger;
|
||||
protected INopDataProvider DataProvider;
|
||||
//protected IHttpContextAccessor HttpContextAccessor;
|
||||
|
||||
public MgDbContextBase(INopDataProvider dataProvider, ILogger logger)
|
||||
{
|
||||
Logger = logger;
|
||||
DataProvider = dataProvider;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Mango.Nop.Services;
|
||||
namespace Mango.Nop.Core.Services;
|
||||
|
||||
public interface IMgLockService
|
||||
{
|
||||
|
|
@ -2,14 +2,13 @@
|
|||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using Nop.Core.Infrastructure;
|
||||
|
||||
namespace Nop.Core;
|
||||
namespace Mango.Nop.Core.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a common helper
|
||||
/// </summary>
|
||||
public partial class CommonHelper
|
||||
public partial class CommonHelper2
|
||||
{
|
||||
#region Fields
|
||||
|
||||
|
|
@ -40,7 +39,7 @@ public partial class CommonHelper
|
|||
|
||||
if (!IsValidEmail(output))
|
||||
{
|
||||
throw new NopException("Email is not valid.");
|
||||
throw new Exception("Email is not valid.");
|
||||
}
|
||||
|
||||
return output;
|
||||
|
|
@ -71,31 +70,31 @@ public partial class CommonHelper
|
|||
return IPAddress.TryParse(ipAddress, out var _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate random digit code
|
||||
/// </summary>
|
||||
/// <param name="length">Length</param>
|
||||
/// <returns>Result string</returns>
|
||||
public static string GenerateRandomDigitCode(int length)
|
||||
{
|
||||
using var random = new SecureRandomNumberGenerator();
|
||||
var str = string.Empty;
|
||||
for (var i = 0; i < length; i++)
|
||||
str = string.Concat(str, random.Next(10).ToString());
|
||||
return str;
|
||||
}
|
||||
///// <summary>
|
||||
///// Generate random digit code
|
||||
///// </summary>
|
||||
///// <param name="length">Length</param>
|
||||
///// <returns>Result string</returns>
|
||||
//public static string GenerateRandomDigitCode(int length)
|
||||
//{
|
||||
// using var random = new SecureRandomNumberGenerator();
|
||||
// var str = string.Empty;
|
||||
// for (var i = 0; i < length; i++)
|
||||
// str = string.Concat(str, random.Next(10).ToString());
|
||||
// return str;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an random integer number within a specified rage
|
||||
/// </summary>
|
||||
/// <param name="min">Minimum number</param>
|
||||
/// <param name="max">Maximum number</param>
|
||||
/// <returns>Result</returns>
|
||||
public static int GenerateRandomInteger(int min = 0, int max = int.MaxValue)
|
||||
{
|
||||
using var random = new SecureRandomNumberGenerator();
|
||||
return random.Next(min, max);
|
||||
}
|
||||
///// <summary>
|
||||
///// Returns an random integer number within a specified rage
|
||||
///// </summary>
|
||||
///// <param name="min">Minimum number</param>
|
||||
///// <param name="max">Maximum number</param>
|
||||
///// <returns>Result</returns>
|
||||
//public static int GenerateRandomInteger(int min = 0, int max = int.MaxValue)
|
||||
//{
|
||||
// using var random = new SecureRandomNumberGenerator();
|
||||
// return random.Next(min, max);
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that a string doesn't exceed maximum allowed length
|
||||
|
|
@ -189,10 +188,10 @@ public partial class CommonHelper
|
|||
|
||||
var instanceType = instance.GetType();
|
||||
var pi = instanceType.GetProperty(propertyName)
|
||||
?? throw new NopException("No property '{0}' found on the instance of type '{1}'.", propertyName, instanceType);
|
||||
?? throw new Exception($"No property '{propertyName}' found on the instance of type '{instanceType}'.");
|
||||
|
||||
if (!pi.CanWrite)
|
||||
throw new NopException("The property '{0}' on the instance of type '{1}' does not have a setter.", propertyName, instanceType);
|
||||
throw new Exception($"No property '{propertyName}' found on the instance of type '{instanceType}'.");
|
||||
if (value != null && !value.GetType().IsAssignableFrom(pi.PropertyType))
|
||||
value = To(value, pi.PropertyType);
|
||||
pi.SetValue(instance, value, Array.Empty<object>());
|
||||
|
|
@ -253,23 +252,22 @@ public partial class CommonHelper
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert enum for front-end
|
||||
/// Splits the camel-case word into separate one
|
||||
/// </summary>
|
||||
/// <param name="str">Input string</param>
|
||||
/// <returns>Converted string</returns>
|
||||
public static string ConvertEnum(string str)
|
||||
/// <returns>Splitted string</returns>
|
||||
public static string SplitCamelCaseWord(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return string.Empty;
|
||||
var result = string.Empty;
|
||||
foreach (var c in str)
|
||||
if (c.ToString() != c.ToString().ToLowerInvariant())
|
||||
result += " " + c.ToString();
|
||||
else
|
||||
result += c.ToString();
|
||||
|
||||
var result = str.ToCharArray()
|
||||
.Select(p => p.ToString())
|
||||
.Aggregate(string.Empty, (current, c) => current + (c == c.ToUpperInvariant() ? $" {c}" : c));
|
||||
|
||||
//ensure no spaces (e.g. when the first letter is upper case)
|
||||
result = result.TrimStart();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -311,13 +309,4 @@ public partial class CommonHelper
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default file provider
|
||||
/// </summary>
|
||||
public static INopFileProvider DefaultFileProvider { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Architecture
|
||||
|
||||
## Framework vs. Consumer Boundary
|
||||
|
||||
This solution is **Layer 2 — Domain framework** (NopCommerce-plugin base). Built on Layer 0 (AyCode.Core), consumed by Layer 3 (specific plugin apps, plural and unknown). Full doctrine: AyCode.Core's `docs/ARCHITECTURE.md#framework-vs-consumer-boundary`.
|
||||
|
||||
### Layer position
|
||||
|
||||
```
|
||||
Layer 0 — AyCode.Core universal primitives (serializers, logging, entities)
|
||||
Layer 2 — Mango.Nop.Core this solution — NopCommerce-plugin domain framework
|
||||
Layer 3 — Consumer plugin apps specific plugin projects with business logic
|
||||
```
|
||||
|
||||
### Nop-plugin-framework-specific notes
|
||||
|
||||
- **`NopDependencies/`** mirrors NopCommerce entity classes under the original Nop namespaces — no runtime Nop dependency, compile-time type parity only
|
||||
- **DTOs** use type parameters for consumer entity types (e.g. `MgOrderDto<TOrderItemDto, TProductDto>`) — concrete types are provided by plugin projects
|
||||
- **Entity hierarchy**: `Nop.Core.BaseEntity` (mirrored) → `MgEntityBase` → generic DTOs/entities parameterized for consumers
|
||||
- **`Logger` / `ILogger`** extend AyCode logging (`AcLoggerBase`) — plugin projects may subclass again if business-specific writers are needed
|
||||
- **Zero runtime Nop dependency** — plugin apps bring their own Nop runtime
|
||||
|
||||
### What belongs here vs. in a plugin
|
||||
|
||||
**Yes, framework:**
|
||||
- NopCommerce entity mirrors (for cross-plugin type sharing)
|
||||
- Generic DTOs with type parameters
|
||||
- Shared interfaces and extensions for NopCommerce domain entities
|
||||
- Logger base with AyCode integration
|
||||
|
||||
**No, plugin only:**
|
||||
- Plugin-specific business logic
|
||||
- Plugin-named types (`XxxPlugin`, `XxxService` where Xxx is a specific product)
|
||||
- Hardcoded plugin configuration, tenant, or product IDs
|
||||
|
||||
## Related Documents
|
||||
|
||||
| Topic | Document |
|
||||
|---|---|
|
||||
| Framework-first doctrine (full) | `../../../../../../../Aycode/Source/AyCode.Core/docs/ARCHITECTURE.md#framework-vs-consumer-boundary` |
|
||||
| Placement rules, anti-patterns | `CONVENTIONS.md#framework-first-placement` |
|
||||
| DTO system and GenericAttribute typed access | `DTOS.md` |
|
||||
| NopDependencies mirror pattern | `NOP_DEPENDENCIES.md` |
|
||||
| Folder structure and key types index | `../README.md` |
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Conventions
|
||||
|
||||
For core framework conventions (Ac prefix, naming patterns, Session/Transaction, etc.) see AyCode.Core's `docs/CONVENTIONS.md`.
|
||||
|
||||
## Framework-First Placement
|
||||
|
||||
Follow the doctrine in AyCode.Core's `docs/CONVENTIONS.md#framework-first-placement`. Same verdict table and hard rules apply.
|
||||
|
||||
**Nop-plugin-framework-specific additions:**
|
||||
- `NopDependencies/` mirror classes stay in their original Nop namespaces — don't rename, don't add plugin-specific fields
|
||||
- DTOs parameterize via type arguments, not concrete plugin types
|
||||
- Any plugin-specific business logic belongs in the plugin project, not here
|
||||
- Adding a new shared DTO: ensure it is useful for 2+ plugin projects (otherwise keep in the single plugin)
|
||||
|
||||
## Naming
|
||||
|
||||
- **`Mg` prefix**: for Mango-framework-level types (e.g. `MgEntityBase`, `MgOrderDto`, `MgProductDto`)
|
||||
- **`IMg` prefix**: for Mango-framework-level interfaces (e.g. `IMgModelDtoBase`, `IMgSoftRemoveEntity`)
|
||||
- **No prefix**: for NopCommerce entity mirrors in `NopDependencies/` — they stay in Nop namespace, same name as upstream
|
||||
|
||||
## XML Documentation
|
||||
|
||||
`<summary>` — brief, developer-facing, readable in VS IntelliSense tooltip. NO implementation details, NO wire-format / byte-level / perf specifics — those live in `docs/TOPIC/*.md`. Add `<example>` only when usage is non-obvious; otherwise omit.
|
||||
|
||||
## See Also
|
||||
|
||||
- Core framework naming (`Ac` / `IAc` prefixes), patterns, serialization conventions: AyCode.Core's `docs/CONVENTIONS.md`
|
||||
- Framework-first verdict table (full): AyCode.Core's `docs/CONVENTIONS.md#framework-first-placement`
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# DTO System
|
||||
|
||||
> Part of `Mango.Nop.Core`. See `Mango.Nop.Core/README.md` for project overview.
|
||||
|
||||
## Two Mapping Strategies
|
||||
|
||||
### Strategy 1: `ModelDtoBase<T>` (simple DTOs)
|
||||
|
||||
Used by: `CustomerDto`, `MgGenericAttributeDto`
|
||||
|
||||
```
|
||||
ModelDtoBase → ModelDtoBase<Customer> → CustomerDto
|
||||
```
|
||||
|
||||
- Manual `CopyEntityValuesToDto`/`CopyDtoValuesToEntity` overrides
|
||||
- `CreateMainEntity()` uses `Activator.CreateInstance<T>()`
|
||||
- No LinqToDB associations
|
||||
|
||||
### Strategy 2: `MgEntityBase` + `IModelDtoBase<T>` (complex DTOs)
|
||||
|
||||
Used by: `MgOrderDto`, `MgOrderItemDto`, `MgStockQuantityHistoryDto`
|
||||
|
||||
```
|
||||
BaseEntity → MgEntityBase → MgOrderDto<TOrderItemDto, TProductDto> : IModelDtoBase<Order>
|
||||
```
|
||||
|
||||
- Uses `PropertyHelper.CopyPublicValueTypeProperties()` for bulk value-type copy
|
||||
- LinqToDB `[Association]` navigation properties
|
||||
- Generic type parameters for child DTOs
|
||||
|
||||
### Strategy 3: Entity inheritance (MgProductDto)
|
||||
|
||||
```
|
||||
BaseEntity → MgEntityBase → MgProductDto : IMgProductDto
|
||||
```
|
||||
|
||||
- No `IModelDtoBase<Product>` (entity mapping methods are commented out)
|
||||
- Direct property declarations mirroring `Product` fields
|
||||
|
||||
## DTO Types
|
||||
|
||||
| Type | Generic params | Maps to entity | Key features |
|
||||
|---|---|---|---|
|
||||
| `ModelDtoBase` | — | — | Abstract base, `int Id` only |
|
||||
| `ModelDtoBase<TMainEntity>` | `TMainEntity : BaseEntity` | `TMainEntity` | `CreateMainEntity()`, `CopyEntityValuesToDto()`, `CopyDtoValuesToEntity()` |
|
||||
| `MgOrderDto<TOrderItemDto, TProductDto>` | `TOrderItemDto : IMgOrderItemDto<TProductDto>`, `TProductDto : IMgProductDto` | `Order` | Has `Customer`, `List<TOrderItemDto>`, `List<OrderNote>` navigations. Enum wrappers: `OrderStatus`, `ShippingStatus`, `PaymentStatus` |
|
||||
| `MgOrderItemDto<TProductDto>` | `TProductDto : IMgProductDto` | `OrderItem` | Has `TProductDto?` navigation. Computed `ProductName` property |
|
||||
| `MgProductDto` | — | *(commented out)* | Base product DTO. Name, Price, StockQuantity, dimensions. No entity mapping methods (currently commented out) |
|
||||
| `MgGenericAttributeDto` | — | `GenericAttribute` | Inherits `GenericAttribute` directly (not `ModelDtoBase`). Full bidirectional mapping |
|
||||
| `MgStockQuantityHistoryDto<TProductDto>` | `TProductDto : IMgProductDto` | `StockQuantityHistory` | Has `TProductDto` navigation. Mapping methods `NotImplementedException` (stub) |
|
||||
| `CustomerDto` | — | `Customer` | `Username`, `Email`, `FirstName`, `LastName`, `FullName` computed. Uses `[AcBinarySerializable]` and LinqToDB `[Table]` |
|
||||
|
||||
## DTO Interfaces
|
||||
|
||||
| Interface | Extends | Purpose |
|
||||
|---|---|---|
|
||||
| `IModelDtoBase` | `IEntityInt`, `IModelDtoBaseEmpty` | Marker for all DTOs with `int Id` |
|
||||
| `IModelDtoBase<TMainEntity>` | `IModelDtoBase` | Bidirectional mapping contract: `CreateMainEntity()`, `CopyDtoValuesToEntity()`, `CopyEntityValuesToDto()` |
|
||||
| `IMgOrderDto<TOrderItemDto, TProductDto>` | `IEntityInt`, `ISoftDeletedEntity` | Order DTO contract with navigation properties and initialization methods |
|
||||
| `IMgOrderItemDto<TProductDto>` | `IEntityInt` | Order item DTO contract with product navigation |
|
||||
| `IMgProductDto` | `IEntityInt`, `ILocalizedEntity`, `ISlugSupported`, `IAclSupported`, `IStoreMappingSupported`, `ISoftDeletedEntity` | Product DTO contract |
|
||||
|
||||
## GenericAttribute Typed Access
|
||||
|
||||
`GenericAttributeExtensions` provides typed access to nopCommerce's polymorphic key-value store:
|
||||
|
||||
| Method | Signature | Purpose |
|
||||
|---|---|---|
|
||||
| `GetValueOrNull<TValue>` | `(IEnumerable<GenericAttribute>, string key) -> TValue?` | Get typed value, returns null if not found |
|
||||
| `GetValueOrDefault<TValue>` | `(IEnumerable<GenericAttribute>, string key, TValue default) -> TValue` | Same with default fallback |
|
||||
| `TryGetValue<TValue>` | `(IEnumerable<GenericAttribute>, string key, out TValue?) -> bool` | Try-pattern for GA value |
|
||||
| `AddNewGenericAttribute` | `(ICollection<GenericAttribute>, ...) -> GenericAttribute` | Create and add new GA with UTC timestamp |
|
||||
|
||||
**Rule:** Always use these extension methods — never parse `GenericAttribute.Value` strings manually.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# NopDependencies Pattern
|
||||
|
||||
> Part of `Mango.Nop.Core`. See `Mango.Nop.Core/README.md` for project overview.
|
||||
|
||||
## Why
|
||||
|
||||
`Mango.Nop.Core` has **zero nopCommerce runtime dependency**. This allows it to be referenced by projects that don't have the full nopCommerce stack (e.g. Blazor/MAUI clients that only need DTOs and interfaces).
|
||||
|
||||
To achieve this, `NopDependencies/` contains **mirror copies** of nopCommerce entity classes with the **same namespace** as the originals:
|
||||
|
||||
```csharp
|
||||
// In NopDependencies/BaseEntity.cs — same namespace as nopCommerce
|
||||
namespace Nop.Core;
|
||||
public abstract partial class BaseEntity : IBaseEntity
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
At compile time, projects referencing only `Mango.Nop.Core` get these mirror types. Projects with the full nopCommerce stack get the real types — they are type-compatible because they share the same namespace and shape.
|
||||
|
||||
## Rules
|
||||
|
||||
- **DO NOT modify** files in `NopDependencies/` unless the nopCommerce version changes
|
||||
- **DO NOT change namespaces** — they must match the original nopCommerce types exactly
|
||||
- All mirror classes are `partial` — they can be extended but should not be modified directly
|
||||
|
||||
## File List
|
||||
|
||||
| File | Namespace | Type |
|
||||
|---|---|---|
|
||||
| `BaseEntity.cs` | `Nop.Core` | `BaseEntity` (abstract, `IBaseEntity`) + `IBaseEntity` interface |
|
||||
| `Catalogs/Customer.cs` | `Nop.Core.Domain.Customers` | `Customer` |
|
||||
| `Catalogs/CustomerRole.cs` | `Nop.Core.Domain.Customers` | `CustomerRole` |
|
||||
| `Catalogs/Order.cs` | `Nop.Core.Domain.Orders` | `Order` |
|
||||
| `Catalogs/OrderItem.cs` | `Nop.Core.Domain.Orders` | `OrderItem` |
|
||||
| `Catalogs/OrderNote.cs` | `Nop.Core.Domain.Orders` | `OrderNote` |
|
||||
| `Catalogs/Product.cs` | `Nop.Core.Domain.Catalog` | `Product` |
|
||||
| `Catalogs/GenericAttribute.cs` | `Nop.Core.Domain.Common` | `GenericAttribute` |
|
||||
| `Catalogs/StockQuantityHistory.cs` | `Nop.Core.Domain.Catalog` | `StockQuantityHistory` |
|
||||
| `Catalogs/DiscountMapping.cs` | `Nop.Core.Domain.Catalog` | `DiscountMapping` |
|
||||
| `Catalogs/DiscountProductMapping.cs` | `Nop.Core.Domain.Catalog` | `DiscountProductMapping` |
|
||||
|
||||
**Enums:** `OrderStatus`, `PaymentStatus`, `ShippingStatus`, `ProductType`, `ManageInventoryMethod`, `BackorderMode`, `LowStockActivity`, `GiftCardType`, `RentalPricePeriod`, `RecurringProductCyclePeriod`, `DownloadActivationType`, `TaxDisplayType`, `VatNumberStatus`
|
||||
|
||||
**Interfaces:** `ISoftDeletedEntity`, `ILocalizedEntity`, `ISlugSupported`, `IAclSupported`, `IStoreMappingSupported`, `IDiscountSupported`
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Mango.Nop.Core documentation
|
||||
|
||||
Topic documentation for the `Mango.Nop.Core` project (Layer 2 framework — NopCommerce-adjacent base types).
|
||||
|
||||
## Reference docs (flat)
|
||||
|
||||
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — Project architecture
|
||||
- [`CONVENTIONS.md`](CONVENTIONS.md) — Project-specific conventions
|
||||
- [`DTOS.md`](DTOS.md) — DTO system, mapping strategies
|
||||
- [`NOP_DEPENDENCIES.md`](NOP_DEPENDENCIES.md) — NopCommerce entity mirror pattern
|
||||
|
||||
## Navigation
|
||||
|
||||
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. All docs at this level are single-file reference; topic folders (e.g., LOGGING/) exist only at sub-projects that host a variant (see `../Mango.Nop.Services/docs/LOGGING/`).
|
||||
|
||||
## See also
|
||||
|
||||
- **Repo-level conventions**: `../../docs/CONVENTIONS.md`
|
||||
- **Base framework**: `../../../../../Aycode/Source/AyCode.Core/` docs
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Mango.Nop.Core.Interfaces;
|
||||
namespace Mango.Nop.Data.Interfaces;
|
||||
|
||||
public interface IMgDalBase //: IAcDalBase//: IDisposable
|
||||
{
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Mango.Nop.Core.Loggers;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Data;
|
||||
using System.Transactions;
|
||||
|
||||
namespace Mango.Nop.Data.Interfaces;
|
||||
|
||||
public interface IMgDbContextBase //: IAcDbContextBase
|
||||
{
|
||||
ILogger Logger { get; init; }
|
||||
INopDataProvider DataProvider { get; init; }
|
||||
|
||||
IRepository<Order> Orders { get; set; }
|
||||
IRepository<Product> Products { get; set; }
|
||||
|
||||
bool Transaction(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false);
|
||||
bool TransactionSafe(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false);
|
||||
|
||||
Task<bool> TransactionAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false);
|
||||
Task<bool> TransactionSafeAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Mango.Nop.Core.Entities;
|
||||
using Nop.Data;
|
||||
|
||||
namespace Mango.Nop.Core.Interfaces;
|
||||
namespace Mango.Nop.Data.Interfaces;
|
||||
|
||||
public interface IMgDbTableBase //: IAcDbTableBase
|
||||
{
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>bin\FruitBank</BaseOutputPath>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Core\Nop.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Data\Nop.Data.csproj" />
|
||||
<ProjectReference Include="..\Mango.Nop.Core\Mango.Nop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Core.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Utils">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="docs\**\*.md" />
|
||||
<None Include="**\README.md" Exclude="$(DefaultItemExcludes);docs\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Mango.Nop.Data
|
||||
|
||||
@project {
|
||||
type = "framework"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Utils (in AyCode.Core repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Data access layer with repository base classes and DB context abstractions. **net9.0**.
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Topic |
|
||||
|---|---|
|
||||
| `REPOSITORIES.md` | MgDbTableBase, MgDtoDbTableBase — CRUD hooks, timestamps, delete rules, event bridging |
|
||||
| `TRANSACTIONS.md` | MgDbContextBase — 4 transaction methods, lock strategy, callback contract |
|
||||
|
||||
## Folder Structure
|
||||
|
||||
| Folder | Purpose |
|
||||
|---|---|
|
||||
| `Interfaces/` | Repository interfaces: `IMgDalBase`, `IMgDbContextBase`, `IMgDbTableBase` |
|
||||
| `Repositories/` | Repository base implementations: `MgDalBase`, `MgDbContextBase`, `MgDbTableBase`, `MgDtoDbTableBase` |
|
||||
|
||||
## Inheritance Chains
|
||||
|
||||
### Repository hierarchy
|
||||
```
|
||||
nopCommerce EntityRepository<TEntity> (Nop.Data)
|
||||
+-- MgDbTableBase<TEntity>
|
||||
+-- MgDtoDbTableBase<TDtoEntity, TMainEntity>
|
||||
```
|
||||
|
||||
### Context hierarchy
|
||||
```
|
||||
MgDbContextBase (abstract, implements IMgDbContextBase)
|
||||
+-- [Consumer DbContexts in plugins]
|
||||
```
|
||||
|
||||
### DAL hierarchy
|
||||
```
|
||||
MgDalBase<TDbContext> (implements IMgDalBase<TDbContext>)
|
||||
+-- [Consumer DALs in plugins]
|
||||
```
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Purpose |
|
||||
|---|---|
|
||||
| `IMgDbTableBase` | Marker interface for repository classes |
|
||||
| `IMgDbContextBase` | Contract: `Logger`, `DataProvider`, `Orders`, `Products`, 4 transaction methods |
|
||||
| `IMgDalBase` | `Name` property |
|
||||
| `IMgDalBase<TDbContext>` | `Context`, `MutextLock` — typed access to DB context |
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `Mango.Nop.Core` (ProjectReference)
|
||||
- `Nop.Core`, `Nop.Data` (nopCommerce ProjectReferences)
|
||||
- `Microsoft.AspNetCore.Mvc.NewtonsoftJson`
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
using System.Linq.Expressions;
|
||||
using System.Transactions;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
using Mango.Nop.Data.Interfaces;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Caching;
|
||||
using Nop.Core.Configuration;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Data;
|
||||
using Nop.Data.DataProviders;
|
||||
|
||||
namespace Mango.Nop.Core.Repositories;
|
||||
namespace Mango.Nop.Data.Repositories;
|
||||
|
||||
public class MgDalBase<TDbContext> : IMgDalBase<TDbContext> where TDbContext : IMgDbContextBase
|
||||
public abstract class MgDalBase<TDbContext> : IMgDalBase<TDbContext> where TDbContext : IMgDbContextBase
|
||||
{
|
||||
public string Name { get; }
|
||||
public TDbContext Context { get; }
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
using System.Transactions;
|
||||
using AyCode.Core.Consts;
|
||||
using AyCode.Core.Helpers;
|
||||
using AyCode.Core.Loggers;
|
||||
using AyCode.Utils.Extensions;
|
||||
using Mango.Nop.Core.Loggers;
|
||||
using Mango.Nop.Core.Services;
|
||||
using Mango.Nop.Data.Interfaces;
|
||||
using Nop.Core.Caching;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Data;
|
||||
|
||||
namespace Mango.Nop.Data.Repositories;
|
||||
|
||||
|
||||
public abstract class MgDbContextBase : IMgDbContextBase
|
||||
{
|
||||
//TODO: ez itt nem ay igazi, kitalálni vmit! - J.
|
||||
private readonly CacheKey _auctionAllKey = new("Nop.auction.all-{0}", AUCTION_PATTERN_KEY);
|
||||
public const string AUCTION_PATTERN_KEY = "Nop.auction.";
|
||||
|
||||
protected readonly IMgLockService LockService;
|
||||
|
||||
public ILogger Logger { get; init; }
|
||||
public INopDataProvider DataProvider { get; init; }
|
||||
|
||||
public IRepository<Order> Orders { get; set; }
|
||||
public IRepository<OrderItem> OrderItems { get; set; }
|
||||
|
||||
public IRepository<Product> Products { get; set; }
|
||||
|
||||
//public IHttpContextAccessor HttpContextAccessor { get; init; }
|
||||
|
||||
public MgDbContextBase(IRepository<Product> productRepository, IRepository<Order> orderRepository, IRepository<OrderItem> orderItemRepository, INopDataProvider dataProvider, IMgLockService lockService, ILogger logger)
|
||||
{
|
||||
LockService = lockService;
|
||||
|
||||
Logger = logger;// new Logger<MgDbContextBase>(logWriters.ToArray());
|
||||
DataProvider = dataProvider;
|
||||
|
||||
Products = productRepository;
|
||||
|
||||
Orders = orderRepository;
|
||||
OrderItems = orderItemRepository;
|
||||
}
|
||||
|
||||
private static TransactionScope CreateTransactionScope(TransactionScopeOption transactionScopeOption = TransactionScopeOption.Required)
|
||||
{
|
||||
//TransactionManager.ImplicitDistributedTransactions = true;
|
||||
|
||||
var transactionOptions = new TransactionOptions
|
||||
{
|
||||
IsolationLevel = IsolationLevel.ReadCommitted,
|
||||
Timeout = TransactionManager.DefaultTimeout
|
||||
};
|
||||
|
||||
return new TransactionScope(transactionScopeOption, transactionOptions, TransactionScopeAsyncFlowOption.Enabled);
|
||||
//return new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled);
|
||||
}
|
||||
|
||||
public bool Transaction(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false)
|
||||
=> TransactionInner(callbackTransactionBody, throwException);
|
||||
|
||||
/// <summary>
|
||||
/// Using LoskService, global lock!
|
||||
/// </summary>
|
||||
/// <param name="callbackTransactionBody"></param>
|
||||
/// <param name="throwException"></param>
|
||||
/// <returns></returns>
|
||||
public bool TransactionSafe(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false)
|
||||
{
|
||||
using (LockService.SemaphoreSlim.UseWait())
|
||||
{
|
||||
return TransactionInner(callbackTransactionBody, throwException);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> TransactionAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false)
|
||||
=> TaskHelper.ToThreadPoolTask(() => TransactionInnerAsync(callbackTransactionBody, throwException));
|
||||
|
||||
/// <summary>
|
||||
/// Using LoskService, global lock!
|
||||
/// </summary>
|
||||
/// <param name="callbackTransactionBody"></param>
|
||||
/// <param name="throwException"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> TransactionSafeAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false)
|
||||
{
|
||||
return TaskHelper.ToThreadPoolTask(async () =>
|
||||
{
|
||||
using (await LockService.SemaphoreSlim.UseWaitAsync())
|
||||
{
|
||||
return await TransactionInnerAsync(callbackTransactionBody, throwException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<bool> TransactionInnerAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false)
|
||||
{
|
||||
bool result;
|
||||
|
||||
try
|
||||
{
|
||||
using (var transaction = CreateTransactionScope())// new TransactionScope( /*TransactionScopeOption.RequiresNew, */TransactionScopeAsyncFlowOption.Enabled))
|
||||
{
|
||||
result = await callbackTransactionBody(transaction);
|
||||
|
||||
if (result) transaction.Complete();
|
||||
}
|
||||
|
||||
if (!result) Logger.Warning($"TransactionInnerAsync({this}) transaction ROLLBACK!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (throwException) throw;
|
||||
|
||||
result = false;
|
||||
Logger.Error($"TransactionInnerAsync({this}) transaction ROLLBACK! ex: {ex.Message}{AcEnv.NL}", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TransactionInner(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false)
|
||||
{
|
||||
bool result;
|
||||
|
||||
try
|
||||
{
|
||||
using(var transaction = CreateTransactionScope()) //new TransactionScope( /*TransactionScopeOption.RequiresNew, */TransactionScopeAsyncFlowOption.Enabled)
|
||||
{
|
||||
result = callbackTransactionBody(transaction);
|
||||
|
||||
if (result) transaction.Complete();
|
||||
}
|
||||
|
||||
if (!result) Logger.Warning($"TransactionInner({this}) transaction ROLLBACK!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (throwException) throw;
|
||||
|
||||
result = false;
|
||||
Logger.Error($"TransactionInnerAsync({this}) transaction error! ex: {ex.Message}{AcEnv.NL}", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetType().Name;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +1,35 @@
|
|||
using System.Linq.Expressions;
|
||||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Interfaces.Entities;
|
||||
using AyCode.Interfaces.TimeStampInfo;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
using Mango.Nop.Core.Loggers;
|
||||
using Mango.Nop.Data.Interfaces;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Caching;
|
||||
using Nop.Core.Configuration;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Data;
|
||||
using Nop.Services.Logging;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Mango.Nop.Core.Repositories;
|
||||
namespace Mango.Nop.Data.Repositories;
|
||||
|
||||
public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings, ILogger logger)
|
||||
public abstract class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||
: EntityRepository<TEntity>(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings), IMgDbTableBase where TEntity : BaseEntity
|
||||
{
|
||||
protected ILogger Logger = logger;
|
||||
//protected ILogger Logger = logger;
|
||||
protected IEventPublisher EventPublisher = eventPublisher;
|
||||
protected INopDataProvider DataProvider = dataProvider;
|
||||
protected IShortTermCacheManager ShortTermCacheManager = shortTermCacheManager;
|
||||
|
||||
public virtual IQueryable<TEntity> GetAll() => Table;
|
||||
|
||||
#region SetTimeStampInfos
|
||||
|
||||
private static void SetTimeStampCreated(TEntity entity)
|
||||
{
|
||||
if (typeof(TEntity).GetInterface(nameof(ITimeStampCreated)) != null)
|
||||
{
|
||||
((entity as ITimeStampCreated)!).Created = DateTime.UtcNow;
|
||||
(entity as ITimeStampCreated)!.Created = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,12 +37,16 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
|
|||
{
|
||||
if (typeof(TEntity).GetInterface(nameof(ITimeStampModified)) != null)
|
||||
{
|
||||
((entity as ITimeStampModified)!).Modified = DateTime.UtcNow;
|
||||
(entity as ITimeStampModified)!.Modified = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion SetTimeStampInfos
|
||||
|
||||
//TODO: Transaction-be tenni az event-eket! - J.
|
||||
|
||||
#region OnCrudEvents
|
||||
|
||||
private void OnInsert(IList<TEntity> entities)
|
||||
{
|
||||
foreach (var entity in entities) OnInsert(entity);
|
||||
|
|
@ -61,7 +69,7 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
|
|||
{
|
||||
SetTimeStampModified(entity);
|
||||
|
||||
_staticCacheManager.ClearAsync().GetAwaiter().GetResult();
|
||||
_staticCacheManager.ClearAsync().GetAwaiter().GetResult(); //TODO: EZ MI??? - J.
|
||||
//_staticCacheManager.RemoveByPrefix(MgDbContextBase.AUCTION_PATTERN_KEY);
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +87,7 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
|
|||
|
||||
//_staticCacheManager.RemoveByPrefix(MgDbContextBase.AUCTION_PATTERN_KEY);
|
||||
}
|
||||
|
||||
#endregion OnCrudEvents
|
||||
|
||||
#region Insert
|
||||
|
|
@ -106,9 +115,11 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
|
|||
OnInsert(entities);
|
||||
return base.InsertAsync(entities, publishEvent);
|
||||
}
|
||||
|
||||
#endregion Insert
|
||||
|
||||
#region Update
|
||||
|
||||
public override void Update(TEntity entity, bool publishEvent = true)
|
||||
{
|
||||
OnUpdate(entity);
|
||||
|
|
@ -132,29 +143,25 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
|
|||
OnUpdate(entities);
|
||||
return base.UpdateAsync(entities, publishEvent);
|
||||
}
|
||||
|
||||
#endregion Update
|
||||
|
||||
#region Delete
|
||||
protected override Task DeleteAsync(IList<TEntity> entities)
|
||||
{
|
||||
OnDelete(entities);
|
||||
return base.DeleteAsync(entities);
|
||||
}
|
||||
|
||||
protected override Task DeleteAsync<T>(IList<T> entities)
|
||||
{
|
||||
if(typeof(T) == typeof(TEntity)) OnDelete(entities.Cast<TEntity>().ToList());
|
||||
|
||||
return base.DeleteAsync(entities);
|
||||
}
|
||||
|
||||
protected override IQueryable<TEntity> AddDeletedFilter(IQueryable<TEntity> query, in bool includeDeleted)
|
||||
{
|
||||
foreach (var entity in query) OnDelete(entity);
|
||||
//EZ NEM DELETE METHOD! A GET-EKNÉL HASZNÁLJA A ISFOTDELETE FILTER-HEZ! - J.
|
||||
//foreach (var entity in query) OnDelete(entity);
|
||||
|
||||
return base.AddDeletedFilter(query, in includeDeleted);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(int entityId, bool publishEvent = true)
|
||||
{
|
||||
var entity = await GetByIdAsync(entityId);
|
||||
await DeleteAsync(entity, publishEvent);
|
||||
}
|
||||
|
||||
public override Task DeleteAsync(TEntity entity, bool publishEvent = true)
|
||||
{
|
||||
OnDelete(entity);
|
||||
|
|
@ -167,22 +174,46 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
|
|||
return base.DeleteAsync(entities, publishEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// It does not call the PublishEvent() and OnDelete() functions!
|
||||
/// </summary>
|
||||
/// <param name="predicate"></param>
|
||||
/// <returns></returns>
|
||||
public override Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
//TODO:ide mit kéne?! - J.
|
||||
return base.DeleteAsync(predicate);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool publishEvent)
|
||||
{
|
||||
if (publishEvent)
|
||||
{
|
||||
var deletingEntities = await Table.Where(predicate).ToListAsync();
|
||||
await base.DeleteAsync(deletingEntities, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await DeleteAsync(predicate);
|
||||
}
|
||||
|
||||
public override void Delete(TEntity entity, bool publishEvent = true)
|
||||
{
|
||||
OnDelete(entity);
|
||||
base.Delete(entity, publishEvent);
|
||||
}
|
||||
|
||||
public override void Delete(IList<TEntity> entities, bool publishEvent = true)
|
||||
{
|
||||
OnDelete(entities);
|
||||
base.Delete(entities, publishEvent);
|
||||
}
|
||||
|
||||
public override int Delete(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
//TODO:ide mit kéne?! - J.
|
||||
return base.Delete(predicate);
|
||||
}
|
||||
|
||||
#endregion Delete
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using System.Linq.Expressions;
|
||||
using LinqToDB;
|
||||
using Mango.Nop.Core.Loggers;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Caching;
|
||||
using Nop.Core.Configuration;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Data;
|
||||
|
||||
namespace Mango.Nop.Data.Repositories;
|
||||
|
||||
public abstract class MgDtoDbTableBase<TDtoEntity, TMainEntity> : MgDbTableBase<TDtoEntity> where TDtoEntity : BaseEntity/*, IMgModelDtoBase<TDtoEntity>*/ where TMainEntity : BaseEntity
|
||||
{
|
||||
public Type MainEntityType { get; } = typeof(Order);
|
||||
|
||||
public MgDtoDbTableBase(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
|
||||
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<TMainEntity?> GetMainEntityById(int id) => await _dataProvider.GetTable<TMainEntity>().FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
public virtual async Task<int> DeleteMainEntityById(int id, bool publishEvent = true)
|
||||
{
|
||||
var affectedRows = 0;
|
||||
|
||||
var mainEntity = await GetMainEntityById(id);
|
||||
if (mainEntity == null) return affectedRows;
|
||||
|
||||
affectedRows = await _dataProvider.GetTable<TMainEntity>().DeleteAsync(x => x.Id == id);
|
||||
|
||||
if (publishEvent) await _eventPublisher.PublishAsync(new EntityDeletedEvent<TMainEntity>(mainEntity));
|
||||
return affectedRows;
|
||||
}
|
||||
|
||||
public Task HandleEventAsync(EntityDeletedEvent<TDtoEntity> eventMessage)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
|
||||
public async Task HandleEventAsync(EntityInsertedEvent<TDtoEntity> eventMessage)
|
||||
{
|
||||
var mainEntity = await GetMainEntityById(eventMessage.Entity.Id);
|
||||
if (mainEntity == null) throw new Exception($"MgDtoDbTableBase<{typeof(TDtoEntity).Name}, {MainEntityType.Name}>->EntityInsertedEvent<{typeof(TDtoEntity).Name}>(); (mainEntity == null); Id: {eventMessage.Entity.Id}");
|
||||
|
||||
await _eventPublisher.PublishAsync(new EntityInsertedEvent<TMainEntity>(mainEntity));
|
||||
}
|
||||
|
||||
public async Task HandleEventAsync(EntityUpdatedEvent<TDtoEntity> eventMessage)
|
||||
{
|
||||
var mainEntity = await GetMainEntityById(eventMessage.Entity.Id);
|
||||
if (mainEntity == null) throw new Exception($"MgDtoDbTableBase<{typeof(TDtoEntity).Name}, {MainEntityType.Name}>->EntityUpdatedEvent<{typeof(TDtoEntity).Name}>(); (mainEntity == null); Id: {eventMessage.Entity.Id}");
|
||||
|
||||
await _eventPublisher.PublishAsync(new EntityUpdatedEvent<TMainEntity>(mainEntity));
|
||||
}
|
||||
|
||||
public override Task DeleteAsync(int entityId, bool publishEvent = true) => DeleteMainEntityById(entityId, publishEvent);
|
||||
|
||||
public override Task DeleteAsync(TDtoEntity entity, bool publishEvent = true) => DeleteMainEntityById(entity.Id, publishEvent);
|
||||
|
||||
public override Task DeleteAsync(IList<TDtoEntity> entities, bool publishEvent = true)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
|
||||
public override Task<int> DeleteAsync(Expression<Func<TDtoEntity, bool>> predicate)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
|
||||
public override Task DeleteAsync(Expression<Func<TDtoEntity, bool>> predicate, bool publishEvent)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
|
||||
public override void Delete(TDtoEntity entity, bool publishEvent = true)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
|
||||
public override void Delete(IList<TDtoEntity> entities, bool publishEvent = true)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
|
||||
public override int Delete(Expression<Func<TDtoEntity, bool>> predicate)
|
||||
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Mango.Nop.Data documentation
|
||||
|
||||
Topic documentation for the `Mango.Nop.Data` project (Layer 2, data access layer).
|
||||
|
||||
## Reference docs (flat)
|
||||
|
||||
- [`REPOSITORIES.md`](REPOSITORIES.md) — Repository pattern usage
|
||||
- [`TRANSACTIONS.md`](TRANSACTIONS.md) — Transaction pattern usage
|
||||
|
||||
## Navigation
|
||||
|
||||
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. All docs at this level are single-file reference.
|
||||
|
||||
## See also
|
||||
|
||||
- **Repo-level conventions**: `../../docs/CONVENTIONS.md`
|
||||
- **Core DTOs**: `../Mango.Nop.Core/docs/DTOS.md`
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Repository Pattern
|
||||
|
||||
> Part of `Mango.Nop.Data`. See `Mango.Nop.Data/README.md` for project overview.
|
||||
> For transaction patterns see `docs/TRANSACTIONS.md`.
|
||||
|
||||
## MgDbTableBase\<TEntity\>
|
||||
|
||||
Repository base wrapping nopCommerce `EntityRepository<TEntity>`.
|
||||
|
||||
**Constructor:**
|
||||
```csharp
|
||||
MgDbTableBase(IEventPublisher, INopDataProvider, IShortTermCacheManager, IStaticCacheManager, AppSettings)
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
| Feature | Detail |
|
||||
|---|---|
|
||||
| `GetAll()` | Returns `IQueryable<TEntity>` from `Table` property |
|
||||
| Automatic timestamps | `ITimeStampCreated.Created` set on insert; `ITimeStampModified.Modified` set on insert/update |
|
||||
| CRUD hooks | `OnInsert(entity)`, `OnUpdate(entity)`, `OnDelete(entity)` — virtual, overridable |
|
||||
| Cache clear | `OnUpdate` clears `IStaticCacheManager` (currently clears all — TODO noted in code) |
|
||||
| All CRUD overrides | Overrides all sync/async `Insert`, `Update`, `Delete` methods to call hooks before `base.*` |
|
||||
| `DeleteAsync(int entityId)` | Convenience: load by id then delete |
|
||||
| `DeleteAsync(predicate, bool publishEvent)` | When `publishEvent=true`, loads entities first then deletes with events |
|
||||
|
||||
## MgDtoDbTableBase\<TDtoEntity, TMainEntity\>
|
||||
|
||||
DTO-aware repository for when the DTO entity (`TDtoEntity`) maps to a different main nopCommerce entity (`TMainEntity`). This is needed because LinqToDB tables are registered for DTOs, but nopCommerce events must fire on the main entity type.
|
||||
|
||||
### Features
|
||||
|
||||
| Feature | Detail |
|
||||
|---|---|
|
||||
| `GetMainEntityById(id)` | Loads `TMainEntity` from `INopDataProvider.GetTable<TMainEntity>()` |
|
||||
| `DeleteMainEntityById(id)` | Deletes the **main entity** (not the DTO) and publishes `EntityDeletedEvent<TMainEntity>` |
|
||||
| Delete overrides | **All Delete methods throw** — forces callers to use `DeleteMainEntityById()` instead |
|
||||
| Event bridging | `EntityInsertedEvent<TDtoEntity>` -> loads main entity -> publishes `EntityInsertedEvent<TMainEntity>` |
|
||||
| Event bridging | `EntityUpdatedEvent<TDtoEntity>` -> loads main entity -> publishes `EntityUpdatedEvent<TMainEntity>` |
|
||||
| Event bridging | `EntityDeletedEvent<TDtoEntity>` -> **throws** (must use `DeleteMainEntityById`) |
|
||||
|
||||
**Critical rule:** Never call `Delete` on a `MgDtoDbTableBase` repository directly. Always use `DeleteMainEntityById(int id)`.
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# Transaction Pattern
|
||||
|
||||
> Part of `Mango.Nop.Data`. See `Mango.Nop.Data/README.md` for project overview.
|
||||
> For repository base classes see `docs/REPOSITORIES.md`.
|
||||
|
||||
## MgDbContextBase
|
||||
|
||||
Abstract database context base. NOT an EF Core DbContext — wraps `INopDataProvider` and `IRepository<T>` nopCommerce repos.
|
||||
|
||||
**Constructor:**
|
||||
```csharp
|
||||
MgDbContextBase(IRepository<Product>, IRepository<Order>, IRepository<OrderItem>, INopDataProvider, IMgLockService, ILogger)
|
||||
```
|
||||
|
||||
### Standard Repositories
|
||||
|
||||
| Property | Type |
|
||||
|---|---|
|
||||
| `Orders` | `IRepository<Order>` |
|
||||
| `OrderItems` | `IRepository<OrderItem>` |
|
||||
| `Products` | `IRepository<Product>` |
|
||||
| `DataProvider` | `INopDataProvider` (LinqToDB raw queries) |
|
||||
| `Logger` | Mango `ILogger` |
|
||||
| `LockService` | `IMgLockService` (global `SemaphoreSlim`) |
|
||||
|
||||
### 4 Transaction Methods
|
||||
|
||||
| Method | Lock | Async |
|
||||
|---|---|---|
|
||||
| `Transaction(callback)` | No | No |
|
||||
| `TransactionSafe(callback)` | `SemaphoreSlim` | No |
|
||||
| `TransactionAsync(callback)` | No | Yes (thread pool) |
|
||||
| `TransactionSafeAsync(callback)` | `SemaphoreSlim` | Yes (thread pool) |
|
||||
|
||||
**Callback contract:** `Func<TransactionScope, (Task<)bool(>)>` — return `true` to commit (`Complete()`), `false` to rollback.
|
||||
|
||||
**Isolation level:** `ReadCommitted`
|
||||
|
||||
**Error handling:** Catches exceptions, logs, returns `false` (unless `throwException = true`).
|
||||
|
||||
**TransactionSafe variants:** Use `LockService.SemaphoreSlim` for global serialization. Use for order creation, stock adjustment — any operation where concurrent modifications would corrupt data.
|
||||
|
||||
**Async variants:** Run on thread pool via `TaskHelper.ToThreadPoolTask()`.
|
||||
|
||||
## MgDalBase\<TDbContext\>
|
||||
|
||||
Data Access Layer orchestrator. Thin wrapper exposing:
|
||||
|
||||
| Property | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `Name` | `string` | DAL instance name |
|
||||
| `Context` | `TDbContext` | The DB context (typed) |
|
||||
| `MutextLock` | `Mutex` | Cross-process locking |
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Mango.Nop.Services
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
using AyCode.Core.Helpers;
|
||||
using AyCode.Entities;
|
||||
using AyCode.Entities.Server.LogItems;
|
||||
using AyCode.Utils.Extensions;
|
||||
using LinqToDB;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.DataProvider;
|
||||
using LinqToDB.DataProvider.SqlServer;
|
||||
using Mango.Nop.Data.Repositories;
|
||||
using Nop.Core;
|
||||
using Nop.Core.Domain.Common;
|
||||
using Nop.Core.Domain.Customers;
|
||||
using Nop.Core.Domain.Logging;
|
||||
using Nop.Data;
|
||||
using Nop.Data.DataProviders;
|
||||
using System.Data.Common;
|
||||
using System.Transactions;
|
||||
using LogLevel = AyCode.Core.Loggers.LogLevel;
|
||||
using LogLevelNop = Nop.Core.Domain.Logging.LogLevel;
|
||||
|
||||
namespace Mango.Nop.Services.Loggers
|
||||
{
|
||||
public interface INopLoggerMsSqlNopDataProvider
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class NopLoggerMsSqlNopDataProvider : MsSqlNopDataProvider, INopLoggerMsSqlNopDataProvider
|
||||
{
|
||||
//protected override IDataProvider LinqToDbDataProvider => SqlServerTools.GetDataProvider(SqlServerVersion.v2012, SqlServerProvider.MicrosoftDataSqlClient);
|
||||
|
||||
//protected override DbConnection CreateDbConnection(string connectionString = null)
|
||||
//{
|
||||
// connectionString = "Data Source=195.26.231.218;Initial Catalog=MangoManagement;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True";
|
||||
// return base.GetInternalDbConnection(connectionString);
|
||||
//}
|
||||
|
||||
public void InsertLogItem<TEntity>(TEntity entity) where TEntity : notnull
|
||||
{
|
||||
using var transaction = CreateTransactionScope(TransactionScopeAsyncFlowOption.Suppress);
|
||||
using var dataContext = CreateDataConnection();
|
||||
|
||||
dataContext.Insert(entity);
|
||||
transaction.Complete();
|
||||
}
|
||||
|
||||
public async Task InsertLogItemAsync<TEntity>(TEntity entity) where TEntity : notnull
|
||||
{
|
||||
using var transaction = CreateTransactionScope(TransactionScopeAsyncFlowOption.Enabled);
|
||||
await using var dataContext = CreateDataConnection();
|
||||
|
||||
await dataContext.InsertAsync(entity);
|
||||
transaction.Complete();
|
||||
}
|
||||
|
||||
private static TransactionScope CreateTransactionScope(TransactionScopeAsyncFlowOption transactionScopeAsyncFlowOption)
|
||||
{
|
||||
var transactionOptions = new TransactionOptions
|
||||
{
|
||||
IsolationLevel = IsolationLevel.ReadUncommitted,
|
||||
Timeout = TransactionManager.DefaultTimeout
|
||||
};
|
||||
|
||||
return new TransactionScope(TransactionScopeOption.Suppress, transactionOptions, transactionScopeAsyncFlowOption);
|
||||
}
|
||||
}
|
||||
|
||||
public class NopLogWriter : AcLogItemWriterBase<AcLogItem>//AcTextLogWriterBase
|
||||
{
|
||||
private readonly SemaphoreSlim _lockSlim = new(0);
|
||||
|
||||
private NopLoggerMsSqlNopDataProvider _dataProvider;
|
||||
private readonly global::Nop.Services.Logging.ILogger _nopLogger;
|
||||
|
||||
protected readonly CommonSettings _commonSettings;
|
||||
protected readonly CustomerSettings _customerSettings;
|
||||
|
||||
protected readonly IWebHelper _webHelper;
|
||||
|
||||
public NopLogWriter(INopLoggerMsSqlNopDataProvider nopLoggerMsSqlNopDataProvider, CommonSettings commonSettings, CustomerSettings customerSettings, IWebHelper webHelper,
|
||||
global::Nop.Services.Logging.ILogger nopLogger) : this(nopLoggerMsSqlNopDataProvider, commonSettings, customerSettings, webHelper,nopLogger, null)
|
||||
{ }
|
||||
|
||||
public NopLogWriter(INopLoggerMsSqlNopDataProvider nopLoggerMsSqlNopDataProvider,
|
||||
CommonSettings commonSettings, CustomerSettings customerSettings, IWebHelper webHelper, global::Nop.Services.Logging.ILogger nopLogger, string? categoryName = null) : base(categoryName)
|
||||
{
|
||||
_nopLogger = nopLogger;
|
||||
_dataProvider = (NopLoggerMsSqlNopDataProvider)nopLoggerMsSqlNopDataProvider;
|
||||
|
||||
_commonSettings = commonSettings;
|
||||
_customerSettings = customerSettings;
|
||||
_webHelper = webHelper;
|
||||
}
|
||||
|
||||
//public NopLogWriter(ILogger nopLogger, AppType appType, LogLevel logLevel, string? categoryName = null) : base(appType, logLevel, categoryName)
|
||||
//{
|
||||
// _nopLogger=nopLogger;
|
||||
//}
|
||||
|
||||
protected override void WriteLogItemCallback(AcLogItem logItem)
|
||||
{
|
||||
//using (_lockSlim.UseWait())
|
||||
{
|
||||
switch (logItem.LogLevel)
|
||||
{
|
||||
case LogLevel.Detail:
|
||||
case LogLevel.Trace:
|
||||
case LogLevel.Debug:
|
||||
//if (_nopLogger.IsEnabled(LogLevelNop.Debug)) InsertLog(LogLevelNop.Debug, logItem.Text, logItem.Exception, null);
|
||||
//break;
|
||||
case LogLevel.Info:
|
||||
if (_nopLogger.IsEnabled(LogLevelNop.Information)) InsertLog(LogLevelNop.Information, logItem.Text, logItem.Exception, null);
|
||||
//_nopLogger.Information(logItem.Text); //.Forget();
|
||||
break;
|
||||
case LogLevel.Suggest:
|
||||
case LogLevel.Warning:
|
||||
if (_nopLogger.IsEnabled(LogLevelNop.Warning)) InsertLog(LogLevelNop.Warning, logItem.Text, logItem.Exception, null);
|
||||
//_nopLogger.Warning(logItem.Text); //.Forget();
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
if (_nopLogger.IsEnabled(LogLevelNop.Error)) InsertLog(LogLevelNop.Error, logItem.Text, logItem.Exception, null);
|
||||
//_nopLogger.Error(logItem.Text); //.Forget();//, logItem.Exception);
|
||||
break;
|
||||
case LogLevel.Disabled:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertLog(LogLevelNop logLevel, string shortMessage, string fullMessage = "", Customer customer = null)
|
||||
{
|
||||
//check ignore word/phrase list?
|
||||
//if (IgnoreLog(shortMessage) || IgnoreLog(fullMessage))
|
||||
// return;
|
||||
|
||||
_dataProvider.InsertLogItem(PrepareLog(logLevel, shortMessage, fullMessage, customer));
|
||||
//_dataProvider.InsertLogItemAsync(PrepareLog(logLevel, shortMessage, fullMessage, customer)).Forget();
|
||||
}
|
||||
|
||||
private Log PrepareLog(LogLevelNop logLevel, string shortMessage, string fullMessage = "", Customer customer = null)
|
||||
{
|
||||
return new Log
|
||||
{
|
||||
LogLevel = logLevel,
|
||||
ShortMessage = shortMessage,
|
||||
FullMessage = fullMessage,
|
||||
IpAddress = _customerSettings.StoreIpAddresses ? _webHelper.GetCurrentIpAddress() : string.Empty,
|
||||
CustomerId = customer?.Id,
|
||||
PageUrl = _webHelper.GetThisPageUrl(true),
|
||||
ReferrerUrl = _webHelper.GetUrlReferrer(),
|
||||
CreatedOnUtc = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +1,56 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>bin\FruitBank</BaseOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MessagePack.Annotations" Version="2.5.192" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Core\Nop.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Data\Nop.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Services\Nop.Services.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\NopCommerce\Presentation\Nop.Web.Framework\Nop.Web.Framework.csproj" />
|
||||
<ProjectReference Include="..\Mango.Nop.Core\Mango.Nop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Core\Nop.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Data\Nop.Data.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Services\Nop.Services.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\FruitBank\Presentation\Nop.Web.Framework\Nop.Web.Framework.csproj" />
|
||||
<ProjectReference Include="..\Mango.Nop.Core\Mango.Nop.Core.csproj" />
|
||||
<ProjectReference Include="..\Mango.Nop.Data\Mango.Nop.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Core.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Utils">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="AyCode.Core">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Core.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Entities.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Interfaces.Server">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AyCode.Utils">
|
||||
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="docs\**\*.md" />
|
||||
<None Include="**\README.md" Exclude="$(DefaultItemExcludes);docs\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using AyCode.Core.Loggers;
|
||||
using Mango.Nop.Core.Interfaces;
|
||||
using Mango.Nop.Core.Loggers;
|
||||
using Mango.Nop.Data.Interfaces;
|
||||
using Mango.Nop.Data.Repositories;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Nop.Core.Domain.Catalog;
|
||||
using Nop.Core.Domain.Customers;
|
||||
using Nop.Core.Domain.Orders;
|
||||
using Nop.Core.Events;
|
||||
using Nop.Services.Events;
|
||||
using Nop.Services.Logging;
|
||||
using Nop.Web.Framework.Events;
|
||||
|
||||
namespace Mango.Nop.Services;
|
||||
|
|
@ -12,8 +16,9 @@ namespace Mango.Nop.Services;
|
|||
/// <summary>
|
||||
/// Represents plugin event consumer
|
||||
/// </summary>
|
||||
public class MgEventConsumer(IHttpContextAccessor httpContextAccessor, ILogger logger) :
|
||||
public abstract class MgEventConsumerBase(IMgDbContextBase ctx, IHttpContextAccessor httpContextAccessor, IEnumerable<IAcLogWriterBase> logWriters) :
|
||||
IConsumer<EntityUpdatedEvent<Product>>,
|
||||
IConsumer<EntityInsertedEvent<Product>>,
|
||||
IConsumer<CustomerRegisteredEvent>,
|
||||
IConsumer<OrderPlacedEvent>,
|
||||
IConsumer<PageRenderingEvent>,
|
||||
|
|
@ -24,28 +29,36 @@ public class MgEventConsumer(IHttpContextAccessor httpContextAccessor, ILogger l
|
|||
{
|
||||
#region Fields
|
||||
|
||||
protected ILogger Logger { get; } = logger;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
|
||||
protected ILogger Logger { get; } = new Logger<MgEventConsumerBase>(logWriters.ToArray());
|
||||
protected readonly IHttpContextAccessor HttpContextAccessor = httpContextAccessor;
|
||||
#endregion
|
||||
|
||||
protected virtual async Task<Product> CheckAndUpdateProductManageInventoryMethodToManageStock(Product product)
|
||||
{
|
||||
if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock) return product;
|
||||
|
||||
product.ManageInventoryMethod = ManageInventoryMethod.ManageStock;
|
||||
await ctx.Products.UpdateAsync(product, false);
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public virtual async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage)
|
||||
{ }
|
||||
|
||||
public virtual async Task HandleEventAsync(CustomerRegisteredEvent eventMessage)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public virtual async Task HandleEventAsync(OrderPlacedEvent eventMessage)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public virtual async Task HandleEventAsync(PageRenderingEvent eventMessage)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public virtual async Task HandleEventAsync(ProductSearchEvent eventMessage)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
namespace Mango.Nop.Services;
|
||||
|
||||
public abstract class MgLockService : IMgLockService
|
||||
{
|
||||
public SemaphoreSlim SemaphoreSlim { get; protected init; }
|
||||
|
||||
protected MgLockService() : this(new SemaphoreSlim(1))
|
||||
{
|
||||
}
|
||||
|
||||
protected MgLockService(SemaphoreSlim semaphoreSlim)
|
||||
{
|
||||
SemaphoreSlim = semaphoreSlim;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using Mango.Nop.Core.Services;
|
||||
|
||||
namespace Mango.Nop.Services;
|
||||
|
||||
public abstract class MgLockServiceBase : IMgLockService
|
||||
{
|
||||
public SemaphoreSlim SemaphoreSlim { get; protected init; }
|
||||
|
||||
protected MgLockServiceBase() : this(new SemaphoreSlim(1))
|
||||
{
|
||||
}
|
||||
|
||||
protected MgLockServiceBase(SemaphoreSlim semaphoreSlim)
|
||||
{
|
||||
SemaphoreSlim = semaphoreSlim;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
namespace Mango.Nop.Services;
|
||||
|
||||
public abstract class MgSessionItem(string sessionKey) : IMgSessionItem
|
||||
public abstract class MgSessionItemBase(string sessionKey) : IMgSessionItem
|
||||
{
|
||||
public string SessionId { get; protected set; } = sessionKey;
|
||||
public string? SignaRConnectionId { get; set; }
|
||||
|
|
@ -3,7 +3,7 @@ using AyCode.Utils.Extensions;
|
|||
|
||||
namespace Mango.Nop.Services;
|
||||
|
||||
public abstract class MgSessionService<TSessionItem> : IMgSessionService<TSessionItem> where TSessionItem : class, IMgSessionItem
|
||||
public abstract class MgSessionServiceBase<TSessionItem> : IMgSessionService<TSessionItem> where TSessionItem : class, IMgSessionItem
|
||||
{
|
||||
protected ConcurrentDictionary<string, TSessionItem> Sessions { get; } = new();
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Mango.Nop.Services
|
||||
|
||||
@project {
|
||||
type = "framework"
|
||||
own-dep-projects = [
|
||||
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Utils (in AyCode.Core repo)"
|
||||
]
|
||||
}
|
||||
|
||||
Service base classes for nopCommerce plugin development — background tasks, session management, events, locking, logging. **net9.0**.
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Topic |
|
||||
|---|---|
|
||||
| `SERVICES.md` | MgBackgroundServiceBase, MgSessionServiceBase, MgEventConsumerBase, MgLockServiceBase |
|
||||
| `LOGGING/README.md` | NopLogWriter — AyCode-to-nopCommerce log bridge, TransactionScope(Suppress) |
|
||||
|
||||
## Folder Structure
|
||||
|
||||
| Folder | Purpose |
|
||||
|---|---|
|
||||
| `Loggers/` | `NopLogWriter`, `NopLoggerMsSqlNopDataProvider` — AyCode -> nopCommerce log bridge |
|
||||
| *(root)* | `MgBackgroundServiceBase`, `MgSessionServiceBase`, `MgEventConsumerBase`, `MgLockServiceBase`, interfaces |
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `Mango.Nop.Core`, `Mango.Nop.Data` (ProjectReferences)
|
||||
- `Nop.Core`, `Nop.Data`, `Nop.Services`, `Nop.Web.Framework` (nopCommerce ProjectReferences)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# NopLogWriter — Logging Bridge
|
||||
|
||||
> Part of `Mango.Nop.Services`. See `Mango.Nop.Services/README.md` for project overview.
|
||||
> For AyCode base logging types (`AcLogItemWriterBase`, `AcLogItem`, log levels) see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`.
|
||||
|
||||
## Overview
|
||||
|
||||
Bridges AyCode logging to nopCommerce's `Nop.Core.Domain.Logging.Log` table via direct DB insert.
|
||||
|
||||
## NopLogWriter
|
||||
|
||||
| Feature | Detail |
|
||||
|---|---|
|
||||
| Inherits | `AcLogItemWriterBase<AcLogItem>` (AyCode.Core) |
|
||||
| Log level mapping | AyCode `Detail/Trace/Debug/Info` -> nopCommerce `Information`; `Suggest/Warning` -> `Warning`; `Error` -> `Error` |
|
||||
| Direct DB insert | Uses `NopLoggerMsSqlNopDataProvider` with `TransactionScope(Suppress)` to avoid transaction conflicts |
|
||||
|
||||
**Constructor:** `(INopLoggerMsSqlNopDataProvider, CommonSettings, CustomerSettings, IWebHelper, Nop.Services.Logging.ILogger, string? categoryName)`
|
||||
|
||||
## NopLoggerMsSqlNopDataProvider
|
||||
|
||||
Extends `MsSqlNopDataProvider`. Provides isolated DB access for log writes.
|
||||
|
||||
| Method | Purpose |
|
||||
|---|---|
|
||||
| `InsertLogItem<T>()` | Sync insert with own `TransactionScope(Suppress, ReadUncommitted)` |
|
||||
| `InsertLogItemAsync<T>()` | Async insert with own `TransactionScope(Suppress, ReadUncommitted)` |
|
||||
|
||||
The `TransactionScope(Suppress)` is critical — without it, log writes would participate in the caller's transaction, causing nesting conflicts and potential deadlocks.
|
||||
|
||||
## Integration Point
|
||||
|
||||
```
|
||||
Application code -> Mango.Nop.Core.Loggers.ILogger (extends IAcLoggerBase)
|
||||
-> Logger<TCategory> (extends AcLoggerBase, delegates to IAcLogWriterBase[])
|
||||
-> NopLogWriter -> Nop Log table (direct SQL insert)
|
||||
-> [Other AyCode log writers: console, file, SignalR, etc.]
|
||||
```
|
||||
|
||||
**Exception:** `MgBackgroundServiceBase` uses `Nop.Services.Logging.ILogger` directly (nopCommerce logger), not the Mango wrapper.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Mango.Nop.Services documentation
|
||||
|
||||
Topic documentation for the `Mango.Nop.Services` project (Layer 2, service patterns).
|
||||
|
||||
## Reference docs (flat)
|
||||
|
||||
- [`SERVICES.md`](SERVICES.md) — Service pattern usage
|
||||
|
||||
## Topic folders
|
||||
|
||||
- [`LOGGING/`](LOGGING/README.md) — Logger bridge between NopCommerce's logger and AyCode.Core's logger (project-specific variant)
|
||||
|
||||
## Navigation
|
||||
|
||||
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. Single-file reference docs remain flat; project-specific variants of framework topics (like LOGGING) live in named subfolders.
|
||||
|
||||
## See also
|
||||
|
||||
- **Base logger** (framework): `../../../../../Aycode/Source/AyCode.Core/AyCode.Core/docs/LOGGING/README.md`
|
||||
- **Remote logger variant**: `../../../../../Aycode/Source/AyCode.Core/AyCode.Services/docs/LOGGING/README.md`
|
||||
- **Server-side logger variant**: `../../../../../Aycode/Source/AyCode.Core/AyCode.Core.Server/docs/LOGGING/README.md`
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# Service Base Classes
|
||||
|
||||
> Part of `Mango.Nop.Services`. See `Mango.Nop.Services/README.md` for project overview.
|
||||
> For logging bridge see `docs/LOGGING/README.md`.
|
||||
|
||||
## MgBackgroundServiceBase
|
||||
|
||||
Abstract hosted background service. Inherits `Microsoft.Extensions.Hosting.BackgroundService`.
|
||||
|
||||
**Constructor:** `(ILogger, IServiceProvider, int executeIntervalMs)`
|
||||
|
||||
| Feature | Detail |
|
||||
|---|---|
|
||||
| Loop pattern | `ExecuteAsync` loops: `Task.Delay(ExecuteIntervalMs)` -> `OnExecuteAsync()`, with pause support |
|
||||
| `OnExecuteAsync(CancellationToken)` | Abstract — subclass implements the actual work |
|
||||
| `Pause(bool)` | Pauses/resumes the loop without stopping the service |
|
||||
| `ExecuteIntervalMs` | Configurable interval between iterations |
|
||||
| Exception handling | Catches and logs errors per iteration, never crashes the loop |
|
||||
| Logging | Uses nopCommerce `Nop.Services.Logging.ILogger` (**not** `Mango.Nop.Core.Loggers.ILogger`) |
|
||||
|
||||
**Interface:** `IMgBackgroundService : IHostedService, IDisposable`
|
||||
|
||||
## MgSessionServiceBase\<TSessionItem\>
|
||||
|
||||
In-memory session management using `ConcurrentDictionary<string, TSessionItem>`.
|
||||
|
||||
| Method | Signature | Purpose |
|
||||
|---|---|---|
|
||||
| `GetOrCreateSessionItem` | `(string sessionId) -> TSessionItem?` | Get existing or create new via `Activator.CreateInstance` |
|
||||
| `TryAddSessionItem` | `(TSessionItem) -> bool` | Add if not exists, throws if duplicate |
|
||||
| `TryGetSessionItem` | `(string sessionId, out TSessionItem) -> bool` | Try-pattern lookup |
|
||||
| `TryRemoveSessionItem` | `(string sessionId, out TSessionItem) -> bool` | Remove and return |
|
||||
| `TryGetSessionItemBySignlaRConnectionId` | `(string connectionId, out TSessionItem?) -> bool` | Find session by SignalR connection |
|
||||
| `Count` | `() -> int` | Active session count |
|
||||
|
||||
**Interface:** `IMgSessionService<TSessionItem> where TSessionItem : IMgSessionItem`
|
||||
|
||||
## MgSessionItemBase
|
||||
|
||||
Base session item. Primary constructor: `(string sessionKey)`.
|
||||
|
||||
| Property | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `SessionId` | `string` | Unique session identifier |
|
||||
| `SignaRConnectionId` | `string?` | Associated SignalR connection ID |
|
||||
| `RequestCount` | `int` | Request counter |
|
||||
|
||||
**Interface:** `IMgSessionItem` — `SessionId`, `SignaRConnectionId`, `RequestCount`
|
||||
|
||||
## MgEventConsumerBase
|
||||
|
||||
Abstract nopCommerce event consumer. Subscribes to:
|
||||
|
||||
| Event | Handler |
|
||||
|---|---|
|
||||
| `EntityUpdatedEvent<Product>` | `HandleEventAsync(...)` — virtual, empty default |
|
||||
| `EntityInsertedEvent<Product>` | `HandleEventAsync(...)` — virtual, empty default |
|
||||
| `CustomerRegisteredEvent` | `HandleEventAsync(...)` — virtual, empty default |
|
||||
| `OrderPlacedEvent` | `HandleEventAsync(...)` — virtual, empty default |
|
||||
| `PageRenderingEvent` | `HandleEventAsync(...)` — virtual, empty default |
|
||||
| `ProductSearchEvent` | `HandleEventAsync(...)` — virtual, empty default |
|
||||
|
||||
Built-in helper: `CheckAndUpdateProductManageInventoryMethodToManageStock(Product)` — ensures product has `ManageStock` inventory method.
|
||||
|
||||
**Constructor:** `(IMgDbContextBase ctx, IHttpContextAccessor, IEnumerable<IAcLogWriterBase> logWriters)`
|
||||
|
||||
## MgLockServiceBase
|
||||
|
||||
Simple in-process lock using `SemaphoreSlim(1)`. Implements `IMgLockService` (defined in `Mango.Nop.Core.Services`).
|
||||
|
||||
Used by `MgDbContextBase.TransactionSafe*` variants for global serialization.
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
namespace Nop.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base class for entities
|
||||
/// </summary>
|
||||
public abstract partial class BaseEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the entity identifier
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
using Nop.Core.Configuration;
|
||||
using Nop.Core.Infrastructure;
|
||||
|
||||
namespace Nop.Core.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// Represents key for caching objects
|
||||
/// </summary>
|
||||
public partial class CacheKey
|
||||
{
|
||||
#region Ctor
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance with key and prefixes
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
/// <param name="prefixes">Prefixes for remove by prefix functionality</param>
|
||||
public CacheKey(string key, params string[] prefixes)
|
||||
{
|
||||
Key = key;
|
||||
Prefixes.AddRange(prefixes.Where(prefix => !string.IsNullOrEmpty(prefix)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance from the current one and fill it with passed parameters
|
||||
/// </summary>
|
||||
/// <param name="createCacheKeyParameters">Function to create parameters</param>
|
||||
/// <param name="keyObjects">Objects to create parameters</param>
|
||||
/// <returns>Cache key</returns>
|
||||
public virtual CacheKey Create(Func<object, object> createCacheKeyParameters, params object[] keyObjects)
|
||||
{
|
||||
var cacheKey = new CacheKey(Key, Prefixes.ToArray());
|
||||
|
||||
if (!keyObjects.Any())
|
||||
return cacheKey;
|
||||
|
||||
cacheKey.Key = string.Format(cacheKey.Key, keyObjects.Select(createCacheKeyParameters).ToArray());
|
||||
|
||||
for (var i = 0; i < cacheKey.Prefixes.Count; i++)
|
||||
cacheKey.Prefixes[i] = string.Format(cacheKey.Prefixes[i], keyObjects.Select(createCacheKeyParameters).ToArray());
|
||||
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a cache key
|
||||
/// </summary>
|
||||
public string Key { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets prefixes for remove by prefix functionality
|
||||
/// </summary>
|
||||
public List<string> Prefixes { get; protected set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a cache time in minutes
|
||||
/// </summary>
|
||||
public int CacheTime { get; set; } = Singleton<AppSettings>.Instance.Get<CacheConfig>().DefaultCacheTime;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
using Nop.Core.Infrastructure;
|
||||
|
||||
namespace Nop.Core.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// Cache key manager
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class should be registered on IoC as singleton instance
|
||||
/// </remarks>
|
||||
public partial class CacheKeyManager : ICacheKeyManager
|
||||
{
|
||||
protected readonly IConcurrentCollection<byte> _keys;
|
||||
|
||||
public CacheKeyManager(IConcurrentCollection<byte> keys)
|
||||
{
|
||||
_keys = keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the key
|
||||
/// </summary>
|
||||
/// <param name="key">The key to add</param>
|
||||
public void AddKey(string key)
|
||||
{
|
||||
_keys.Add(key, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the key
|
||||
/// </summary>
|
||||
/// <param name="key">The key to remove</param>
|
||||
public void RemoveKey(string key)
|
||||
{
|
||||
_keys.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all keys
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_keys.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove keys by prefix
|
||||
/// </summary>
|
||||
/// <param name="prefix">Prefix to delete keys</param>
|
||||
/// <returns>The list of removed keys</returns>
|
||||
public IEnumerable<string> RemoveByPrefix(string prefix)
|
||||
{
|
||||
if (!_keys.Prune(prefix, out var subtree) || subtree?.Keys == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return subtree.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of keys
|
||||
/// </summary>
|
||||
public IEnumerable<string> Keys => _keys.Keys;
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Nop.Core.Configuration;
|
||||
|
||||
namespace Nop.Core.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default cache key service implementation
|
||||
/// </summary>
|
||||
public abstract partial class CacheKeyService : ICacheKeyService
|
||||
{
|
||||
#region Fields
|
||||
|
||||
protected readonly AppSettings _appSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ctor
|
||||
|
||||
protected CacheKeyService(AppSettings appSettings)
|
||||
{
|
||||
_appSettings = appSettings;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the cache key prefix
|
||||
/// </summary>
|
||||
/// <param name="prefix">Cache key prefix</param>
|
||||
/// <param name="prefixParameters">Parameters to create cache key prefix</param>
|
||||
protected virtual string PrepareKeyPrefix(string prefix, params object[] prefixParameters)
|
||||
{
|
||||
return prefixParameters?.Any() ?? false
|
||||
? string.Format(prefix, prefixParameters.Select(CreateCacheKeyParameters).ToArray())
|
||||
: prefix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the hash value of the passed identifiers
|
||||
/// </summary>
|
||||
/// <param name="ids">Collection of identifiers</param>
|
||||
/// <returns>String hash value</returns>
|
||||
protected virtual string CreateIdsHash(IEnumerable<int> ids)
|
||||
{
|
||||
var identifiers = ids.ToList();
|
||||
|
||||
if (!identifiers.Any())
|
||||
return string.Empty;
|
||||
|
||||
var identifiersString = string.Join(", ", identifiers.OrderBy(id => id));
|
||||
return HashHelper.CreateHash(Encoding.UTF8.GetBytes(identifiersString), HashAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an object to cache parameter
|
||||
/// </summary>
|
||||
/// <param name="parameter">Object to convert</param>
|
||||
/// <returns>Cache parameter</returns>
|
||||
protected virtual object CreateCacheKeyParameters(object parameter)
|
||||
{
|
||||
return parameter switch
|
||||
{
|
||||
null => "null",
|
||||
IEnumerable<int> ids => CreateIdsHash(ids),
|
||||
IEnumerable<BaseEntity> entities => CreateIdsHash(entities.Select(entity => entity.Id)),
|
||||
BaseEntity entity => entity.Id,
|
||||
decimal param => param.ToString(CultureInfo.InvariantCulture),
|
||||
_ => parameter
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of cache key and fills it by passed parameters
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">Initial cache key</param>
|
||||
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
|
||||
/// <returns>Cache key</returns>
|
||||
public virtual CacheKey PrepareKey(CacheKey cacheKey, params object[] cacheKeyParameters)
|
||||
{
|
||||
return cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of cache key using the default cache time and fills it by passed parameters
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">Initial cache key</param>
|
||||
/// <param name="cacheKeyParameters">Parameters to create cache key</param>
|
||||
/// <returns>Cache key</returns>
|
||||
public virtual CacheKey PrepareKeyForDefaultCache(CacheKey cacheKey, params object[] cacheKeyParameters)
|
||||
{
|
||||
var key = cacheKey.Create(CreateCacheKeyParameters, cacheKeyParameters);
|
||||
|
||||
key.CacheTime = _appSettings.Get<CacheConfig>().DefaultCacheTime;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets an algorithm used to create the hash value of identifiers need to cache
|
||||
/// </summary>
|
||||
protected string HashAlgorithm => "SHA1";
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
namespace Nop.Core.Caching;
|
||||
|
||||
public static class CachingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a cached item. If it's not in the cache yet, then load and cache it.
|
||||
/// NOTE: this method is only kept for backwards compatibility: the async overload is preferred!
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of cached item</typeparam>
|
||||
/// <param name="cacheManager">Cache manager</param>
|
||||
/// <param name="key">Cache key</param>
|
||||
/// <param name="acquire">Function to load item if it's not in the cache yet</param>
|
||||
/// <returns>The cached value associated with the specified key</returns>
|
||||
public static T Get<T>(this IStaticCacheManager cacheManager, CacheKey key, Func<T> acquire)
|
||||
{
|
||||
return cacheManager.GetAsync(key, acquire).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove items by cache key prefix
|
||||
/// </summary>
|
||||
/// <param name="cacheManager">Cache manager</param>
|
||||
/// <param name="prefix">Cache key prefix</param>
|
||||
/// <param name="prefixParameters">Parameters to create cache key prefix</param>
|
||||
public static void RemoveByPrefix(this IStaticCacheManager cacheManager, string prefix, params object[] prefixParameters)
|
||||
{
|
||||
cacheManager.RemoveByPrefixAsync(prefix, prefixParameters).Wait();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue