mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-08-02 09:55:57 +08:00
initial commit
This commit is contained in:
33
Examples/efcore_to_freesql/DBContexts/BaseDBContext.cs
Normal file
33
Examples/efcore_to_freesql/DBContexts/BaseDBContext.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using efcore_to_freesql.Entitys;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace efcore_to_freesql.DBContexts
|
||||
{
|
||||
|
||||
public class BaseDBContext : DbContext
|
||||
{
|
||||
|
||||
public static IFreeSql Fsql { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
Fsql.CodeFirst.ConfigEntity(modelBuilder.Model); //ͬ<><CDAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
//<2F><><EFBFBD>õ<EFBFBD><C3B5><EFBFBD>
|
||||
Fsql.CodeFirst.ApplyConfiguration(new SongConfiguration());
|
||||
|
||||
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
//Fsql.CodeFirst.ApplyConfigurationsFromAssembly(typeof(SongConfiguration).Assembly);
|
||||
|
||||
Fsql.CodeFirst.SyncStructure<Song>();
|
||||
|
||||
}
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlite(@"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10");
|
||||
}
|
||||
}
|
||||
}
|
21
Examples/efcore_to_freesql/DBContexts/Topic1Context.cs
Normal file
21
Examples/efcore_to_freesql/DBContexts/Topic1Context.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using efcore_to_freesql.Entitys;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace efcore_to_freesql.DBContexts
|
||||
{
|
||||
|
||||
public class Topic1Context : BaseDBContext
|
||||
{
|
||||
|
||||
public DbSet<Topic1> Topic1s { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
||||
modelBuilder.Entity<Topic1>().ToTable("topic1_sss").HasKey(a => a.Id);
|
||||
modelBuilder.Entity<Topic1>().Property(a => a.Id).HasColumnName("topic1_id").ValueGeneratedOnAdd();
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
21
Examples/efcore_to_freesql/DBContexts/Topic2Context.cs
Normal file
21
Examples/efcore_to_freesql/DBContexts/Topic2Context.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using efcore_to_freesql.Entitys;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace efcore_to_freesql.DBContexts
|
||||
{
|
||||
|
||||
public class Topic2Context : BaseDBContext
|
||||
{
|
||||
|
||||
public DbSet<Topic2> Topic2s { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
||||
modelBuilder.Entity<Topic2>().ToTable("topic2_sss");
|
||||
modelBuilder.Entity<Topic2>().Property(a => a.Id).HasColumnName("topic2_id");
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
48
Examples/efcore_to_freesql/Entitys/Song.cs
Normal file
48
Examples/efcore_to_freesql/Entitys/Song.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace efcore_to_freesql.Entitys
|
||||
{
|
||||
public class SongType
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Song> Songs { get; set; }
|
||||
}
|
||||
|
||||
public class Song
|
||||
{
|
||||
[Column(IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Url { get; set; }
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
public int TypeId { get; set; }
|
||||
public SongType Type { get; set; }
|
||||
public List<Tag> Tags { get; set; }
|
||||
|
||||
public int Field1 { get; set; }
|
||||
public long RowVersion { get; set; }
|
||||
}
|
||||
public class Song_tag
|
||||
{
|
||||
public int Song_id { get; set; }
|
||||
public Song Song { get; set; }
|
||||
|
||||
public int Tag_id { get; set; }
|
||||
public Tag Tag { get; set; }
|
||||
}
|
||||
|
||||
public class Tag
|
||||
{
|
||||
[Column(IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Song> Songs { get; set; }
|
||||
}
|
||||
}
|
14
Examples/efcore_to_freesql/Entitys/Topic1.cs
Normal file
14
Examples/efcore_to_freesql/Entitys/Topic1.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace efcore_to_freesql.Entitys
|
||||
{
|
||||
|
||||
public class Topic1
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
}
|
18
Examples/efcore_to_freesql/Entitys/Topic2.cs
Normal file
18
Examples/efcore_to_freesql/Entitys/Topic2.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace efcore_to_freesql.Entitys
|
||||
{
|
||||
|
||||
public class Topic2
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
using efcore_to_freesql.Entitys;
|
||||
using FreeSql;
|
||||
using FreeSql.Extensions.EfCoreFluentApi;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public static class CodeFirstExtensions
|
||||
{
|
||||
|
||||
public static void ConfigEntity(this ICodeFirst codeFirst, IMutableModel efmodel)
|
||||
{
|
||||
|
||||
foreach (var type in efmodel.GetEntityTypes())
|
||||
{
|
||||
|
||||
codeFirst.ConfigEntity(type.ClrType, a =>
|
||||
{
|
||||
|
||||
//表名
|
||||
var relationalTableName = type.FindAnnotation("Relational:TableName");
|
||||
if (relationalTableName != null)
|
||||
a.Name(relationalTableName.Value?.ToString() ?? type.ClrType.Name);
|
||||
|
||||
foreach (var prop in type.GetProperties())
|
||||
{
|
||||
|
||||
var freeProp = a.Property(prop.Name);
|
||||
|
||||
//列名
|
||||
var relationalColumnName = prop.FindAnnotation("Relational:ColumnName");
|
||||
if (relationalColumnName != null)
|
||||
freeProp.Name(relationalColumnName.Value?.ToString() ?? prop.Name);
|
||||
|
||||
//主键
|
||||
freeProp.IsPrimary(prop.IsPrimaryKey());
|
||||
|
||||
//自增
|
||||
freeProp.IsIdentity(
|
||||
prop.ValueGenerated == ValueGenerated.Never ||
|
||||
prop.ValueGenerated == ValueGenerated.OnAdd ||
|
||||
prop.GetAnnotations().Where(z =>
|
||||
z.Name == "SqlServer:ValueGenerationStrategy" && z.Value.ToString().Contains("IdentityColumn") //sqlserver 自增
|
||||
|| z.Value.ToString().Contains("IdentityColumn") //其他数据库实现未经测试
|
||||
).Any()
|
||||
);
|
||||
|
||||
//可空
|
||||
freeProp.IsNullable(prop.GetAfterSaveBehavior() != PropertySaveBehavior.Throw);
|
||||
|
||||
//类型
|
||||
var relationalColumnType = prop.FindAnnotation("Relational:ColumnType");
|
||||
if (relationalColumnType != null)
|
||||
{
|
||||
|
||||
var dbType = relationalColumnType.ToString();
|
||||
if (!string.IsNullOrEmpty(dbType))
|
||||
{
|
||||
|
||||
var maxLength = prop.FindAnnotation("MaxLength");
|
||||
if (maxLength != null)
|
||||
dbType += $"({maxLength})";
|
||||
|
||||
freeProp.DbType(dbType);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void EfCoreFluentApiTestGeneric(this ICodeFirst cf)
|
||||
{
|
||||
cf.Entity<Song>(eb =>
|
||||
{
|
||||
eb.ToTable("tb_song1");
|
||||
eb.Ignore(a => a.Field1);
|
||||
eb.Property(a => a.Title).HasColumnType("varchar(50)").IsRequired();
|
||||
eb.Property(a => a.Url).HasMaxLength(100);
|
||||
|
||||
eb.Property(a => a.RowVersion).IsRowVersion();
|
||||
eb.Property(a => a.CreateTime).HasDefaultValueSql("current_timestamp");
|
||||
|
||||
eb.HasKey(a => a.Id);
|
||||
eb.HasIndex(a => a.Title).IsUnique().HasName("idx_tb_song1111");
|
||||
|
||||
//一对多、多对一
|
||||
eb.HasOne(a => a.Type).HasForeignKey(a => a.TypeId).WithMany(a => a.Songs);
|
||||
|
||||
//多对多
|
||||
eb.HasMany(a => a.Tags).WithMany(a => a.Songs, typeof(Song_tag));
|
||||
});
|
||||
cf.Entity<SongType>(eb =>
|
||||
{
|
||||
eb.ToTable("tb_songtype1");
|
||||
eb.HasMany(a => a.Songs).WithOne(a => a.Type).HasForeignKey(a => a.TypeId);
|
||||
|
||||
eb.HasData(new[]
|
||||
{
|
||||
new SongType
|
||||
{
|
||||
Id = 1,
|
||||
Name = "流行",
|
||||
Songs = new List<Song>(new[]
|
||||
{
|
||||
new Song{ Title = "真的爱你" },
|
||||
new Song{ Title = "爱你一万年" },
|
||||
})
|
||||
},
|
||||
new SongType
|
||||
{
|
||||
Id = 2,
|
||||
Name = "乡村",
|
||||
Songs = new List<Song>(new[]
|
||||
{
|
||||
new Song{ Title = "乡里乡亲" },
|
||||
})
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
cf.SyncStructure<SongType>();
|
||||
cf.SyncStructure<Song>();
|
||||
}
|
||||
|
||||
public static void EfCoreFluentApiTestDynamic(this ICodeFirst cf)
|
||||
{
|
||||
cf.Entity(typeof(Song), eb =>
|
||||
{
|
||||
eb.ToTable("tb_song2");
|
||||
eb.Ignore("Field1");
|
||||
eb.Property("Title").HasColumnType("varchar(50)").IsRequired();
|
||||
eb.Property("Url").HasMaxLength(100);
|
||||
|
||||
eb.Property("RowVersion").IsRowVersion();
|
||||
eb.Property("CreateTime").HasDefaultValueSql("current_timestamp");
|
||||
|
||||
eb.HasKey("Id");
|
||||
eb.HasIndex("Title").IsUnique().HasName("idx_tb_song2222");
|
||||
|
||||
//一对多、多对一
|
||||
eb.HasOne("Type").HasForeignKey("TypeId").WithMany("Songs");
|
||||
|
||||
//多对多
|
||||
eb.HasMany("Tags").WithMany("Songs", typeof(Song_tag));
|
||||
});
|
||||
cf.Entity(typeof(SongType), eb =>
|
||||
{
|
||||
eb.ToTable("tb_songtype2");
|
||||
eb.HasMany("Songs").WithOne("Type").HasForeignKey("TypeId");
|
||||
|
||||
eb.HasData(new[]
|
||||
{
|
||||
new SongType
|
||||
{
|
||||
Id = 1,
|
||||
Name = "流行",
|
||||
Songs = new List<Song>(new[]
|
||||
{
|
||||
new Song{ Title = "真的爱你" },
|
||||
new Song{ Title = "爱你一万年" },
|
||||
})
|
||||
},
|
||||
new SongType
|
||||
{
|
||||
Id = 2,
|
||||
Name = "乡村",
|
||||
Songs = new List<Song>(new[]
|
||||
{
|
||||
new Song{ Title = "乡里乡亲" },
|
||||
})
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
cf.SyncStructure<SongType>();
|
||||
cf.SyncStructure<Song>();
|
||||
}
|
||||
}
|
||||
|
||||
public class SongConfiguration : FreeSql.Extensions.EfCoreFluentApi.IEntityTypeConfiguration<Song>
|
||||
{
|
||||
public void Configure(EfCoreTableFluent<Song> eb)
|
||||
{
|
||||
eb.ToTable("tb_song_config");
|
||||
eb.Ignore(a => a.Field1);
|
||||
eb.Property(a => a.Title).HasColumnType("varchar(50)").IsRequired();
|
||||
eb.Property(a => a.Url).HasMaxLength(100);
|
||||
|
||||
eb.Property(a => a.RowVersion).IsRowVersion();
|
||||
eb.Property(a => a.CreateTime).HasDefaultValueSql("current_timestamp");
|
||||
|
||||
eb.HasKey(a => a.Id);
|
||||
eb.HasIndex(a => a.Title).IsUnique().HasName("idx_tb_song1111");
|
||||
|
||||
//一对多、多对一
|
||||
eb.HasOne(a => a.Type).HasForeignKey(a => a.TypeId).WithMany(a => a.Songs);
|
||||
|
||||
//多对多
|
||||
eb.HasMany(a => a.Tags).WithMany(a => a.Songs, typeof(Song_tag));
|
||||
}
|
||||
}
|
24
Examples/efcore_to_freesql/Program.cs
Normal file
24
Examples/efcore_to_freesql/Program.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace efcore_to_freesql
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
30
Examples/efcore_to_freesql/Properties/launchSettings.json
Normal file
30
Examples/efcore_to_freesql/Properties/launchSettings.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:58143",
|
||||
"sslPort": 44349
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"efcore_to_freesql": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
Examples/efcore_to_freesql/Startup.cs
Normal file
87
Examples/efcore_to_freesql/Startup.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using efcore_to_freesql.DBContexts;
|
||||
using efcore_to_freesql.Entitys;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace efcore_to_freesql
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
|
||||
{
|
||||
Configuration = configuration;
|
||||
|
||||
Fsql = new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10")
|
||||
.UseAutoSyncStructure(true)
|
||||
.Build();
|
||||
|
||||
//Fsql.CodeFirst.EfCoreFluentApiTestGeneric();
|
||||
//Fsql.CodeFirst.EfCoreFluentApiTestDynamic();
|
||||
|
||||
BaseDBContext.Fsql = Fsql;
|
||||
|
||||
var sql11 = Fsql.Select<Topic1>().ToSql();
|
||||
//SELECT a."Id", a."Title", a."CreateTime" FROM "Topic1" a
|
||||
var sql12 = Fsql.Insert<Topic1>().AppendData(new Topic1()).ToSql();
|
||||
//INSERT INTO "Topic1"("Id", "Title", "CreateTime") VALUES(@Id0, @Title0, @CreateTime0)
|
||||
|
||||
var sql21 = Fsql.Select<Topic2>().ToSql();
|
||||
//SELECT a."Id", a."Title", a."CreateTime" FROM "Topic2" a
|
||||
var sql22 = Fsql.Insert<Topic2>().AppendData(new Topic2()).ToSql();
|
||||
//INSERT INTO "Topic2"("Id", "Title", "CreateTime") VALUES(@Id0, @Title0, @CreateTime0)
|
||||
|
||||
using (var db = new Topic1Context())
|
||||
{
|
||||
db.Topic1s.Add(new Topic1());
|
||||
}
|
||||
using (var db = new Topic2Context())
|
||||
{
|
||||
db.Topic2s.Add(new Topic2());
|
||||
}
|
||||
|
||||
var sql13 = Fsql.Select<Topic1>().ToSql();
|
||||
//SELECT a."topic1_id", a."Title", a."CreateTime" FROM "topic1_sss" a
|
||||
var sql14 = Fsql.Insert<Topic1>().AppendData(new Topic1()).ToSql();
|
||||
//INSERT INTO "topic1_sss"("Title", "CreateTime") VALUES(@Title0, @CreateTime0)
|
||||
|
||||
var sql23 = Fsql.Select<Topic2>().ToSql();
|
||||
//SELECT a."topic2_id", a."Title", a."CreateTime" FROM "topic2_sss" a
|
||||
var sql24 = Fsql.Insert<Topic2>().AppendData(new Topic2()).ToSql();
|
||||
//INSERT INTO "topic2_sss"("Title", "CreateTime") VALUES(@Title0, @CreateTime0)
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
public IFreeSql Fsql { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllersWithViews();
|
||||
services.AddSingleton<IFreeSql>(Fsql);
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
Console.OutputEncoding = Encoding.GetEncoding("GB2312");
|
||||
Console.InputEncoding = Encoding.GetEncoding("GB2312");
|
||||
|
||||
app.UseHttpMethodOverride(new HttpMethodOverrideOptions { FormFieldName = "X-Http-Method-Override" });
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(a => a.MapControllers());
|
||||
}
|
||||
}
|
||||
}
|
9
Examples/efcore_to_freesql/appsettings.Development.json
Normal file
9
Examples/efcore_to_freesql/appsettings.Development.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
8
Examples/efcore_to_freesql/appsettings.json
Normal file
8
Examples/efcore_to_freesql/appsettings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
18
Examples/efcore_to_freesql/efcore_to_freesql.csproj
Normal file
18
Examples/efcore_to_freesql/efcore_to_freesql.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
|
||||
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Reference in New Issue
Block a user