Commit d94f3747 by dingsongjie

完成 efcore 与 orleans 结合的全部测试

parent 91952c96
......@@ -19,12 +19,26 @@ namespace Backet.Api.Controllers
this.clusterClient = clusterClient;
}
[HttpPost("api/backet/AddBacket")]
public void AddBacket([FromBody]Backet.Api.Grains.Abstraction.BacketDto backet)
public Task<bool> AddBacket([FromBody]Backet.Api.Grains.Abstraction.BacketDto backet)
{
var newId = "da8a489fa7b4409294ee1b358fbbfba5";
backet.Id = newId;
var grain = clusterClient.GetGrain<IBacketGrain>(newId);
grain.AddBacket(backet);
return grain.AddBacket(backet);
}
[HttpPost("api/backet/UpdateBacket")]
public Task<bool> UpdateBacket()
{
var id = "da8a489fa7b4409294ee1b358fbbfba5";
var grain = clusterClient.GetGrain<IBacketGrain>(id);
return grain.UpdateBacket("88");
}
[HttpPost("api/backet/AddItem")]
public Task<bool> AddItem()
{
var id = "da8a489fa7b4409294ee1b358fbbfba5";
var grain = clusterClient.GetGrain<IBacketGrain>(id);
return grain.AddBacketItem("55","测试3",1000);
}
}
}
\ No newline at end of file
......@@ -20,6 +20,10 @@ namespace Backet.Api.Domain.AggregatesModel.BacketAggregate
BacketItems.Add(backetItem);
SetBacketTotalPrice();
}
public void ModifyItemProductId(string productId)
{
BacketItems.ForEach(m => m.ProductId = productId);
}
private void SetBacketTotalPrice()
{
foreach (var item in BacketItems)
......
......@@ -9,6 +9,8 @@ namespace Backet.Api.Grains.Abstraction
public interface IBacketGrain: IGrainWithStringKey
{
Task<bool> AddBacket(BacketDto backet);
Task<bool> UpdateBacket(string userId);
Task<bool> AddBacketItem(string productId, string productName, long price);
}
public class BacketItemDto
{
......
......@@ -26,5 +26,25 @@ namespace Backet.Api.Grains
await WriteStateAsync();
return true;
}
public async Task<bool> AddBacketItem(string productId,string productName,long price)
{
if (State == null) return false;
State.AddBacketItem(productId, productName, price);
Update(State);
await WriteStateAsync();
return true;
}
public async Task<bool> UpdateBacket(string userId)
{
if (State == null) return false;
State.UserId = userId;
State.ModifyItemProductId(userId);
Update(State);
await WriteStateAsync();
return true;
}
}
}
......@@ -34,8 +34,8 @@ namespace Backet.Api
services.ConfigureGrainStorageOptions<BacketDbContext, BacketGrain, Backet.Api.Domain.AggregatesModel.BacketAggregate.Backet>(
options => options
.UseQuery(context => context.Backets.AsNoTracking()
//.Include(box => box.BacketItems)
.UseQuery(context => context.Backets
.Include(box => box.BacketItems)
));
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Product.Api.Apis
{
public class DefaultController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Pole.Application.EventBus
{
public interface IBus
{
Task Publish<TReliableEvent>(TReliableEvent @event, object callbackParemeter, CancellationToken cancellationToken = default) where TReliableEvent : class;
}
}
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace Orleans.Providers.EntityFramework
{
public delegate void ConfigureEntryStateDelegate<TGrainState>(EntityEntry<TGrainState> entry)
where TGrainState : class;
}
\ No newline at end of file
using System.Threading;
namespace Orleans.Providers.EntityFramework
{
/// <summary>
/// An async local context to apply modifications to current behavior of write and clear operations.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public static class GrainStorageContext<TEntity>
where TEntity : class
{
// ReSharper disable once StaticMemberInGenericType
private static readonly AsyncLocal<bool> IsConfiguredLocal
= new AsyncLocal<bool>();
private static readonly AsyncLocal<ConfigureEntryStateDelegate<TEntity>>
ConfigureStateDelegateLocal
= new AsyncLocal<ConfigureEntryStateDelegate<TEntity>>();
internal static bool IsConfigured => IsConfiguredLocal.Value;
internal static ConfigureEntryStateDelegate<TEntity> ConfigureStateDelegate
=> ConfigureStateDelegateLocal.Value;
/// <summary>
/// Configures the entry state.
/// Use it to modify what gets changed during the write operations.
/// </summary>
/// <param name="configureState">The delegate to be called before saving context's state.</param>
public static void ConfigureEntryState(ConfigureEntryStateDelegate<TEntity> configureState)
{
ConfigureStateDelegateLocal.Value = configureState;
IsConfiguredLocal.Value = true;
}
public static void Clear()
{
ConfigureStateDelegateLocal.Value = null;
IsConfiguredLocal.Value = false;
}
}
}
\ No newline at end of file
using System;
using Orleans.Runtime;
namespace Orleans.Providers.EntityFramework.Conventions
{
public delegate ValueType GetCompoundKeyDelegate(IAddressable addressable, out string keyExt);
}
\ No newline at end of file
......@@ -259,17 +259,9 @@ namespace Pole.Orleans.Provider.EntityframeworkCore.Conventions
throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " +
$"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}");
Func<TContext, string, Task<TEntity>> compiledQuery = null;
if (options.DbSetAccessor != null)
{
var predicate = CreateKeyPredicate<TEntity, string>(options);
compiledQuery = EF.CompileAsyncQuery((TContext context, string grainKey)
=> options.DbSetAccessor(context)
.SingleOrDefault(predicate));
}
else
{
compiledQuery = CreateCompiledQuery<TContext, TGrain, TEntity, string>(options);
}
compiledQuery = CreateCompiledQuery<TContext, TGrain, TEntity, string>(options);
......@@ -415,16 +407,28 @@ namespace Pole.Orleans.Provider.EntityframeworkCore.Conventions
var keyParameter = Expression.Parameter(typeof(TKey), "grainKey");
var predicate = CreateKeyPredicate<TEntity, TKey>(options, keyParameter);
var queryable = Expression.Call(
MethodCallExpression queryable = null;
if (options.DbSetAccessor.Method.IsStatic)
{
queryable = Expression.Call(
options.DbSetAccessor.Method,
Expression.Constant(options.DbSetAccessor),
contextParameter);
}
else
{
queryable = Expression.Call(
Expression.Constant(options.DbSetAccessor.Target),
options.DbSetAccessor.Method,
contextParameter);
}
var compiledLambdaBody = Expression.Call(
typeof(Queryable).GetMethods().Single(mi =>
mi.Name == nameof(Queryable.SingleOrDefault) && mi.GetParameters().Count() == 2)
.MakeGenericMethod(typeof(TEntity)),
queryable,
queryable,
Expression.Quote(predicate));
var lambdaExpression = Expression.Lambda<Func<TContext, TKey, TEntity>>(
......
......@@ -13,11 +13,20 @@ namespace Pole.Orleans.Provider.EntityframeworkCore
{
public void ConfigureSaveEntry(ConfigureSaveEntryContext<TContext, TEntity> context)
{
EntityEntry<TEntity> entry = context.DbContext.Entry(context.Entity);
if (context.IsPersisted)
{
// todo update necessary table
//EntityEntry<TEntity> entry = context.DbContext.Set<TEntity>().Update(context.Entity);
}
else
{
EntityEntry<TEntity> entry = context.DbContext.Set<TEntity>().Add(context.Entity);
}
entry.State = context.IsPersisted
? EntityState.Modified
: EntityState.Added;
//entry.State = context.IsPersisted
// ? EntityState.Modified
// : EntityState.Added;
}
}
}
using System;
namespace Orleans.Providers.EntityFramework.Exceptions
{
// todo: Use for configuration errors
public class GrainStorageConfigurationException : Exception
{
public GrainStorageConfigurationException()
{
}
public GrainStorageConfigurationException(string message) : base(message)
{
}
public GrainStorageConfigurationException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
\ No newline at end of file
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Orleans.Providers.EntityFramework.Conventions;
using Orleans.Runtime;
using Orleans.Storage;
using Pole.Orleans.Provider.EntityframeworkCore;
using Pole.Orleans.Provider.EntityframeworkCore.Conventions;
namespace Orleans.Providers.EntityFramework.Extensions
{
public static class GrainStorageServiceCollectionExtensions
{
public static IServiceCollection ConfigureGrainStorageOptions<TContext, TGrain, TEntity>(
this IServiceCollection services,
Action<GrainStorageOptions<TContext, TGrain, TEntity>> configureOptions = null)
where TContext : DbContext
where TGrain : Grain<TEntity>
where TEntity : class, new()
{
return services
.AddSingleton<IPostConfigureOptions<GrainStorageOptions<TContext, TGrain, TEntity>>,
GrainStoragePostConfigureOptions<TContext, TGrain, TEntity, TEntity>>()
.Configure<GrainStorageOptions<TContext, TGrain, TEntity>>(typeof(TGrain).FullName, options =>
{
configureOptions?.Invoke(options);
});
}
public static IServiceCollection ConfigureGrainStorageOptions<TContext, TGrain, TGrainState, TEntity>(
this IServiceCollection services,
Action<GrainStorageOptions<TContext, TGrain, TEntity>> configureOptions = null)
where TContext : DbContext
where TGrain : Grain<TGrainState>
where TGrainState : new()
where TEntity : class
{
return services
.AddSingleton<IPostConfigureOptions<GrainStorageOptions<TContext, TGrain, TEntity>>,
GrainStoragePostConfigureOptions<TContext, TGrain, TGrainState, TEntity>>()
.Configure<GrainStorageOptions<TContext, TGrain, TEntity>>(typeof(TGrain).FullName, options =>
{
configureOptions?.Invoke(options);
});
}
public static IServiceCollection AddEfGrainStorage<TContext>(
this IServiceCollection services,
string providerName = ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)
where TContext : DbContext
{
services.TryAddSingleton(typeof(IEntityTypeResolver), typeof(EntityTypeResolver));
services.TryAddSingleton(typeof(IGrainStorageConvention), typeof(GrainStorageConvention));
services.TryAddSingleton(typeof(IGrainStateEntryConfigurator<,,>),
typeof(DefaultGrainStateEntryConfigurator<,,>));
services.AddSingleton(typeof(EntityFrameworkGrainStorage<TContext>));
services.TryAddSingleton<IGrainStorage>(sp =>
sp.GetServiceByName<IGrainStorage>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME));
services.AddSingletonNamedService<IGrainStorage>(providerName,
(sp, name) => sp.GetRequiredService<EntityFrameworkGrainStorage<TContext>>());
return services;
}
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Orleans.Hosting;
namespace Orleans.Providers.EntityFramework.Extensions
{
public static class GrainStorageSiloHostBuilderExtensions
{
public static ISiloHostBuilder AddEfGrainStorageAsDefault<TContext>(this ISiloHostBuilder builder)
where TContext : DbContext
{
return builder.AddEfGrainStorage<TContext>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME);
}
public static ISiloHostBuilder AddEfGrainStorage<TContext>(this ISiloHostBuilder builder,
string providerName)
where TContext : DbContext
{
return builder
.ConfigureServices(services =>
{
services.AddEfGrainStorage<TContext>(providerName);
});
}
public static ISiloBuilder AddEfGrainStorageAsDefault<TContext>(this ISiloBuilder builder)
where TContext : DbContext
{
return builder.AddEfGrainStorage<TContext>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME);
}
public static ISiloBuilder AddEfGrainStorage<TContext>(this ISiloBuilder builder,
string providerName)
where TContext : DbContext
{
return builder
.ConfigureServices(services => { services.AddEfGrainStorage<TContext>(providerName); });
}
}
}
\ No newline at end of file
......@@ -7,9 +7,12 @@ using Microsoft.Extensions.Options;
using Orleans;
using Orleans.Runtime;
using Orleans.Storage;
using Pole.Core.Domain;
using Pole.Core.EventBus.Event;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
......@@ -19,7 +22,7 @@ namespace Pole.Orleans.Provider.EntityframeworkCore
where TContext : DbContext
where TGrain : Grain<TGrainState>
where TGrainState : class, new()
where TEntity : class
where TEntity : Entity
{
private readonly GrainStorageOptions<TContext, TGrain, TEntity> _options;
private readonly IServiceScopeFactory _scopeFactory;
......@@ -75,13 +78,18 @@ namespace Pole.Orleans.Provider.EntityframeworkCore
else
{
bool isPersisted = _options.IsPersistedFunc(entity);
if (isPersisted)
{
TEntity entityInDb = await _options.ReadStateAsync(context, grainReference)
.ConfigureAwait(false);
Copy(entity, entityInDb);
}
else
{
context.Set<TEntity>().Add(entity);
}
_entryConfigurator.ConfigureSaveEntry(
new ConfigureSaveEntryContext<TContext, TEntity>(
context, entity)
{
IsPersisted = isPersisted
});
}
try
......@@ -118,7 +126,20 @@ namespace Pole.Orleans.Provider.EntityframeworkCore
}
}
public static void Copy<T>(T from, T to) where T : Entity
{
if (ReferenceEquals(from, null))
throw new ArgumentNullException("from");
if (ReferenceEquals(to, null))
throw new ArgumentNullException("to");
Type type = from.GetType();
PropertyInfo[] Properties = type.GetProperties();
foreach (PropertyInfo p in Properties)
{
if (p.Name == "DomainEvents" || p.Name == "Id") continue;
p.SetValue(to, p.GetValue(from));
}
}
private GrainStorageOptions<TContext, TGrain, TEntity> GetOrCreateDefaultOptions(string grainType)
{
var options
......
......@@ -13,7 +13,7 @@ namespace Pole.Orleans.Provider.EntityframeworkCore
{
public static GrainStorageOptions<TContext, TGrain, TGrainState> UseQuery<TContext, TGrain, TGrainState>(
this GrainStorageOptions<TContext, TGrain, TGrainState> options,
Func<TContext, IQueryable<TGrainState>> queryFunc)
Func<TContext, IQueryable<TGrainState>>queryFunc)
where TContext : DbContext
where TGrainState : class
{
......
using System.Threading.Tasks;
using Orleans.Runtime;
namespace Orleans.Providers.EntityFramework
{
internal delegate Task ReadWriteStateAsyncDelegate(string grainType, GrainReference grainReference,
IGrainState grainState, object storageOptions);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment