mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-04-22 02:32:50 +08:00
Merge branch 'master' of https://github.com/dotnetcore/FreeSql
This commit is contained in:
commit
8599f53fd1
50
FreeSql.Tests/FreeSql.Tests/Internal/UtilsTest.cs
Normal file
50
FreeSql.Tests/FreeSql.Tests/Internal/UtilsTest.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace FreeSql.Tests.Internal
|
||||||
|
{
|
||||||
|
|
||||||
|
public class UtilsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TestGetDbParamtersByObject()
|
||||||
|
{
|
||||||
|
var ps = FreeSql.Internal.Utils.
|
||||||
|
GetDbParamtersByObject<DbParameter>("select @p",
|
||||||
|
new { p = (DbParameter)new SqlParameter() { ParameterName = "p", Value = "test" } },
|
||||||
|
"@",
|
||||||
|
(name, type, value) =>
|
||||||
|
{
|
||||||
|
if (value?.Equals(DateTime.MinValue) == true) value = new DateTime(1970, 1, 1);
|
||||||
|
var ret = new SqlParameter { ParameterName = $"@{name}", Value = value };
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
Assert.Single(ps);
|
||||||
|
Assert.Equal("test", ps[0].Value);
|
||||||
|
Assert.Equal("p", ps[0].ParameterName);
|
||||||
|
Assert.Equal(typeof(SqlParameter), ps[0].GetType());
|
||||||
|
|
||||||
|
|
||||||
|
var ps2 = FreeSql.Internal.Utils.
|
||||||
|
GetDbParamtersByObject<DbParameter>("select @p",
|
||||||
|
new Dictionary<string, DbParameter> { { "p", (DbParameter)new SqlParameter() { ParameterName = "p", Value = "test" } } },
|
||||||
|
"@",
|
||||||
|
(name, type, value) =>
|
||||||
|
{
|
||||||
|
if (value?.Equals(DateTime.MinValue) == true) value = new DateTime(1970, 1, 1);
|
||||||
|
var ret = new SqlParameter { ParameterName = $"@{name}", Value = value };
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
Assert.Single(ps2);
|
||||||
|
Assert.Equal("test", ps2[0].Value);
|
||||||
|
Assert.Equal("p", ps2[0].ParameterName);
|
||||||
|
Assert.Equal(typeof(SqlParameter), ps2[0].GetType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ namespace FreeSql.Tests
|
|||||||
{
|
{
|
||||||
public static T TryTo<T>(this string that)
|
public static T TryTo<T>(this string that)
|
||||||
{
|
{
|
||||||
return (T)Internal.Utils.GetDataReaderValue(typeof(T), that);
|
return (T)FreeSql.Internal.Utils.GetDataReaderValue(typeof(T), that);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FormatDateTime()
|
public static string FormatDateTime()
|
||||||
@ -311,7 +311,7 @@ namespace FreeSql.Tests
|
|||||||
public TaskBuild Parent { get; set; }
|
public TaskBuild Parent { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void parseExp(object sender, Aop.ParseExpressionEventArgs e)
|
void parseExp(object sender, Aop.ParseExpressionEventArgs e)
|
||||||
{
|
{
|
||||||
@ -323,7 +323,7 @@ namespace FreeSql.Tests
|
|||||||
if (callExp.Method.Name == "TryTo")
|
if (callExp.Method.Name == "TryTo")
|
||||||
{
|
{
|
||||||
e.Result = Expression.Lambda(
|
e.Result = Expression.Lambda(
|
||||||
typeof(Func<>).MakeGenericType(callExp.Method.GetGenericArguments().FirstOrDefault()),
|
typeof(Func<>).MakeGenericType(callExp.Method.GetGenericArguments().FirstOrDefault()),
|
||||||
e.Expression).Compile().DynamicInvoke()?.ToString();
|
e.Expression).Compile().DynamicInvoke()?.ToString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -464,10 +464,11 @@ namespace FreeSql.Tests
|
|||||||
emoji = g.sqlserver.Select<TestGuidId>().Where(a => a.Id == testemoji.Id).First();
|
emoji = g.sqlserver.Select<TestGuidId>().Where(a => a.Id == testemoji.Id).First();
|
||||||
Assert.Equal("💐🌸💮🌹🌺🌻🌼🌷🌱🌿🍀", emoji.xxx);
|
Assert.Equal("💐🌸💮🌹🌺🌻🌼🌷🌱🌿🍀", emoji.xxx);
|
||||||
|
|
||||||
var _model = new TestUpdateModel {
|
var _model = new TestUpdateModel
|
||||||
F_EmpId = "xx11",
|
{
|
||||||
F_RoleType = TestUpdateModelEnum.x2,
|
F_EmpId = "xx11",
|
||||||
F_UseType = TestUpdateModelEnum.x3
|
F_RoleType = TestUpdateModelEnum.x2,
|
||||||
|
F_UseType = TestUpdateModelEnum.x3
|
||||||
};
|
};
|
||||||
var testsql2008 = g.sqlserver.Update<TestUpdateModel>()
|
var testsql2008 = g.sqlserver.Update<TestUpdateModel>()
|
||||||
.Where(a => a.F_EmpId == _model.F_EmpId)
|
.Where(a => a.F_EmpId == _model.F_EmpId)
|
||||||
@ -578,7 +579,7 @@ namespace FreeSql.Tests
|
|||||||
|
|
||||||
var gkjdjd = g.sqlite.Select<AuthorTest>().Where(a => a.Post.AsSelect().Count() > 0).ToList();
|
var gkjdjd = g.sqlite.Select<AuthorTest>().Where(a => a.Post.AsSelect().Count() > 0).ToList();
|
||||||
|
|
||||||
var testrunsql1 = g.mysql.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
var testrunsql1 = g.mysql.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
||||||
var testrunsql2 = g.pgsql.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
var testrunsql2 = g.pgsql.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
||||||
var testrunsql3 = g.sqlserver.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
var testrunsql3 = g.sqlserver.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
||||||
var testrunsql4 = g.oracle.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
var testrunsql4 = g.oracle.Select<TaskBuild>().Where(a => a.OptionsEntity04 > DateTime.Now.AddDays(0).ToString("yyyyMMdd").TryTo<int>()).ToSql();
|
||||||
@ -600,18 +601,18 @@ namespace FreeSql.Tests
|
|||||||
|
|
||||||
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
||||||
.UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=7")
|
.UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=7")
|
||||||
.UseNameConvert(Internal.NameConvertType.PascalCaseToUnderscoreWithLower)
|
.UseNameConvert(FreeSql.Internal.NameConvertType.PascalCaseToUnderscoreWithLower)
|
||||||
.UseNoneCommandParameter(true)
|
.UseNoneCommandParameter(true)
|
||||||
.UseAutoSyncStructure(true) //自动同步实体结构到数据库
|
.UseAutoSyncStructure(true) //自动同步实体结构到数据库
|
||||||
.UseMonitorCommand(a => Trace.WriteLine(a.CommandText))
|
.UseMonitorCommand(a => Trace.WriteLine(a.CommandText))
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var data = fsql.Select<Post>().ToList(r => new
|
var data = fsql.Select<Post>().ToList(r => new
|
||||||
{
|
{
|
||||||
Id = r.Id,
|
Id = r.Id,
|
||||||
Name = r.AuthorId.ToString(),
|
Name = r.AuthorId.ToString(),
|
||||||
AuthorName = r.Author.Name,
|
AuthorName = r.Author.Name,
|
||||||
});
|
});
|
||||||
|
|
||||||
//g.mysql.Aop.AuditValue += (s, e) =>
|
//g.mysql.Aop.AuditValue += (s, e) =>
|
||||||
//{
|
//{
|
||||||
@ -636,8 +637,8 @@ namespace FreeSql.Tests
|
|||||||
.Include(a => a.MedicalRecord)
|
.Include(a => a.MedicalRecord)
|
||||||
.ToSql();
|
.ToSql();
|
||||||
|
|
||||||
var dkdkd = g.mysql.Select<TaskBuild>().AsTable((t,old) => "TaskBuild22")
|
var dkdkd = g.mysql.Select<TaskBuild>().AsTable((t, old) => "TaskBuild22")
|
||||||
.ToList< TestDto>(a => new TestDto()
|
.ToList<TestDto>(a => new TestDto()
|
||||||
{
|
{
|
||||||
Id = a.Id,
|
Id = a.Id,
|
||||||
IsLeaf = g.mysql.Select<TaskBuild>().AsTable((t, old) => "TaskBuild22").Any(b => b.TemplatesId == a.Id)
|
IsLeaf = g.mysql.Select<TaskBuild>().AsTable((t, old) => "TaskBuild22").Any(b => b.TemplatesId == a.Id)
|
||||||
@ -645,13 +646,13 @@ namespace FreeSql.Tests
|
|||||||
|
|
||||||
|
|
||||||
var xxxkdkd = g.oracle.Select<Templates, TaskBuild>()
|
var xxxkdkd = g.oracle.Select<Templates, TaskBuild>()
|
||||||
.InnerJoin((a,b) => true)
|
.InnerJoin((a, b) => true)
|
||||||
.Where((a,b) => (DateTime.Now - a.EditTime).TotalMinutes > 100)
|
.Where((a, b) => (DateTime.Now - a.EditTime).TotalMinutes > 100)
|
||||||
.OrderBy((a,b) => g.oracle.Select<Templates>().Where(c => b.Id == c.Id2).Count())
|
.OrderBy((a, b) => g.oracle.Select<Templates>().Where(c => b.Id == c.Id2).Count())
|
||||||
.ToSql();
|
.ToSql();
|
||||||
|
|
||||||
|
|
||||||
g.oracle.Aop.SyncStructureAfter += (s, e) =>
|
|
||||||
|
g.oracle.Aop.SyncStructureAfter += (s, e) =>
|
||||||
Trace.WriteLine(e.Sql);
|
Trace.WriteLine(e.Sql);
|
||||||
|
|
||||||
g.oracle.CodeFirst.SyncStructure<Class1>();
|
g.oracle.CodeFirst.SyncStructure<Class1>();
|
||||||
@ -729,7 +730,7 @@ namespace FreeSql.Tests
|
|||||||
.ToSql(a => new NewsArticleDto
|
.ToSql(a => new NewsArticleDto
|
||||||
{
|
{
|
||||||
ArticleTitle = a.Key,
|
ArticleTitle = a.Key,
|
||||||
ChannelId = (int)a.Sum(a.Value.Item1.OptionsEntity04)
|
ChannelId = (int)a.Sum(a.Value.Item1.OptionsEntity04)
|
||||||
});
|
});
|
||||||
|
|
||||||
var testgrpsql2 = g.sqlite.Select<TaskBuild>()
|
var testgrpsql2 = g.sqlite.Select<TaskBuild>()
|
||||||
@ -1004,7 +1005,7 @@ namespace FreeSql.Tests
|
|||||||
cou = b.Count(),
|
cou = b.Count(),
|
||||||
sum = b.Sum(b.Key.yyyy),
|
sum = b.Sum(b.Key.yyyy),
|
||||||
sum2 = b.Sum(b.Value.TypeGuid)
|
sum2 = b.Sum(b.Value.TypeGuid)
|
||||||
});
|
});
|
||||||
var aggtolist22 = select
|
var aggtolist22 = select
|
||||||
.GroupBy(a => new { a.Title, yyyy = string.Concat(a.CreateTime.Year, '-', a.CreateTime.Month) })
|
.GroupBy(a => new { a.Title, yyyy = string.Concat(a.CreateTime.Year, '-', a.CreateTime.Month) })
|
||||||
.ToDictionaryAsync(b => new
|
.ToDictionaryAsync(b => new
|
||||||
|
@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
@ -118,7 +119,7 @@ namespace FreeSql.Internal
|
|||||||
var leftBt = colattr.DbType.IndexOf('(');
|
var leftBt = colattr.DbType.IndexOf('(');
|
||||||
colattr.DbType = colattr.DbType.Substring(0, leftBt).ToUpper() + colattr.DbType.Substring(leftBt);
|
colattr.DbType = colattr.DbType.Substring(0, leftBt).ToUpper() + colattr.DbType.Substring(leftBt);
|
||||||
}
|
}
|
||||||
else if(common._orm.Ado.DataType != DataType.ClickHouse)
|
else if (common._orm.Ado.DataType != DataType.ClickHouse)
|
||||||
colattr.DbType = colattr.DbType.ToUpper();
|
colattr.DbType = colattr.DbType.ToUpper();
|
||||||
|
|
||||||
if (colattrIsNull == false && colattrIsNullable == true) colattr.DbType = colattr.DbType.Replace("NOT NULL", "");
|
if (colattrIsNull == false && colattrIsNullable == true) colattr.DbType = colattr.DbType.Replace("NOT NULL", "");
|
||||||
@ -135,7 +136,7 @@ namespace FreeSql.Internal
|
|||||||
colattr.DbType = Regex.Replace(colattr.DbType, @"\bNULL\b", "").Trim() + " NOT NULL";
|
colattr.DbType = Regex.Replace(colattr.DbType, @"\bNULL\b", "").Trim() + " NOT NULL";
|
||||||
}
|
}
|
||||||
if (colattr.IsNullable == true && colattr.DbType.Contains("NOT NULL")) colattr.DbType = colattr.DbType.Replace("NOT NULL", "");
|
if (colattr.IsNullable == true && colattr.DbType.Contains("NOT NULL")) colattr.DbType = colattr.DbType.Replace("NOT NULL", "");
|
||||||
else if (colattr.IsNullable == true && !colattr.DbType.Contains("Nullable") && common._orm.Ado.DataType == DataType.ClickHouse)colattr.DbType = $"Nullable({colattr.DbType})" ;
|
else if (colattr.IsNullable == true && !colattr.DbType.Contains("Nullable") && common._orm.Ado.DataType == DataType.ClickHouse) colattr.DbType = $"Nullable({colattr.DbType})";
|
||||||
colattr.DbType = Regex.Replace(colattr.DbType, @"\([^\)]+\)", m =>
|
colattr.DbType = Regex.Replace(colattr.DbType, @"\([^\)]+\)", m =>
|
||||||
{
|
{
|
||||||
var tmpLt = Regex.Replace(m.Groups[0].Value, @"\s", "");
|
var tmpLt = Regex.Replace(m.Groups[0].Value, @"\s", "");
|
||||||
@ -1272,12 +1273,13 @@ namespace FreeSql.Internal
|
|||||||
});
|
});
|
||||||
|
|
||||||
public static T[] GetDbParamtersByObject<T>(string sql, object obj, string paramPrefix, Func<string, Type, object, T> constructorParamter)
|
public static T[] GetDbParamtersByObject<T>(string sql, object obj, string paramPrefix, Func<string, Type, object, T> constructorParamter)
|
||||||
|
where T : IDataParameter
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(sql) || obj == null) return new T[0];
|
if (string.IsNullOrEmpty(sql) || obj == null) return new T[0];
|
||||||
var isCheckSql = sql != "*";
|
var isCheckSql = sql != "*";
|
||||||
var ttype = typeof(T);
|
var ttype = typeof(T);
|
||||||
var type = obj.GetType();
|
var type = obj.GetType();
|
||||||
if (type == ttype) return new[] { (T)Convert.ChangeType(obj, type) };
|
if (ttype.IsAssignableFrom(type)) return new[] { (T)obj };
|
||||||
var ret = new List<T>();
|
var ret = new List<T>();
|
||||||
var dic = obj as IDictionary;
|
var dic = obj as IDictionary;
|
||||||
if (dic != null)
|
if (dic != null)
|
||||||
@ -1288,7 +1290,7 @@ namespace FreeSql.Internal
|
|||||||
if (isCheckSql && string.IsNullOrEmpty(paramPrefix) == false && sql.IndexOf($"{paramPrefix}{dbkey}", StringComparison.CurrentCultureIgnoreCase) == -1) continue;
|
if (isCheckSql && string.IsNullOrEmpty(paramPrefix) == false && sql.IndexOf($"{paramPrefix}{dbkey}", StringComparison.CurrentCultureIgnoreCase) == -1) continue;
|
||||||
var val = dic[key];
|
var val = dic[key];
|
||||||
var valType = val == null ? typeof(string) : val.GetType();
|
var valType = val == null ? typeof(string) : val.GetType();
|
||||||
if (valType == ttype) ret.Add((T)Convert.ChangeType(val, ttype));
|
if (ttype.IsAssignableFrom(valType)) ret.Add((T)val);
|
||||||
else ret.Add(constructorParamter(dbkey, valType, val));
|
else ret.Add(constructorParamter(dbkey, valType, val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1299,7 +1301,7 @@ namespace FreeSql.Internal
|
|||||||
{
|
{
|
||||||
if (isCheckSql && string.IsNullOrEmpty(paramPrefix) == false && sql.IndexOf($"{paramPrefix}{p.Name}", StringComparison.CurrentCultureIgnoreCase) == -1) continue;
|
if (isCheckSql && string.IsNullOrEmpty(paramPrefix) == false && sql.IndexOf($"{paramPrefix}{p.Name}", StringComparison.CurrentCultureIgnoreCase) == -1) continue;
|
||||||
var pvalue = p.GetValue(obj, null);
|
var pvalue = p.GetValue(obj, null);
|
||||||
if (p.PropertyType == ttype) ret.Add((T)Convert.ChangeType(pvalue, ttype));
|
if (ttype.IsAssignableFrom(p.PropertyType)) ret.Add((T)pvalue);
|
||||||
else ret.Add(constructorParamter(p.Name, p.PropertyType, pvalue));
|
else ret.Add(constructorParamter(p.Name, p.PropertyType, pvalue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1371,7 +1373,7 @@ namespace FreeSql.Internal
|
|||||||
this.Value = value;
|
this.Value = value;
|
||||||
this.DataIndex = dataIndex;
|
this.DataIndex = dataIndex;
|
||||||
}
|
}
|
||||||
public static ConstructorInfo Constructor = typeof(RowInfo). GetConstructor(new[] { typeof(object), typeof(int) });
|
public static ConstructorInfo Constructor = typeof(RowInfo).GetConstructor(new[] { typeof(object), typeof(int) });
|
||||||
public static PropertyInfo PropertyValue = typeof(RowInfo).GetProperty("Value");
|
public static PropertyInfo PropertyValue = typeof(RowInfo).GetProperty("Value");
|
||||||
public static PropertyInfo PropertyDataIndex = typeof(RowInfo).GetProperty("DataIndex");
|
public static PropertyInfo PropertyDataIndex = typeof(RowInfo).GetProperty("DataIndex");
|
||||||
}
|
}
|
||||||
@ -1851,7 +1853,7 @@ namespace FreeSql.Internal
|
|||||||
static MethodInfo MethodBigIntegerParse = typeof(Utils).GetMethod("ToBigInteger", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
|
static MethodInfo MethodBigIntegerParse = typeof(Utils).GetMethod("ToBigInteger", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(string) }, null);
|
||||||
static PropertyInfo PropertyDateTimeOffsetDateTime = typeof(DateTimeOffset).GetProperty("DateTime", BindingFlags.Instance | BindingFlags.Public);
|
static PropertyInfo PropertyDateTimeOffsetDateTime = typeof(DateTimeOffset).GetProperty("DateTime", BindingFlags.Instance | BindingFlags.Public);
|
||||||
static PropertyInfo PropertyDateTimeTicks = typeof(DateTime).GetProperty("Ticks", BindingFlags.Instance | BindingFlags.Public);
|
static PropertyInfo PropertyDateTimeTicks = typeof(DateTime).GetProperty("Ticks", BindingFlags.Instance | BindingFlags.Public);
|
||||||
static ConstructorInfo CtorDateTimeOffsetArgsTicks = typeof(DateTimeOffset). GetConstructor(new[] { typeof(long), typeof(TimeSpan) });
|
static ConstructorInfo CtorDateTimeOffsetArgsTicks = typeof(DateTimeOffset).GetConstructor(new[] { typeof(long), typeof(TimeSpan) });
|
||||||
static Encoding DefaultEncoding = Encoding.UTF8;
|
static Encoding DefaultEncoding = Encoding.UTF8;
|
||||||
static MethodInfo MethodEncodingGetBytes = typeof(Encoding).GetMethod("GetBytes", new[] { typeof(string) });
|
static MethodInfo MethodEncodingGetBytes = typeof(Encoding).GetMethod("GetBytes", new[] { typeof(string) });
|
||||||
static MethodInfo MethodEncodingGetString = typeof(Encoding).GetMethod("GetString", new[] { typeof(byte[]) });
|
static MethodInfo MethodEncodingGetString = typeof(Encoding).GetMethod("GetString", new[] { typeof(byte[]) });
|
||||||
@ -1870,7 +1872,7 @@ namespace FreeSql.Internal
|
|||||||
{
|
{
|
||||||
if (type.IsArray)
|
if (type.IsArray)
|
||||||
{
|
{
|
||||||
switch (type.FullName)
|
switch (type.FullName)
|
||||||
{
|
{
|
||||||
case "System.Byte[]":
|
case "System.Byte[]":
|
||||||
return Expression.IfThenElse(
|
return Expression.IfThenElse(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user