How to configure AbpAutoMapperOptions to expression mapping? - expression

I want to configure an abp.io application project to successfully map Expression<Func<TDto, bool>> to Expression<Func<TEntity, bool>>
I don't know is that can be done by Volo.Abp.ObjectMapping
I succussed to get mapping work like that
var mapper = new Mapper(new MapperConfiguration(cfg =>
{
cfg.AddExpressionMapping();
cfg.CreateMap<Category, CategoryDto>();
})
);
var predicateConverted =mapper.MapExpression <Expression<Func<Category, bool>>>(predicate);
but I think I should but that configuration with
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<TesttApplicationModule>();
});
}

You need to use the AutoMapper.Extensions.ExpressionMapping package for expression mapping and use the AddExpressionMapping method (as you mentioned).
You can configure the AbpAutoMapperOptions option as follow to enable expression mapping (after adding the related package to your *.Application project):
Configure<AbpAutoMapperOptions>(options =>
{
//add a new configurators and call the AddExpressionMapping()
options.Configurators.Add(config =>
{
config.MapperConfiguration.AddExpressionMapping();
});
options.AddMaps<TesttApplicationModule>();
});

Related

Circular dependency on Elsa IWorkflowLaunchpad when injecting ISignaler with custom WorkflowContexProvider

I'm evaluating Elsa for a new project at work, but have run into trouble when creating a service that DIs ISignaler, only when I also have a WorkflowContextProvider as indicated below.
My WorkflowProvider:
public class ContractorWorkflowContextProvider : WorkflowContextRefresher<ContractorRecruitVM>
{
private IContractorApi _contractorApi;
public ContractorWorkflowContextProvider(IContractorApi api)
{
_contractorApi = api;
}
public override async ValueTask<ContractorRecruitVM?> LoadAsync(LoadWorkflowContext context, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(context.ContextId))
{
return null;
}
var ctrId = int.Parse(context.ContextId);
var vm = await _contractorApi.GetById(ctrId);
return vm;
}
...
}
ContractorApi:
public class ContractorApi : IContractorApi
{
IPersonRepo _personRepo;
IElsaDemoUOW _uow;
IElsaSignalService _elsaSignalService;
public ContractorApi(IPersonRepo personRepo, IElsaDemoUOW uow, IElsaSignalService elsaSignalService)
{
_personRepo = personRepo;
_uow = uow;
_elsaSignalService = elsaSignalService;
}
}
and my ElsaSignalService:
public class ElsaSignalService : IElsaSignalService
{
ISignaler _signaler;
public ElsaSignalService(ISignaler signaler)
{
_signaler = signaler;
}
public async Task<bool> CtrInitDocsUploaded(string workflowInstanceId)
{
var res = await _signaler.TriggerSignalAsync(signal: "ctr-init-docs-uploaded", workflowInstanceId: workflowInstanceId);
return true;
}
}
DI is setup like:
...
services
.AddElsa(elsa => elsa
.UseEntityFrameworkPersistence(ef => ef.UseSqlServer(elsaConnString, b => b.MigrationsAssembly("Elsa.Persistence.EntityFramework.SqlServer")))
.AddConsoleActivities()
.AddHttpActivities(elsaSection.GetSection("Server").Bind)
.AddQuartzTemporalActivities()
.AddJavaScriptActivities()
.AddWorkflowsFrom<Startup>()
.AddEmailActivities(elsaSection.GetSection("Smtp").Bind)
)
.AddWorkflowContextProvider<ContractorWorkflowContextProvider>();
...
services.AddTransient<IElsaDemoUOW, ElsaDemoUOW>();
services.AddTransient<IPersonRepo, PersonRepo>();
services.AddTransient<IContractorApi, ContractorApi>();
services.AddTransient<IElsaSignalService, ElsaSignalService>();
...
I'm not sure if I'm going about this correctly at all, but my goal is to have my "ElsaSignalService" that I'll be calling from the API, and that ElsaSignalService will raise whatever signal is appropriate.
Elsa will receive the signals, and in some/many cases save the workflowcontext back (using the custom provider I specified). If this is a valid setup, I'm not sure where the circular reference error is coming from, as I don't see one. If I'm misunderstanding how to structure Elsa to do what I'm after, would greatly appreciate input.
When I hit the app with the setup described above, I get the following circular dependency error but nothing I see looks like it's creating one.
A circular dependency was detected for the service of type 'Elsa.Services.IWorkflowLaunchpad'.
Elsa.Services.IWorkflowLaunchpad(Elsa.Services.Workflows.WorkflowLaunchpad) ->
Elsa.Services.IWorkflowRunner(Elsa.Services.Workflows.WorkflowRunner) ->
Elsa.Services.IWorkflowContextManager(Elsa.Services.WorkflowContexts.WorkflowContextManager) ->
System.Collections.Generic.IEnumerable<Elsa.Providers.WorkflowContexts.IWorkflowContextProvider> ->
Elsa.Providers.WorkflowContexts.IWorkflowContextProvider(ElsaDemo.DemoApp.WorkflowContexts.ContractorWorkflowContextProvider) ->
ElsaDemo.DemoApp.API.IContractorApi(ElsaDemo.DemoApp.API.ContractorApi) ->
ElsaDemo.DemoApp.Services.IElsaSignalService(ElsaDemo.DemoApp.Services.ElsaSignalService) ->
Elsa.Activities.Signaling.Services.ISignaler(Elsa.Activities.Signaling.Services.Signaler) ->
Elsa.Services.IWorkflowLaunchpad

Entity Framework Core CompileAsyncQuery syntax to do a query returning a list?

Documentation and examples online about compiled async queries are kinda sparse, so I might as well ask for guidance here.
Let's say I have a repository pattern method like this to query all entries in a table:
public async Task<List<ProgramSchedule>> GetAllProgramsScheduledList()
{
using (var context = new MyDataContext(_dbOptions))
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await context.ProgramsScheduledLists.ToListAsync();
}
}
This works fine.
Now I want to do the same, but with an async compiled query.
One way I managed to get it to compile is with this syntax:
static readonly Func<MyDataContext, Task<List<ProgramSchedule>>> GetAllProgramsScheduledListQuery;
static ProgramsScheduledListRepository()
{
GetAllProgramsScheduledListQuery = EF.CompileAsyncQuery<MyDataContext, List<ProgramSchedule>>(t => t.ProgramsScheduledLists.ToList());
}
public async Task<List<ProgramSchedule>> GetAllProgramsScheduledList()
{
using (var context = new MyDataContext(_dbOptions))
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return await GetAllProgramsScheduledListQuery(context);
}
}
But then on runtime this exception get thrown:
System.ArgumentException: Expression of type 'System.Collections.Generic.List`1[Model.Scheduling.ProgramSchedule]' cannot be used for return type 'System.Threading.Tasks.Task`1[System.Collections.Generic.List`1[Model.Scheduling.ProgramSchedule]]'
The weird part is that if I use any other operator (for example SingleOrDefault), it works fine. It only have problem returning List.
Why?
EF.CompileAsync for set of records, returns IAsyncEnumrable<T>. To get List from such query you have to enumerate IAsyncEnumrable and fill List,
private static Func<MyDataContext, IAsyncEnumerable<ProgramSchedule>> compiledQuery =
EF.CompileAsyncQuery((MyDataContext ctx) =>
ctx.ProgramsScheduledLists);
public static async Task<List<ProgramSchedule>> GetAllProgramsScheduledList(CancellationToken ct = default)
{
using (var context = new MyDataContext(_dbOptions))
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var result = new List<ProgramSchedule>();
await foreach (var s in compiledQuery(context).WithCancellation(ct))
{
result.Add(s);
}
return result;
}
}

ef core - multi tenancy using global filters

OnModelCreating is called once per db context. This is a problem since the tenant Id is set per request.
How do I re-configure the global filter everytime I create an new instance of the dbcontext?
If I can't use global filter, what is the alternative way?
Update:
I needed to provide a generic filter with an expression like e => e.TenantId == _tenantId. I am using the following expression:
var p = Expression.Parameter(type, "e");
Expression.Lambda(
Expression.Equal(
Expression.Property(p, tenantIdProperty.PropertyInfo),
Expression.Constant(_tenantId))
p);
Since this is run once, _tenantId is fixed. So even if I update it, the first value is captured in the linq expression.
So my question, what is the proper way to set the right side of that equality.
With EF.Core you can actually use the following filter and syntax
protected void OnModelCreating(ModelBuilder modelBuilder)
{
var entityConfiguration = modelBuilder.Entity<MyTenantAwareEntity>();
entityConfiguration.ToTable("my_table")
.HasQueryFilter(e => EF.Property<string>(e, "TenantId") == _tenantProvider.GetTenant())
[...]
The _tenantProvider is the class responsible to get your tenant, in your case from the HttpRequest, to do it you can use HttpContextAccessor.
This is fixed with the following as the right expression
Expression.MakeMemberAccess(
Expression.Constant(this, baseDbContextType),
baseDbContextType.GetProperty("TenantId")
I use a base class for all my db contexts.
GetProperty() works as is because TenantId is public property.
and..
If you use it with soft delete, the solution is; --for ef core 3.1
internal static void AddQueryFilter<T>(this EntityTypeBuilder
entityTypeBuilder, Expression<Func<T, bool>> expression)
{
var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
var expressionFilter = ReplacingExpressionVisitor.Replace(
expression.Parameters.Single(), parameterType, expression.Body);
var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter();
if (currentQueryFilter != null)
{
var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
}
var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
entityTypeBuilder.HasQueryFilter(lambdaExpression);
}
Usage:
if (typeof(ITrackSoftDelete).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackSoftDelete>(e => IsSoftDeleteFilterEnabled == false || e.IsDeleted == false);
if (typeof(ITrackTenant).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackTenant>(e => e.TenantId == MyTenantId);
Thanks to YZahringer

ElasticSearch Nest Create Fields Object

Looking at the docs we should be able to create a new source filter like so
new SearchRequest<Project>
{
Source = new SourceFilter
{
Include = Fields<Project>(p => p.Name, prop => prop.StartedOn)
}
}
The issue I'm facing is that Fields isn't typed and doesn't have a constructor.
How do I go about making a Fields for use in sourceFilters, queries etc?
You can find Fields<> method in class Infer, so change example code to
new SearchRequest<Project>
{
Source = new SourceFilter
{
Include = Infer.Fields<Project>(p => p.Name, prop => prop.StartedOn)
}
}
Also you can import this static class in your cs file with using static Nest.Infer;, so you will be able to use this example as it is.
Hope it helps.

Linq Parsing Error when trying to create seperation of concerns

I am in the middle of a refactoring cycle where I converted some extension methods that used to look like this:
public static IQueryable<Family> FilterOnRoute(this IQueryable<Family> families, WicRoute route)
{
return families.Where(fam => fam.PODs
.Any(pod => pod.Route.RouteID == route.RouteID));
}
to a more fluent implementation like this:
public class SimplifiedFamilyLinqBuilder
{
private IQueryable<Family> _families;
public SimplifiedFamilyLinqBuilder Load(IQueryable<Family> families)
{
_families = families;
return this;
}
public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
_families = _families.Where(fam => fam.PODs
.Any(pod => pod.Route.RouteID == route.RouteID));
return this;
}
public IQueryable<Family> AsQueryable()
{
return _families;
}
}
which I can call like this: (note this is using Linq-to-Nhibernate)
var families =
new SimplifiedFamilyLinqBuilder()
.Load(session.Query<Family>())
.OnRoute(new WicRoute() {RouteID = 1})
.AsQueryable()
.ToList();
this produces the following SQL which is fine with me at the moment: (of note is that the above Linq is being translated to a SQL Query)
select ... from "Family" family0_
where exists (select pods1_.PODID from "POD" pods1_
inner join Route wicroute2_ on pods1_.RouteID=wicroute2_.RouteID
where family0_.FamilyID=pods1_.FamilyID
and wicroute2_.RouteID=#p0);
#p0 = 1
my next effort in refactoring is to move the query part that deals with the child to another class like this:
public class SimplifiedPODLinqBuilder
{
private IQueryable<POD> _pods;
public SimplifiedPODLinqBuilder Load(IQueryable<POD> pods)
{
_pods = pods;
return this;
}
public SimplifiedPODLinqBuilder OnRoute(WicRoute route)
{
_pods = _pods.Where(pod => pod.Route.RouteID == route.RouteID);
return this;
}
public IQueryable<POD> AsQueryable()
{
return _pods;
}
}
with SimplifiedFamilyLinqBuilder changing to this:
public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
_families = _families.Where(fam =>
_podLinqBuilder.Load(fam.PODs.AsQueryable())
.OnRoute(route)
.AsQueryable()
.Any()
);
return this;
}
only I now get this error:
Remotion.Linq.Parsing.ParserException : Cannot parse expression 'value(Wic.DataTests.LinqBuilders.SimplifiedPODLinqBuilder)' as it has an unsupported type. Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed.
I started to implement IQueryable on SimplifiedPODLinqBuilder(as that seemed more logical than implementing IEnumberable) and thought I would be clever by doing this:
public class SimplifiedPODLinqBuilder : IQueryable
{
private IQueryable<POD> _pods;
...
public IEnumerator GetEnumerator()
{
return _pods.GetEnumerator();
}
public Expression Expression
{
get { return _pods.Expression; }
}
public Type ElementType
{
get { return _pods.ElementType; }
}
public IQueryProvider Provider
{
get { return _pods.Provider; }
}
}
only to get this exception (apparently Load is not being called and _pods is null):
System.NullReferenceException : Object reference not set to an instance of an object.
is there a way for me to refactor this code out that will parse properly into an expression that will go to SQL?
The part fam => _podLinqBuilder.Load(fam.PODs.AsQueryable() is never going to work, because the linq provider will try to parse this into SQL and for that it needs mapped members of Family after the =>, or maybe a mapped user-defined function but I don't know if Linq-to-Nhibernate supports that (I never really worked with it, because I still doubt if it is production-ready).
So, what can you do?
To be honest, I like the extension methods much better. You switched to a stateful approach, which doesn't mix well with the stateless paradigm of linq. So you may consider to retrace your steps.
Another option: the expression in .Any(pod => pod.Route.RouteID == route.RouteID)); could be paremeterized (.Any(podExpression), with
OnRoute(WicRoute route, Expression<Func<POD,bool>> podExpression)
(pseudocode).
Hope this makes any sense.
You need to separate methods you intend to call from expressions you intend to translate.
This is great, you want each of those methods to run. They return an instance that implements IQueryable<Family> and operate on that instance.
var families = new SimplifiedFamilyLinqBuilder()
.Load(session.Query<Family>())
.OnRoute(new WicRoute() {RouteID = 1})
.AsQueryable()
.ToList();
This is no good. you don't want Queryable.Where to get called, you want it to be an expression tree which can be translated to SQL. But PodLinqBuilder.Load is a node in that expression tree which can't be translated to SQL!
families = _families
.Where(fam => _podLinqBuilder.Load(fam.PODs.AsQueryable())
.OnRoute(route)
.AsQueryable()
.Any();
You can't call .Load inside the Where expression (it won't translate to sql).
You can't call .Load outside the Where expression (you don't have the fam parameter).
In the name of "separation of concerns", you are mixing query construction methods with query definition expressions. LINQ, by its Integrated nature, encourages you to attempt this thing which will not work.
Consider making expression construction methods instead of query construction methods.
public static Expression<Func<Pod, bool>> GetOnRouteExpr(WicRoute route)
{
int routeId = route.RouteID;
Expression<Func<Pod, bool>> result = pod => pod.Route.RouteID == route.RouteID;
return result;
}
called by:
Expression<Func<Pod, bool>> onRoute = GetOnRouteExpr(route);
families = _families.Where(fam => fam.PODs.Any(onRoute));
With this approach, the question is now - how do I fluidly hang my ornaments from the expression tree?

Resources