Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions FastCache.MultiSource/Attributes/MultiSourceEvictableAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AspectCore.DynamicProxy;
using FastCache.Core.Driver;
Expand All @@ -12,12 +14,14 @@ namespace FastCache.MultiSource.Attributes
public class MultiSourceEvictableAttribute : AbstractInterceptorAttribute
{
private readonly string[] _keys;
private readonly string _expression;
private readonly string[] _expression;
private readonly Target _target;

public sealed override int Order { get; set; }

public MultiSourceEvictableAttribute(string[] keys, string expression, Target target)
public override bool AllowMultiple { get; } = true;

public MultiSourceEvictableAttribute(string[] keys, string[] expression, Target target)
{
_keys = keys;
_expression = expression;
Expand All @@ -43,18 +47,30 @@ public override async Task Invoke(AspectContext context, AspectDelegate next)
throw new ArgumentOutOfRangeException();
}

var tasks = new ConcurrentBag<Task>();

var dictionary = new Dictionary<string, object>();
var parameterInfos = context.ImplementationMethod.GetParameters();
for (var i = 0; i < context.Parameters.Length; i++)
{
dictionary.Add(parameterInfos[i].Name, context.Parameters[i]);
}

foreach (var s in _keys)
var keys = _keys.Select(x => x.Trim()).Distinct().ToList();
var expressions = _expression.Select(x => x.Trim()).Distinct().ToList();

foreach (var key in keys)
{
var key = KeyGenerateHelper.GetKey(_expression, dictionary);
foreach (var expression in expressions)
{
var deleteKey = KeyGenerateHelper.GetKey(expression, dictionary);
tasks.Add(cacheClient.Delete(deleteKey, key));
}
}

await cacheClient.Delete(key, s);
if (tasks.Count > 0)
{
await Task.WhenAll(tasks);
}
}
}
Expand Down
49 changes: 47 additions & 2 deletions IntegrationTests/MultiSourceApiRequestCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using FastCache.Core.Driver;
using FastCache.Redis.Driver;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using TestApi.DB;
Expand All @@ -31,7 +33,8 @@ public MultiSourceApiRequestCacheTests(WebApplicationFactory<Program> factory)
new()
{
Id = "1",
Name = "anson1"
Name = "anson1",
Age = 10
},
new()
{
Expand Down Expand Up @@ -148,7 +151,7 @@ public async void CacheAndEvictOther(string baseUrl)
var timeResult = end - start;
Assert.True(timeResult < 500000);
}

[Theory]
[InlineData("/MultiSource")]
public async Task TestUpdated(string baseUrl)
Expand Down Expand Up @@ -186,4 +189,46 @@ public async void CacheResultTaskNull(string baseUrl)
var result1 = await resp1.Content.ReadAsStringAsync();
Assert.Equal("", result1);
}

[Theory]
[InlineData("/MultiSource")]
public async void TestMultiSourceEvictableAllowsEvictionByMultipleRules(string baseUrl)
{
var responseMessage = await _httpClient.GetAsync($"{baseUrl}?id=1");
Assert.Equal(responseMessage.StatusCode, HttpStatusCode.OK);

var user = await responseMessage.Content.ReadFromJsonAsync<User>();
Assert.Equal(user.Name, "anson1");
Assert.Equal(user.Age, 10);

var responseMessageBySearchName = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1");
Assert.Equal(responseMessageBySearchName.StatusCode, HttpStatusCode.OK);

var userBySearchName = await responseMessageBySearchName.Content.ReadFromJsonAsync<User>();

Assert.Equal(userBySearchName.Name, "anson1");
Assert.Equal(userBySearchName.Age, 10);

user.Age = 1;

var updateAfter = await _httpClient.PutAsJsonAsync(baseUrl, user);
Assert.Equal(updateAfter.StatusCode, HttpStatusCode.OK);

var responseMessageBySearchNameUpdated = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1");
Assert.Equal(responseMessageBySearchNameUpdated.StatusCode, HttpStatusCode.OK);

var userUpdated = await responseMessageBySearchNameUpdated.Content.ReadFromJsonAsync<User>();
Assert.Equal("anson1", userUpdated.Name);
Assert.Equal(1, userUpdated.Age);

var deleted = await _httpClient.DeleteAsync($"{baseUrl}?id=1");
Assert.True(deleted.StatusCode == HttpStatusCode.OK);

var responseMessageBySearchNameDeleted = await _httpClient.GetAsync($"{baseUrl}/get/name?name=anson1");
Assert.Equal(responseMessageBySearchNameDeleted.StatusCode, HttpStatusCode.OK);

var userDeleted = await responseMessageBySearchNameDeleted.Content.ReadFromJsonAsync<User>();
Assert.Equal("anson1", userDeleted.Name);
Assert.Equal(1, userDeleted.Age);
}
}
10 changes: 8 additions & 2 deletions TestApi/Controllers/MultiSourceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ public User Add(User user)

[HttpPut]
[MultiSourceCacheable("MultiSource-single", "{user:id}", Target.Redis, 5)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{user:id}", Target.Redis)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{user:id}", "{user:name}"], Target.Redis)]
public virtual async Task<User> Update(User user)
{
return await _userService.Update(user);
}

[HttpDelete]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{id}", Target.Redis)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{id}"], Target.Redis)]
public virtual bool Delete(string id)
{
return _userService.Delete(id);
Expand All @@ -57,4 +57,10 @@ public virtual IEnumerable<User> Users(string page)
{
return await _userService.SingleOrDefault(id);
}

[HttpGet("get/name")]
public virtual async Task<User?> SearchName(string name)
{
return await _userService.SingleOrDefaultByName(name);
}
}
4 changes: 2 additions & 2 deletions TestApi/Controllers/MultiSourceInMemoryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ public User Add(User user)

[HttpPut]
[MultiSourceCacheable("MultiSource-single", "{user:id}", Target.InMemory, 5)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{user:id}", Target.InMemory)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{user:id}"], Target.InMemory)]
public virtual async Task<User> Update(User user)
{
return await _userService.Update(user);
}

[HttpDelete]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{id}", Target.InMemory)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{id}"], Target.InMemory)]
public virtual bool Delete(string id)
{
return _userService.Delete(id);
Expand Down
6 changes: 3 additions & 3 deletions TestApi/Controllers/UseLockController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class UseLockController(ILockUserService lockUserService)
[HttpPost("add-with-cache")]
[DistributedLock("user-add")]
[MultiSourceCacheable("MultiSource-single", "{user:id}", Target.Redis, 5)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "{user:id}", Target.Redis)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["{user:id}"], Target.Redis)]
public virtual async Task<User> AddWithCache(User user, int delayMs = 0)
{
return await lockUserService.Add(user, delayMs);
Expand All @@ -32,7 +32,7 @@ public virtual async Task<User> Get(string id)
{
return await lockUserService.Single(id);
}

[HttpGet("users")]
[MultiSourceCacheable("MultiSource-single", "{id}", Target.Redis, 5)]
public virtual IEnumerable<User> Users(string page)
Expand All @@ -41,7 +41,7 @@ public virtual IEnumerable<User> Users(string page)
}

[HttpDelete]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, "*{id}*", Target.Redis)]
[MultiSourceEvictable(new[] { "MultiSource-single", "MultiSources" }, ["*{id}*"], Target.Redis)]
public virtual bool Delete(string id)
{
return lockUserService.Delete(id);
Expand Down
2 changes: 2 additions & 0 deletions TestApi/Entity/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

public record User : IEntity
{
public string Id { get; set; }

Check warning on line 7 in TestApi/Entity/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Id' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public string Name { get; set; }

Check warning on line 8 in TestApi/Entity/User.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

public int Age { get; set; }

[NotMapped]public List<long>? ThirdPartyIds { get; set; }
}
2 changes: 2 additions & 0 deletions TestApi/Service/IMultiSourceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public interface IMultiSourceService

Task<User?> SingleOrDefault(string id);

Task<User?> SingleOrDefaultByName(string name);

Task<User> Update(User user);

bool Delete(string id);
Expand Down
7 changes: 7 additions & 0 deletions TestApi/Service/MultiSourceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public virtual async Task<User> Update(User user)
{
var first = await dbContext.Set<User>().FirstAsync(x => x.Id == user.Id);
first.Name = user.Name;
first.Age = user.Age;
dbContext.Set<User>().Update(first);
await dbContext.SaveChangesAsync();
return first;
Expand All @@ -52,6 +53,12 @@ public virtual IEnumerable<User> List(string page)
return await dbContext.Set<User>().SingleOrDefaultAsync(x => x.Id == id);
}

[MultiSourceCacheable("MultiSource-single", "{name}", Target.Redis, 60)]
public async Task<User?> SingleOrDefaultByName(string name)
{
return await dbContext.Set<User>().SingleOrDefaultAsync(x => x.Name == name);
}

public virtual Task<string?> TestReturnNull()
{
return null;
Expand Down
Loading