Compare commits

...

3 Commits

Author SHA1 Message Date
honzapatCZ
ebee356321 Merge branch 'master' of https://git.nejcraft.cz/Nejcraft/NejCommon.NET
# Conflicts:
#	Controllers/AutoController.cs
2024-09-15 20:10:54 +02:00
honzapatCZ
90d875ad02 even more common 2024-09-15 20:08:31 +02:00
honzapatCZ
1d7e81c71d unify few extra services 2024-09-15 19:47:20 +02:00
13 changed files with 373 additions and 300 deletions

View File

@ -0,0 +1,103 @@
using System.Linq.Expressions;
using System.Reflection;
using ApiSourceGeneratorHelper;
using AutoMapPropertyHelper;
using EntityFrameworkCore.Projectables;
using EntityFrameworkCore.Projectables.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NejCommon.Utils;
using Swashbuckle.AspNetCore.Annotations;
using NejCommon.Models;
namespace NejCommon.Controllers
{
public abstract class AutoChildController<TOwner, TType, TRequest, TResponse> : AutoChildController<TOwner, TType, TResponse, TRequest, TResponse>
where TType : class, new()
where TRequest : IAutomappedAttribute<TType, TRequest>, new()
where TResponse : IAutomappedAttribute<TType, TResponse>, new()
{
public AutoChildController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
{
}
}
/// <summary>
///
/// </summary>
/// <typeparam name="TType">The underyling type</typeparam>
/// <typeparam name="TRequest">The request type</typeparam>
/// <typeparam name="TResponse">The response type</typeparam>
[ApiController]
public abstract class AutoChildController<TOwner, TType, TGetResponse, TUpdateRequest, TUpdateResponse> : ControllerBase
where TType : class, new()
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
where TUpdateRequest : IAutomappedAttribute<TType, TUpdateRequest>, new()
where TUpdateResponse : IAutomappedAttribute<TType, TUpdateResponse>, new()
{
protected readonly CommonDbContext db;
protected readonly IServiceProvider providers;
public AutoChildController(CommonDbContext appDb, IServiceProvider providers) : base()
{
db = appDb;
this.providers = providers;
}
protected abstract TType GetQuery(TOwner comp);
protected abstract void Assign(TOwner comp, TType query);
public virtual bool Trackable => true;
/// <summary>
/// Gets the <typeparamref name="TType"/>
/// </summary>
/// <param name="entity"></param>
/// <response code="200">Success</response>
/// <response code="0">There was an error</response>
[HttpGet]
[Route("")]
public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices] TOwner owner)
{
var entity = GetQuery(owner);
var dat = new TGetResponse().ApplyFrom(providers, entity);
return TypedResults.Ok(dat);
}
/// <summary>
/// Update company.
/// </summary>
/// <param name="entity"></param>
/// <param name="body"></param>
/// <response code="200">Success</response>
/// <response code="0">There was an error</response>
[HttpPut]
[Route("")]
public virtual async Task<Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>> Update([FromServices] TOwner owner, [FromBody] TUpdateRequest body)
{
var entity = GetQuery(owner);
if (entity == null)
{
if (Trackable)
entity = db.Create<TType>();
else
entity = new TType();
Assign(owner, entity);
}
/*
if(entity is InvoiceBase inv){
//Console.Writeline(inv.InvoiceItems.Count);
}*/
body.ApplyTo(providers, entity);
var dat = new TUpdateResponse().ApplyFrom(providers, entity);
var res = await db.ApiSaveChangesAsyncOk<TType, TUpdateResponse>(providers, entity);
//use the private constructor thru reflection
var ctor = typeof(Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
return (Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>)ctor.Invoke(new object[] { res.Result });
}
}
}

View File

@ -8,27 +8,20 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NejAccountingAPI.Controllers;
using NejAccountingAPI.Models;
using NejCommon.Models;
using NejCommon.Utils;
using Swashbuckle.AspNetCore.Annotations;
using NejCommon.Models;
using System.Runtime.CompilerServices;
namespace NejCommon.Controllers
{
public abstract class AutoController<TType, TRequest, TResponse> : AutoController<Company, TType, TRequest, TResponse> where TType : class, new() where TRequest : IAutomappedAttribute<TType, TRequest>, new() where TResponse : IAutomappedAttribute<TType, TResponse>, new()
{
public AutoController(AppDbContext appDb, IServiceProvider providers) : base(appDb, providers)
{
}
}
public abstract class AutoController<TOwner, TType, TRequest, TResponse> : AutoController<TOwner, TType, TResponse, TRequest, TResponse, TResponse, TRequest, TResponse>
where TType : class, new()
where TRequest : IAutomappedAttribute<TType, TRequest>, new()
where TResponse : IAutomappedAttribute<TType, TResponse>, new()
{
public AutoController(AppDbContext appDb, IServiceProvider providers) : base(appDb, providers)
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
{
}
}
@ -40,7 +33,7 @@ namespace NejCommon.Controllers
/// <typeparam name="TRequest">The request type</typeparam>
/// <typeparam name="TResponse">The response type</typeparam>
[ApiController]
public abstract class AutoController<TOwner, TType, TGetAllResponse, TCreateRequest, TCreateResponse, TGetResponse, TUpdateRequest, TUpdateResponse> : ControllerBase
public abstract partial class AutoController<TOwner, TType, TGetAllResponse, TCreateRequest, TCreateResponse, TGetResponse, TUpdateRequest, TUpdateResponse> : AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse>
where TType : class, new()
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new()
where TCreateRequest : IAutomappedAttribute<TType, TCreateRequest>, new()
@ -49,24 +42,15 @@ namespace NejCommon.Controllers
where TUpdateRequest : IAutomappedAttribute<TType, TUpdateRequest>, new()
where TUpdateResponse : IAutomappedAttribute<TType, TUpdateResponse>, new()
{
protected readonly AppDbContext db;
protected readonly IServiceProvider providers;
public AutoController(AppDbContext appDb, IServiceProvider providers) : base()
public AutoController(CommonDbContext appDb, IServiceProvider providers) : base(appDb, providers)
{
db = appDb;
this.providers = providers;
}
protected abstract IQueryable<TType> GetQuery(TOwner comp);
protected virtual TType AssociateWithParent(TType entity, TOwner comp)
{
if (typeof(TOwner) != typeof(Company))
{
throw new NotImplementedException("Special parent association not implemented");
}
var props = typeof(TType).GetProperty("Company");
var props = typeof(TType).GetProperty(HelperMainName);
if (props == null)
{
//not implemented
@ -75,26 +59,6 @@ namespace NejCommon.Controllers
props.SetValue(entity, comp);
return entity;
}
protected virtual IQueryable<TType> ApplyDefaultOrdering(IQueryable<TType> query)
{
return query;
}
/// <summary>
/// Get all the <typeparamref name="TType"/>
/// </summary>
/// <response code="200">Success</response>
/// <response code="0">There was an error</response>
[HttpGet]
public virtual async Task<Results<BadRequest<Error>, Ok<PaginationResponse<TGetAllResponse>>>> GetAll(TOwner company, [FromQuery] Pagination pag)
{
var data = await ApplyDefaultOrdering(GetQuery(company)).AsNoTrackingWithIdentityResolution().ApplyPaginationRes<TType, TGetAllResponse>(providers, pag);
//Console.Writeline(data.Data);
return TypedResults.Ok(data);
}
/// <summary>
/// Creates the <typeparamref name="TType"/>
@ -103,7 +67,7 @@ namespace NejCommon.Controllers
/// <response code="201">Success</response>
/// <response code="0">There was an error</response>
[HttpPost]
public virtual async Task<Results<BadRequest<Error>, CreatedAtRoute<TCreateResponse>>> Create(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>();
@ -116,20 +80,6 @@ namespace NejCommon.Controllers
return await db.ApiSaveChangesAsyncCreate<TType, TCreateResponse>(providers, entity);
}
/// <summary>
/// Gets the <typeparamref name="TType"/>
/// </summary>
/// <param name="entity"></param>
/// <response code="200">Success</response>
/// <response code="0">There was an error</response>
[HttpGet]
[Route("{id}/")]
public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices][ModelBinder(Name = "id")] TType entity)
{
var dat = new TGetResponse().ApplyFrom(providers, entity);
return TypedResults.Ok(dat);
}
/// <summary>
/// Delete company.
/// </summary>
@ -155,10 +105,7 @@ namespace NejCommon.Controllers
[HttpPut]
[Route("{id}/")]
public virtual async Task<Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>> Update([FromServices][ModelBinder(Name = "id")] TType entity, [FromBody] TUpdateRequest body)
{/*
if(entity is InvoiceBase inv){
//Console.Writeline(inv.InvoiceItems.Count);
}*/
{
body.ApplyTo(providers, entity);
var dat = new TUpdateResponse().ApplyFrom(providers, entity);
@ -170,4 +117,55 @@ namespace NejCommon.Controllers
return (Results<NotFound, BadRequest<Error>, Ok<TUpdateResponse>>)ctor.Invoke(new object[] { res.Result });
}
}
[ApiController]
public abstract class AutoGetterController<TOwner, TType, TGetAllResponse, TGetResponse> : ControllerBase
where TType : class, new()
where TGetAllResponse : IAutomappedAttribute<TType, TGetAllResponse>, new()
where TGetResponse : IAutomappedAttribute<TType, TGetResponse>, new()
{
protected readonly CommonDbContext db;
protected readonly IServiceProvider providers;
public AutoGetterController(CommonDbContext appDb, IServiceProvider providers) : base()
{
db = appDb;
this.providers = providers;
}
protected abstract IQueryable<TType> GetQuery(TOwner comp);
protected virtual IQueryable<TType> ApplyDefaultOrdering(IQueryable<TType> query)
{
return query;
}
/// <summary>
/// Get all the <typeparamref name="TType"/>
/// </summary>
/// <response code="200">Success</response>
/// <response code="0">There was an error</response>
[HttpGet]
public virtual async Task<Results<BadRequest<Error>, Ok<PaginationResponse<TGetAllResponse>>>> GetAll([FromServices] TOwner company, [FromQuery] Pagination pag)
{
var data = await ApplyDefaultOrdering(GetQuery(company)).AsNoTrackingWithIdentityResolution().ApplyPaginationRes<TType, TGetAllResponse>(providers, pag);
//Console.Writeline(data.Data);
return TypedResults.Ok(data);
}
/// <summary>
/// Gets the <typeparamref name="TType"/>
/// </summary>
/// <param name="entity"></param>
/// <response code="200">Success</response>
/// <response code="0">There was an error</response>
[HttpGet]
[Route("{id}/")]
public virtual async Task<Results<NotFound, Ok<TGetResponse>>> Get([FromServices][ModelBinder(Name = "id")] TType entity)
{
var dat = new TGetResponse().ApplyFrom(providers, entity);
return TypedResults.Ok(dat);
}
}
}

View File

@ -1,11 +1,10 @@
using BlazorTemplater;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Mvc;
using NejAccountingAPI.Services.Email;
using PuppeteerSharp;
using NejCommon.Services.Email;
using System.Runtime.CompilerServices;
namespace NejAccountingAPI.Emails
namespace NejCommon.Emails
{
public abstract class EmailBase<T1, T2> : ComponentBase where T1 : EmailBase<T1, T2>
{

View File

@ -1,4 +1,4 @@
@namespace NejAccountingAPI.Emails
@namespace NejCommon.Emails
@inherits EmailBase<GeneralMessage, GeneralMessage.GeneralMessageData>

View File

@ -1 +1 @@
@namespace NejAccountingAPI.Emails
@namespace NejCommon.Emails

View File

@ -1,5 +1,5 @@
/*
! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com
*/
/*
@ -31,9 +31,12 @@
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html {
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
@ -43,10 +46,14 @@ html {
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
@ -118,8 +125,10 @@ strong {
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
@ -128,8 +137,12 @@ samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-size: 1em;
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
@ -188,12 +201,18 @@ select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
@ -217,9 +236,9 @@ select {
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
@ -338,6 +357,14 @@ menu {
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
@ -433,6 +460,9 @@ video {
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
@ -464,6 +494,10 @@ video {
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::backdrop {
@ -480,6 +514,9 @@ video {
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
@ -511,234 +548,28 @@ video {
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
.visible {
visibility: visible;
}
.static {
position: static;
}
.ml-2 {
margin-left: 0.5rem;
}
.inline {
display: inline;
}
.flex {
display: flex;
}
.table {
display: table;
}
.h-full {
height: 100%;
}
.h-16 {
height: 4rem;
}
.h-48 {
height: 12rem;
}
.w-full {
width: 100%;
}
.w-16 {
width: 4rem;
}
.w-48 {
width: 12rem;
}
.flex-grow {
flex-grow: 1;
}
.flex-col {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.items-end {
align-items: flex-end;
}
.items-center {
align-items: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.self-end {
align-self: flex-end;
}
.rounded-md {
border-radius: 0.375rem;
}
.rounded {
border-radius: 0.25rem;
}
.border-4 {
border-width: 4px;
}
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(var(--primary) / var(--tw-bg-opacity));
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.pt-2 {
padding-top: 0.5rem;
}
.pb-2 {
padding-bottom: 0.5rem;
}
.pl-8 {
padding-left: 2rem;
}
.pr-8 {
padding-right: 2rem;
}
.pr-4 {
padding-right: 1rem;
}
.pt-1 {
padding-top: 0.25rem;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-semibold {
font-weight: 600;
}
.font-bold {
font-weight: 700;
}
.text-secondary {
--tw-text-opacity: 1;
color: rgb(var(--secondary-text) / var(--tw-text-opacity));
}
.text-primary {
--tw-text-opacity: 1;
color: rgb(var(--primary-text) / var(--tw-text-opacity));
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

View File

@ -11,8 +11,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using NejAccountingAPI.Documents;
using NejAccountingAPI.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq.Expressions;
using System.Reflection;
@ -54,7 +52,7 @@ namespace NejCommon.Models
public abstract class EntityBinder<TEntity> : EntityBinder<TEntity, string> where TEntity : class
{
public EntityBinder(AppDbContext db, RouteValue<TEntity>[] routeValues) : base(db, routeValues)
public EntityBinder(DbContext db, RouteValue<TEntity>[] routeValues) : base(db, routeValues)
{
}
}
@ -65,13 +63,13 @@ namespace NejCommon.Models
where TEntity : class
where TIdType : IEquatable<TIdType>
{
private readonly AppDbContext _db;
private readonly DbContext _db;
private readonly RouteValue<TEntity>[] _routeValues;
public RouteValue<TEntity>[] RouteValues => _routeValues;
public EntityBinder(AppDbContext db, RouteValue<TEntity>[] routeValues)
public EntityBinder(DbContext db, RouteValue<TEntity>[] routeValues)
{
_db = db;
_routeValues = routeValues;

145
Models/CommonDbContext.cs Normal file
View File

@ -0,0 +1,145 @@
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapPropertyHelper;
using EntityFrameworkCore.Projectables;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
namespace NejCommon.Models;
public abstract class CommonDbContext : DbContext
{
public CommonDbContext() : base()
{
}
public CommonDbContext(DbContextOptions options)
: base(options)
{
}
public async Task<bool> ApiSaveChangesAsync()
{
try
{
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
{
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) 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());
if (entity != null && Entry(entity).State == EntityState.Deleted)
{
Entry(entity).State = EntityState.Modified;
}
if (entity == null)
{
entity = await Set<T>().FirstOrDefaultAsync(predicate);
}
if (entity == null)
{
var newAppDB = Activator.CreateInstance(this.GetType()) as CommonDbContext;
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);
}
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());
//var entity = this.;
if (entity == null)
{
var newAppDB = Activator.CreateInstance(this.GetType()) as CommonDbContext;
entity = newAppDB.Set<T>().FirstOrDefault(predicate);
if (entity != null)
Attach(entity);
}
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);
return entity;
}
public void ApplyRelationships()
{
this.ChangeTracker.DetectChanges();
}
}

View File

@ -1,7 +1,7 @@
using System.Linq.Expressions;
using Microsoft.AspNetCore.Mvc;
namespace NejAccountingAPI.Models;
namespace NejCommon.Models;
public class DateOnlyFrame
{

View File

@ -1,7 +1,7 @@
using System.Linq.Expressions;
using NejCommon.Utils;
namespace NejAccountingAPI.Models;
namespace NejCommon.Models;
public class DateTimeFrame
{
public DateTimeFrame(){

View File

@ -1,8 +1,8 @@
using System.Net.Mail;
using System.Reflection;
using NejAccountingAPI.Emails;
using NejCommon.Emails;
namespace NejAccountingAPI.Services.Email;
namespace NejCommon.Services.Email;
public interface IEmailService
{

View File

@ -4,7 +4,7 @@ using MailKit.Net.Imap;
using MailKit.Net.Smtp;
using MimeKit;
namespace NejAccountingAPI.Services.Email;
namespace NejCommon.Services.Email;
public class SMTPService : IEmailService
{

View File

@ -4,7 +4,6 @@ using AutoMapPropertyHelper;
using EntityFrameworkCore.Projectables;
using EntityFrameworkCore.Projectables.Extensions;
using Microsoft.EntityFrameworkCore;
using NejAccountingAPI.Models;
using NejCommon.Controllers;
using NejCommon.Models;
@ -56,7 +55,7 @@ public static class Extensions
}
var extPar = Expression.Parameter(typeof(TType));
var binder = (TEntityBinder)typeof(TEntityBinder).GetConstructor(new[] { typeof(AppDbContext) })!.Invoke(new object?[] { null });
var binder = (TEntityBinder)typeof(TEntityBinder).GetConstructor(new[] { typeof(DbContext) })!.Invoke(new object?[] { null });
var idMatcher = binder.RouteValues.First(x => x.Primary).Expression;
var reducedIdMatcher = Expression.Lambda<Func<TType, bool>>(Expression.Invoke(idMatcher, extPar, Expression.Constant(search)).Reduce(), extPar);
//Console.Writeline(reducedIdMatcher);