add polymnorphism

This commit is contained in:
honzapatCZ 2025-01-11 19:00:51 +01:00
parent c0d49b8e24
commit ba100f0010
5 changed files with 323 additions and 240 deletions

View File

@ -17,8 +17,8 @@ namespace NejCommon.Controllers
{ {
public abstract class AutoChildController<TOwner, TType, TRequest, TResponse> : AutoChildController<TOwner, TType, TResponse, TRequest, TResponse> public abstract class AutoChildController<TOwner, TType, TRequest, TResponse> : AutoChildController<TOwner, TType, TResponse, TRequest, TResponse>
where TType : class, new() where TType : class, new()
where TRequest : IAutomappedAttribute<TType, TRequest>, new() where TRequest : class, IAutomappedAttribute<TType, TRequest>, new()
where TResponse : IAutomappedAttribute<TType, TResponse>, new() where TResponse : class, IAutomappedAttribute<TType, TResponse>, new()
{ {
public AutoChildController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers) public AutoChildController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
{ {
@ -34,9 +34,9 @@ namespace NejCommon.Controllers
[ApiController] [ApiController]
public abstract class AutoChildController<TOwner, TType, TGetResponse, TUpdateRequest, TUpdateResponse> : ControllerBase public abstract class AutoChildController<TOwner, TType, TGetResponse, TUpdateRequest, TUpdateResponse> : ControllerBase
where TType : class, new() where TType : class, new()
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new() where TGetResponse : class, IAutomappedAttribute<TType, TGetResponse>, new()
where TUpdateRequest : IAutomappedAttribute<TType, TUpdateRequest>, new() where TUpdateRequest : class, IAutomappedAttribute<TType, TUpdateRequest>, new()
where TUpdateResponse : IAutomappedAttribute<TType, TUpdateResponse>, new() where TUpdateResponse : class, IAutomappedAttribute<TType, TUpdateResponse>, new()
{ {
protected readonly CommonDbContext db; protected readonly CommonDbContext db;
protected readonly IServiceProvider providers; protected readonly IServiceProvider providers;
@ -99,8 +99,6 @@ namespace NejCommon.Controllers
}*/ }*/
body.ApplyTo(providers, entity); body.ApplyTo(providers, entity);
var dat = new TUpdateResponse().ApplyFrom(providers, entity);
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity); var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity);
//use the private constructor thru reflection //use the private constructor thru reflection

View File

@ -17,9 +17,9 @@ namespace NejCommon.Controllers
{ {
public abstract class AutoController<TOwner, TType, TRequest, TResponse> : AutoController<TOwner, TType, TResponse, TRequest, TResponse, TResponse, TRequest, TResponse> public abstract class AutoController<TOwner, TType, TRequest, TResponse> : AutoController<TOwner, TType, TResponse, TRequest, TResponse, TResponse, TRequest, TResponse>
where TType : class, new() where TType : class
where TRequest : IAutomappedAttribute<TType, TRequest>, new() where TRequest : class, IAutomappedAttribute<TType, TRequest>, new()
where TResponse : IAutomappedAttribute<TType, TResponse>, new() where TResponse : class, IAutomappedAttribute<TType, TResponse>, new()
{ {
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers) public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
{ {
@ -34,13 +34,13 @@ namespace NejCommon.Controllers
/// <typeparam name="TResponse">The response type</typeparam> /// <typeparam name="TResponse">The response type</typeparam>
[ApiController] [ApiController]
public abstract partial class AutoController<TOwner, TType, TGetAllResponse, TCreateRequest, TCreateResponse, TGetResponse, TUpdateRequest, TUpdateResponse> : AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse> public abstract partial class AutoController<TOwner, TType, TGetAllResponse, TCreateRequest, TCreateResponse, TGetResponse, TUpdateRequest, TUpdateResponse> : AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse>
where TType : class, new() where TType : class
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new() where TGetAllResponse : class, IAutomappedAttribute<TType, TGetAllResponse>, new()
where TCreateRequest : IAutomappedAttribute<TType, TCreateRequest>, new() where TCreateRequest : class, IAutomappedAttribute<TType, TCreateRequest>, new()
where TCreateResponse : IAutomappedAttribute<TType, TCreateResponse>, new() where TCreateResponse : class, IAutomappedAttribute<TType, TCreateResponse>, new()
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new() where TGetResponse : class, IAutomappedAttribute<TType, TGetResponse>, new()
where TUpdateRequest : IAutomappedAttribute<TType, TUpdateRequest>, new() where TUpdateRequest : class, IAutomappedAttribute<TType, TUpdateRequest>, new()
where TUpdateResponse : IAutomappedAttribute<TType, TUpdateResponse>, new() where TUpdateResponse : class, IAutomappedAttribute<TType, TUpdateResponse>, new()
{ {
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers) public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
@ -59,6 +59,12 @@ namespace NejCommon.Controllers
props.SetValue(entity, comp); props.SetValue(entity, comp);
return entity; return entity;
} }
protected override IAutomappedAttribute GetResponseType(RequestedResponseType type, TType entity) => type switch
{
RequestedResponseType.Create => new TCreateResponse(),
RequestedResponseType.Update => new TUpdateResponse(),
_ => base.GetResponseType(type, entity),
};
/// <summary> /// <summary>
/// Creates the <typeparamref name="TType"/> /// Creates the <typeparamref name="TType"/>
@ -69,15 +75,16 @@ namespace NejCommon.Controllers
[HttpPost] [HttpPost]
public virtual async Task<Results<BadRequest<Error>, CreatedAtRoute<TCreateResponse>>> Create([FromServices] TOwner company, [FromBody] TCreateRequest body) public virtual async Task<Results<BadRequest<Error>, CreatedAtRoute<TCreateResponse>>> Create([FromServices] TOwner company, [FromBody] TCreateRequest body)
{ {
var entity = db.Create<TType>(); var type = body.GetSourceType();
var entity = (TType)db.Create(type);
entity = AssociateWithParent(entity, company); entity = AssociateWithParent(entity, company);
await db.AddAsync(entity); await db.AddAsync(entity);
body.ApplyTo(providers, entity); body.ApplyTo(providers, (object)entity);
return await db.ApiSaveChangesAsyncCreate<TType, TCreateResponse>(providers, entity); return await db.ApiSaveChangesAsyncCreate<TType, TCreateResponse>(providers, entity, true, (TCreateResponse)GetResponseType(RequestedResponseType.Create, entity));
} }
/// <summary> /// <summary>
@ -106,11 +113,11 @@ namespace NejCommon.Controllers
[Route("{id}/")] [Route("{id}/")]
public virtual async Task<Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>> Update([FromServices][ModelBinder(Name = "id")] TType entity, [FromBody] TUpdateRequest body) public virtual async Task<Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>> Update([FromServices][ModelBinder(Name = "id")] TType entity, [FromBody] TUpdateRequest body)
{ {
body.ApplyTo(providers, entity); body.ApplyTo(providers, (object)entity);
var dat = new TUpdateResponse().ApplyFrom(providers, entity); var dat = new TUpdateResponse().ApplyFrom(providers, entity);
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity); var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity, true, (TUpdateResponse)GetResponseType(RequestedResponseType.Update, entity));
//use the private constructor thru reflection //use the private constructor thru reflection
var ctor = typeof(Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; var ctor = typeof(Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
@ -119,7 +126,7 @@ namespace NejCommon.Controllers
} }
[ApiController] [ApiController]
public abstract class AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse> : ControllerBase public abstract class AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse> : ControllerBase
where TType : class, new() where TType : class
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new() where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new()
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new() where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
{ {
@ -134,6 +141,18 @@ namespace NejCommon.Controllers
protected abstract IQueryable<TType> GetQuery(TOwner comp); protected abstract IQueryable<TType> GetQuery(TOwner comp);
public enum RequestedResponseType
{
Get,
Create,
Update
}
protected virtual IAutomappedAttribute GetResponseType(RequestedResponseType type, TType entity) => type switch
{
RequestedResponseType.Get => new TGetResponse(),
_ => throw new InvalidOperationException("Not implemented"),
};
protected virtual IQueryable<TType> ApplyDefaultOrdering(IQueryable<TType> query) protected virtual IQueryable<TType> ApplyDefaultOrdering(IQueryable<TType> query)
{ {
return query; return query;
@ -169,8 +188,8 @@ namespace NejCommon.Controllers
[Route("{id}/")] [Route("{id}/")]
public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices][ModelBinder(Name = "id")] TType entity) public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices][ModelBinder(Name = "id")] TType entity)
{ {
var dat = new TGetResponse().ApplyFrom(providers, entity); var dat = GetResponseType(RequestedResponseType.Get, entity).ApplyFrom(providers, entity);
return TypedResults.Ok(dat); return TypedResults.Ok((TGetResponse)dat);
} }
} }
} }

View File

@ -1,7 +1,14 @@
using System.Diagnostics.Eventing.Reader; using System.Diagnostics.Eventing.Reader;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
@ -9,100 +16,132 @@ namespace NejCommon.Controllers
{ {
public class TypedResultsMetadataProvider : IOperationFilter public class TypedResultsMetadataProvider : IOperationFilter
{ {
public void Apply(OpenApiOperation operation, OperationFilterContext context) private readonly Lazy<string[]> _contentTypes;
/// <summary>
/// Constructor to inject services
/// </summary>
/// <param name="mvc">MVC options to define response content types</param>
public TypedResultsMetadataProvider(IOptions<MvcOptions> mvc)
{ {
var responseType = context.MethodInfo.ReturnType; _contentTypes = new Lazy<string[]>(() =>
//Console.WriteLine(context.MethodInfo.DeclaringType.Name);
//Console.WriteLine(context.MethodInfo.Name);
//Console.WriteLine(responseType);
var t = IsSubclassOfRawGeneric(typeof(Microsoft.AspNetCore.Http.HttpResults.Results<,>), responseType);
if (t == null)
{ {
return; var apiResponseTypes = new List<string>();
} if (mvc.Value == null)
var parArg = t.GetGenericArguments();
if (operation.Responses.ContainsKey("200"))
operation.Responses.Remove("200");
foreach (var arg in parArg)
{
if (arg == typeof(NotFound))
{ {
operation.Responses.Add("404", new OpenApiResponse { Description = "Not found" }); apiResponseTypes.Add("application/json");
}
else if (arg == typeof(Ok))
{
operation.Responses.Add("200", new OpenApiResponse { Description = "Success" });
}
else if (IsSubclassOfRawGeneric(typeof(Ok<>), arg) != null)
{
var okArg = IsSubclassOfRawGeneric(typeof(Ok<>), arg).GetGenericArguments()[0];
// Console.WriteLine("Adding: " + okArg);
//get or generate the schema
var schema = context.SchemaGenerator.GenerateSchema(okArg, context.SchemaRepository);
operation.Responses.Add("200", new OpenApiResponse { Description = "Success", Content = { { "application/json", new OpenApiMediaType { Schema = schema } } } });
}
else if (arg == typeof(CreatedAtRoute))
{
operation.Responses.Add("201", new OpenApiResponse { Description = "Success" });
}
else if (IsSubclassOfRawGeneric(typeof(CreatedAtRoute<>), arg) != null)
{
if (operation.Responses.ContainsKey("201"))
operation.Responses.Remove("201");
var okArg = IsSubclassOfRawGeneric(typeof(CreatedAtRoute<>), arg).GetGenericArguments()[0];
// Console.WriteLine("Adding: " + okArg);
//get or generate the schema
var schema = context.SchemaGenerator.GenerateSchema(okArg, context.SchemaRepository);
operation.Responses.Add("201", new OpenApiResponse { Description = "Success", Content = { { "application/json", new OpenApiMediaType { Schema = schema } } } });
}
else if (arg == typeof(BadRequest))
{
operation.Responses.Add("400", new OpenApiResponse { Description = "There was an error" });
}
else if (IsSubclassOfRawGeneric(typeof(BadRequest<>), arg) != null)
{
if (operation.Responses.ContainsKey("400"))
operation.Responses.Remove("400");
var okArg = IsSubclassOfRawGeneric(typeof(BadRequest<>), arg).GetGenericArguments()[0];
// Console.WriteLine("Adding: " + okArg);
//get or generate the schema
var schema = context.SchemaGenerator.GenerateSchema(okArg, context.SchemaRepository);
operation.Responses.Add("400", new OpenApiResponse { Description = "There was an error", Content = { { "application/json", new OpenApiMediaType { Schema = schema } } } });
}
else if (arg == typeof(FileStreamHttpResult)){
operation.Responses.Add("200", new OpenApiResponse { Description = "Success", Content = { { "application/octet-stream", new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string", Format = "binary" } } } } });
} }
else else
{ {
Console.WriteLine("Unknown type: " + arg); var jsonApplicationType = mvc.Value.FormatterMappings.GetMediaTypeMappingForFormat("json");
if (jsonApplicationType != null)
apiResponseTypes.Add(jsonApplicationType);
var xmlApplicationType = mvc.Value.FormatterMappings.GetMediaTypeMappingForFormat("xml");
if (xmlApplicationType != null)
apiResponseTypes.Add(xmlApplicationType);
} }
return apiResponseTypes.ToArray();
});
}
void IOperationFilter.Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (!IsControllerAction(context)) return;
var actionReturnType = UnwrapTask(context.MethodInfo.ReturnType);
if (!IsHttpResults(actionReturnType)) return;
if (typeof(IEndpointMetadataProvider).IsAssignableFrom(actionReturnType))
{
var populateMetadataMethod = actionReturnType.GetMethod("Microsoft.AspNetCore.Http.Metadata.IEndpointMetadataProvider.PopulateMetadata", BindingFlags.Static | BindingFlags.NonPublic);
if (populateMetadataMethod == null) return;
var endpointBuilder = new MetadataEndpointBuilder();
populateMetadataMethod.Invoke(null, new object[] { context.MethodInfo, endpointBuilder });
var responseTypes = endpointBuilder.Metadata.Cast<IProducesResponseTypeMetadata>().ToList();
if (!responseTypes.Any()) return;
operation.Responses.Clear();
foreach (var responseType in responseTypes)
{
var statusCode = responseType.StatusCode.ToString();
var oar = new OpenApiResponse { Description = GetResponseDescription(statusCode) };
if (responseType.Type != null && responseType.Type != typeof(void))
{
var schema = context.SchemaGenerator.GenerateSchema(responseType.Type, context.SchemaRepository);
foreach (var contentType in _contentTypes.Value)
{
oar.Content.Add(contentType, new OpenApiMediaType { Schema = schema });
}
}
operation.Responses.Add(statusCode, oar);
}
}
else if (actionReturnType == typeof(UnauthorizedHttpResult))
{
operation.Responses.Clear();
operation.Responses.Add("401", new OpenApiResponse { Description = ReasonPhrases.GetReasonPhrase(401) });
} }
} }
static Type? IsSubclassOfRawGeneric(Type generic, Type toCheck) private static bool IsControllerAction(OperationFilterContext context)
=> context.ApiDescription.ActionDescriptor is ControllerActionDescriptor;
private static bool IsHttpResults(Type type)
=> type.Namespace == "Microsoft.AspNetCore.Http.HttpResults";
private static Type UnwrapTask(Type type)
{ {
while (toCheck != null && toCheck != typeof(object)) if (type.IsGenericType)
{ {
//if Task is used, we need to check the underlying type var genericType = type.GetGenericTypeDefinition();
var realTypeNoTask = toCheck.IsGenericType && toCheck.GetGenericTypeDefinition() == typeof(Task<>) ? toCheck.GetGenericArguments()[0] : toCheck; if (genericType == typeof(Task<>) || genericType == typeof(ValueTask<>))
var cur = realTypeNoTask.IsGenericType ? realTypeNoTask.GetGenericTypeDefinition() : realTypeNoTask;
//Console.WriteLine(cur);
if (generic == cur)
{ {
return realTypeNoTask; return type.GetGenericArguments()[0];
} }
toCheck = toCheck.BaseType;
} }
return null; return type;
}
private static string? GetResponseDescription(string statusCode)
=> ResponseDescriptionMap
.FirstOrDefault(entry => Regex.IsMatch(statusCode, entry.Key))
.Value;
private static readonly IReadOnlyCollection<KeyValuePair<string, string>> ResponseDescriptionMap = new[]
{
new KeyValuePair<string, string>("1\\d{2}", "Information"),
new KeyValuePair<string, string>("201", "Created"),
new KeyValuePair<string, string>("202", "Accepted"),
new KeyValuePair<string, string>("204", "No Content"),
new KeyValuePair<string, string>("2\\d{2}", "Success"),
new KeyValuePair<string, string>("304", "Not Modified"),
new KeyValuePair<string, string>("3\\d{2}", "Redirect"),
new KeyValuePair<string, string>("400", "Bad Request"),
new KeyValuePair<string, string>("401", "Unauthorized"),
new KeyValuePair<string, string>("403", "Forbidden"),
new KeyValuePair<string, string>("404", "Not Found"),
new KeyValuePair<string, string>("405", "Method Not Allowed"),
new KeyValuePair<string, string>("406", "Not Acceptable"),
new KeyValuePair<string, string>("408", "Request Timeout"),
new KeyValuePair<string, string>("409", "Conflict"),
new KeyValuePair<string, string>("429", "Too Many Requests"),
new KeyValuePair<string, string>("4\\d{2}", "Client Error"),
new KeyValuePair<string, string>("5\\d{2}", "Server Error"),
new KeyValuePair<string, string>("default", "Error")
};
private sealed class MetadataEndpointBuilder : EndpointBuilder
{
public override Endpoint Build() => throw new NotImplementedException();
} }
} }
} }

View File

@ -11,158 +11,171 @@ using Microsoft.EntityFrameworkCore;
namespace NejCommon.Models; namespace NejCommon.Models;
public abstract class CommonDbContext : DbContext public abstract class CommonDbContext : DbContext
{
public CommonDbContext() : base()
{ {
public CommonDbContext() : base() }
public CommonDbContext(DbContextOptions options)
: base(options)
{
}
public abstract CommonDbContext CreateCopy();
public async Task<bool> ApiSaveChangesAsync()
{
try
{ {
await SaveChangesAsync();
return true;
} }
public CommonDbContext(DbContextOptions options) catch (Exception ex)
: base(options)
{ {
Console.WriteLine("Error saving db: " + ex.Message);
Console.WriteLine(ex.StackTrace);
return false;
}
}
public static BadRequest<Error> SaveError = TypedResults.BadRequest(new Error
{
Message = "Error saving data to database"
});
public async Task<Results<BadRequest<Error>, T1>> ApiSaveChangesAsync<T1>(T1 value) where T1 : IResult
{
var res = await ApiSaveChangesAsync();
if (res)
return value;
else
return SaveError;
}
public async Task<Results<BadRequest<Error>, CreatedAtRoute<T2>>> ApiSaveChangesAsyncCreate<T1, T2>(IServiceProvider providers, T1 value, bool apply = true, T2? response = null) where T2 : class, IAutomappedAttribute<T1, T2>, new()
{
if(response == null)
response = new T2();
if (!apply)
return TypedResults.CreatedAtRoute(response.ApplyFrom(providers, value));
var res = await ApiSaveChangesAsync();
if (res)
return TypedResults.CreatedAtRoute(response.ApplyFrom(providers, value));
else
return SaveError;
}
public async Task<Results<BadRequest<Error>, Ok<T2>>> ApiSaveChangesAsyncOk<T1, T2>(IServiceProvider providers, T1 value, bool apply = true, T2? response = null) where T2 : class, IAutomappedAttribute<T1, T2>, new()
{
if(response == null)
response = new T2();
if (!apply)
return TypedResults.Ok(response.ApplyFrom(providers, value));
var res = await ApiSaveChangesAsync();
if (res)
return TypedResults.Ok(response.ApplyFrom(providers, value));
else
return SaveError;
}
public async Task<T> FindOrCreateAsync<T>(Expression<Func<T, bool>> predicate, Func<T> factory) where T : class
{
var entity = ChangeTracker.Entries<T>().Select(x => x.Entity).FirstOrDefault(predicate.Compile());
//find in change tracker
if (entity != null && Entry(entity).State == EntityState.Deleted)
{
Entry(entity).State = EntityState.Modified;
} }
public abstract CommonDbContext CreateCopy(); //find in currentDb
if (entity == null)
public async Task<bool> ApiSaveChangesAsync()
{ {
try entity = await Set<T>().FirstOrDefaultAsync(predicate);
{
await SaveChangesAsync();
return true;
}
catch (Exception ex)
{
Console.WriteLine("Error saving db: " + ex.Message);
Console.WriteLine(ex.StackTrace);
return false;
}
} }
public static BadRequest<Error> SaveError = TypedResults.BadRequest(new Error //find in up-to-date db
if (entity == null)
{ {
Message = "Error saving data to database" var newAppDB = CreateCopy();
});
public async Task<Results<BadRequest<Error>, T1>> ApiSaveChangesAsync<T1>(T1 value) where T1 : IResult entity = await newAppDB.Set<T>().FirstOrDefaultAsync(predicate);
{
var res = await ApiSaveChangesAsync();
if (res) //track the entity if it's not null and not already being tracked
return value; if (entity != null && this.Entry(entity).State == EntityState.Detached)
else Attach(entity);
return SaveError;
}
public async Task<Results<BadRequest<Error>, CreatedAtRoute<T2>>> ApiSaveChangesAsyncCreate<T1, T2>(IServiceProvider providers, T1 value, bool apply = true) where T2 : IAutomappedAttribute<T1, T2>, new()
{
if (!apply)
return TypedResults.CreatedAtRoute(new T2().ApplyFrom(providers, value));
var res = await ApiSaveChangesAsync();
if (res)
return TypedResults.CreatedAtRoute(new T2().ApplyFrom(providers, value));
else
return SaveError;
}
public async Task<Results<BadRequest<Error>, Ok<T2>>> ApiSaveChangesAsyncOk<T1, T2>(IServiceProvider providers, T1 value, bool apply = true) where T2 : IAutomappedAttribute<T1, T2>, new()
{
if (!apply)
return TypedResults.Ok(new T2().ApplyFrom(providers, value));
var res = await ApiSaveChangesAsync();
if (res)
return TypedResults.Ok(new T2().ApplyFrom(providers, value));
else
return SaveError;
}
public async Task<T> FindOrCreateAsync<T>(Expression<Func<T, bool>> predicate, Func<T> factory) where T : class
{
var entity = ChangeTracker.Entries<T>().Select(x => x.Entity).FirstOrDefault(predicate.Compile());
//find in change tracker
if (entity != null && Entry(entity).State == EntityState.Deleted)
{
Entry(entity).State = EntityState.Modified;
}
//find in currentDb
if (entity == null)
{
entity = await Set<T>().FirstOrDefaultAsync(predicate);
}
//find in up-to-date db
if (entity == null)
{
var newAppDB = CreateCopy();
entity = await newAppDB.Set<T>().FirstOrDefaultAsync(predicate);
//track the entity if it's not null and not already being tracked
if (entity != null && this.Entry(entity).State == EntityState.Detached)
Attach(entity);
}
//create if not found
if (entity == null)
{
var newEntity = factory();
await this.AddAsync(newEntity);
entity = newEntity;
}
return entity;
}
public T FindOrCreate<T>(Expression<Func<T, bool>> predicate, Func<T> factory) where T : class
{
var entity = ChangeTracker.Entries<T>().Where(e => e.State != EntityState.Deleted).Select(x => x.Entity).FirstOrDefault(predicate.Compile());
//find in change tracker
if (entity != null && Entry(entity).State == EntityState.Deleted)
{
Entry(entity).State = EntityState.Modified;
}
//find in currentDb
if (entity == null)
{
entity = Set<T>().FirstOrDefault(predicate);
}
//find in up-to-date db
if (entity == null)
{
var newAppDB = CreateCopy();
entity = newAppDB.Set<T>().FirstOrDefault(predicate);
if (entity != null)
Attach(entity);
}
//create if not found
if (entity == null)
{
var newEntity = factory();
this.Add(newEntity);
entity = newEntity;
}
return entity;
} }
public T Create<T>(Action<T> config = null, params object[] constructorArguments) //create if not found
if (entity == null)
{ {
var entity = this.CreateProxy<T>(constructorArguments); var newEntity = factory();
await this.AddAsync(newEntity);
config?.Invoke(entity); entity = newEntity;
this.Add(entity);
this.ChangeTracker.DetectChanges();
return entity;
} }
public void ApplyRelationships() return entity;
}
public T FindOrCreate<T>(Expression<Func<T, bool>> predicate, Func<T> factory) where T : class
{
var entity = ChangeTracker.Entries<T>().Where(e => e.State != EntityState.Deleted).Select(x => x.Entity).FirstOrDefault(predicate.Compile());
//find in change tracker
if (entity != null && Entry(entity).State == EntityState.Deleted)
{ {
this.ChangeTracker.DetectChanges(); Entry(entity).State = EntityState.Modified;
} }
}
//find in currentDb
if (entity == null)
{
entity = Set<T>().FirstOrDefault(predicate);
}
//find in up-to-date db
if (entity == null)
{
var newAppDB = CreateCopy();
entity = newAppDB.Set<T>().FirstOrDefault(predicate);
if (entity != null)
Attach(entity);
}
//create if not found
if (entity == null)
{
var newEntity = factory();
this.Add(newEntity);
entity = newEntity;
}
return entity;
}
public T Create<T>(Action<T> config = null, params object[] constructorArguments)
{
var entity = this.CreateProxy<T>(constructorArguments);
config?.Invoke(entity);
this.Add(entity);
this.ChangeTracker.DetectChanges();
return entity;
}
public object Create(Type entityType, Action<object> config = null, params object[] constructorArguments)
{
var entity = this.CreateProxy(entityType, constructorArguments);
config?.Invoke(entity);
this.Add(entity);
this.ChangeTracker.DetectChanges();
return entity;
}
public void ApplyRelationships()
{
this.ChangeTracker.DetectChanges();
}
}

View File

@ -33,6 +33,20 @@ public static class Extensions
Data = query.Select(projector).AsAsyncEnumerable() Data = query.Select(projector).AsAsyncEnumerable()
}; };
} }
public static async Task<PaginationResponse<TResponseType>> ApplyPaginationRes<TType, TResponseType>(this IQueryable<TType> query, IServiceProvider providers, Pagination pag, Expression<Func<TType, TResponseType>> projector) where TResponseType : IAutomappedAttribute<TType, TResponseType>, new()
{
var totalCount = await query.CountAsync();
query = query.Skip(pag.Offset);
query = query.Take(pag.Count);
return new PaginationResponse<TResponseType>
{
TotalCount = totalCount,
Offset = pag.Offset,
Count = pag.Count,
Data = query.Select(projector).AsAsyncEnumerable()
};
}
public static async Task<PaginationResponse<TResponseType>> ApplySearchPaginationRes<TType, TResponseType, TEntityBinder>(this IQueryable<TType> query, string? search, IServiceProvider providers, Pagination pag, List<Expression<Func<TType, string, bool>>> matchers) public static async Task<PaginationResponse<TResponseType>> ApplySearchPaginationRes<TType, TResponseType, TEntityBinder>(this IQueryable<TType> query, string? search, IServiceProvider providers, Pagination pag, List<Expression<Func<TType, string, bool>>> matchers)
where TType : class where TType : class