2021-11-15 14:56:11 +05:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.EntityFrameworkCore.Metadata;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace AsbCloudDb
|
|
|
|
|
{
|
|
|
|
|
public static class EFExtentions
|
|
|
|
|
{
|
|
|
|
|
static Dictionary<Type, IQueryStringFactory> QueryFactories { get; set; } = new Dictionary<Type, IQueryStringFactory>();
|
|
|
|
|
|
2021-11-15 16:52:12 +05:00
|
|
|
|
static IQueryStringFactory GetQueryStringFactory<T>(DbSet<T> dbset)
|
2021-11-15 14:56:11 +05:00
|
|
|
|
where T : class
|
|
|
|
|
{
|
|
|
|
|
var t = typeof(T);
|
|
|
|
|
QueryStringFactory<T> factory = (QueryStringFactory<T>)QueryFactories.GetValueOrDefault(t);
|
|
|
|
|
if (factory is null)
|
|
|
|
|
{
|
|
|
|
|
factory = new QueryStringFactory<T>(dbset);
|
|
|
|
|
QueryFactories.Add(t, factory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return factory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Task<int> ExecInsertOrUpdateAsync<T>(this Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade database, DbSet<T> dbset, IEnumerable<T> items, CancellationToken token)
|
|
|
|
|
where T : class
|
|
|
|
|
{
|
2021-11-15 16:52:12 +05:00
|
|
|
|
var factory = (QueryStringFactory<T>)GetQueryStringFactory(dbset);
|
2021-11-15 14:56:11 +05:00
|
|
|
|
var query = factory.MakeInsertOrUpdateSql(items);
|
|
|
|
|
return database.ExecuteSqlRawAsync(query, token);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IQueryStringFactory{}
|
|
|
|
|
|
|
|
|
|
class QueryStringFactory<T> : IQueryStringFactory
|
|
|
|
|
where T : class
|
|
|
|
|
{
|
|
|
|
|
private readonly string pk;
|
|
|
|
|
private readonly string tableName;
|
2021-11-15 16:52:12 +05:00
|
|
|
|
private readonly string colunmsString;
|
|
|
|
|
private readonly string conflictUpdateSet;
|
|
|
|
|
private readonly IEnumerable<IClrPropertyGetter> getters;
|
2021-11-15 14:56:11 +05:00
|
|
|
|
|
|
|
|
|
public QueryStringFactory(DbSet<T> dbset)
|
|
|
|
|
{
|
|
|
|
|
var ps = dbset.EntityType.GetProperties();
|
|
|
|
|
var pkColsNames = dbset.EntityType.FindPrimaryKey()?.Properties.Select(p => p.GetColumnBaseName());
|
|
|
|
|
pk = pkColsNames is null ? string.Empty : $"({string.Join(", ", pkColsNames)})";
|
|
|
|
|
|
|
|
|
|
tableName = dbset.EntityType.GetTableName();
|
|
|
|
|
getters = ps.Select(p => p.GetGetter());
|
|
|
|
|
var colNames = ps.Select(p => $"\"{p.GetColumnBaseName()}\"");
|
|
|
|
|
colunmsString = $"({string.Join(", ", colNames)})";
|
|
|
|
|
conflictUpdateSet = string.Join(", ", colNames.Select(n => $"{n} = excluded.{n}"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string MakeInsertOrUpdateSql(IEnumerable<T> items)
|
|
|
|
|
{
|
|
|
|
|
/* EXAMPLE:
|
|
|
|
|
INSERT INTO the_table (id, column_1, column_2)
|
|
|
|
|
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
|
|
|
|
|
ON CONFLICT (id) DO UPDATE
|
|
|
|
|
SET column_1 = excluded.column_1,
|
|
|
|
|
column_2 = excluded.column_2;
|
|
|
|
|
*/
|
|
|
|
|
var sqlBuilder = new StringBuilder("INSERT INTO ", 7);
|
|
|
|
|
sqlBuilder.Append(tableName);
|
|
|
|
|
sqlBuilder.Append(colunmsString);
|
|
|
|
|
sqlBuilder.AppendLine(" VALUES ");
|
|
|
|
|
sqlBuilder.Append(MakeQueryValues(items));
|
|
|
|
|
sqlBuilder.AppendLine(" ON CONFLICT ");
|
|
|
|
|
if (string.IsNullOrEmpty(pk))
|
|
|
|
|
{
|
|
|
|
|
sqlBuilder.Append("DO NOTHING;");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sqlBuilder.Append(pk);
|
|
|
|
|
sqlBuilder.Append(" DO UPDATE SET ");
|
|
|
|
|
sqlBuilder.AppendLine(conflictUpdateSet);
|
|
|
|
|
sqlBuilder.Append(';');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sqlBuilder.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string MakeQueryValues(IEnumerable<T> items)
|
|
|
|
|
{
|
|
|
|
|
var rows = items.Select(item => MakeRow(item));
|
|
|
|
|
return string.Join(",", rows);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string MakeRow(T item)
|
|
|
|
|
{
|
|
|
|
|
var values = getters.Select(getter => FormatValue(getter.GetClrValue(item)));
|
|
|
|
|
return $"({string.Join(",", values)})";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FormatValue(object v)
|
|
|
|
|
=> v switch
|
|
|
|
|
{
|
|
|
|
|
string vStr => $"'{vStr}'",
|
|
|
|
|
DateTime vDate => $"'{FormatDateValue(vDate)}'",
|
|
|
|
|
IFormattable vFormattable=> FormatFormattableValue(vFormattable),
|
|
|
|
|
_ => System.Text.Json.JsonSerializer.Serialize(v),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static string FormatFormattableValue(IFormattable v)
|
|
|
|
|
=> v switch
|
|
|
|
|
{
|
|
|
|
|
double vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
float vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
decimal vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
int vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
short vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
uint vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
ushort vt => vt.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
_ => v.ToString(null, System.Globalization.CultureInfo.InvariantCulture),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static string FormatDateValue(DateTime vDate)
|
|
|
|
|
{
|
|
|
|
|
if (vDate.Kind == DateTimeKind.Unspecified)
|
|
|
|
|
vDate = DateTime.SpecifyKind(vDate, DateTimeKind.Utc);
|
|
|
|
|
return vDate.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.ffffff zzz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|