forked from ddrilling/AsbCloudServer
LastData rename to Measure. Implement MeasureService
This commit is contained in:
parent
843dd276a3
commit
9724b24d98
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace AsbCloudApp.Data
|
||||
{
|
||||
public class LastDataDto
|
||||
public class MeasureDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
@ -13,9 +13,7 @@ namespace AsbCloudApp.Data
|
||||
|
||||
public string CategoryName { get; set; }
|
||||
|
||||
public DateTime DateInsert { get; set; }
|
||||
|
||||
public DateTime DateModify { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
public Dictionary<string, object> Data { get; set; }
|
||||
}
|
@ -5,13 +5,13 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudApp.Services
|
||||
{
|
||||
public interface ILastDataService
|
||||
public interface IMeasureService
|
||||
{
|
||||
Task<Dictionary<int, string>> GetCategoriesAsync(CancellationToken token);
|
||||
Task<IEnumerable<LastDataDto>> GetHisoryAsync(int idWell, int idCategory, CancellationToken token);
|
||||
Task<IEnumerable<LastDataDto>> GetAllLastAsync(int idWell, CancellationToken token);
|
||||
Task<int> InsertAsync(int idWell, LastDataDto data, CancellationToken token);
|
||||
Task<int> UpdateAsync(int idWell, LastDataDto data, CancellationToken token);
|
||||
Task<IEnumerable<MeasureDto>> GetHisoryAsync(int idWell, int idCategory, CancellationToken token);
|
||||
Task<IEnumerable<MeasureDto>> GetAllLastAsync(int idWell, CancellationToken token);
|
||||
Task<int> InsertAsync(int idWell, MeasureDto data, CancellationToken token);
|
||||
Task<int> UpdateAsync(int idWell, MeasureDto data, CancellationToken token);
|
||||
Task<int> MarkAsDeleteAsync(int idWell, int idData, CancellationToken token);
|
||||
Task<int> DeleteAsync(int idWell, int idData, CancellationToken token);
|
||||
}
|
@ -17,22 +17,23 @@ namespace AsbCloudDb.Model
|
||||
public virtual DbSet<Company> Companies { get; set; }
|
||||
public virtual DbSet<DataSaubBase> DataSaubBases { get; set; }
|
||||
public virtual DbSet<Deposit> Deposits { get; set; }
|
||||
public virtual DbSet<User> Users { get; set; }
|
||||
public virtual DbSet<UserRole> UserRoles { get; set; }
|
||||
public virtual DbSet<Well> Wells { get; set; }
|
||||
public virtual DbSet<ReportProperty> ReportProperties { get; set; }
|
||||
public virtual DbSet<FileInfo> Files { get; set; }
|
||||
public virtual DbSet<FileCategory> FileCategories { get; set; }
|
||||
public virtual DbSet<FileInfo> Files { get; set; }
|
||||
public virtual DbSet<Measure> Measures { get; set; }
|
||||
public virtual DbSet<MeasureCategory> MeasureCategories { get; set; }
|
||||
public virtual DbSet<ReportProperty> ReportProperties { get; set; }
|
||||
public virtual DbSet<Telemetry> Telemetries { get; set; }
|
||||
public virtual DbSet<TelemetryAnalysis> TelemetryAnalysis { get; set; }
|
||||
public virtual DbSet<TelemetryEvent> TelemetryEvents { get; set; }
|
||||
public virtual DbSet<TelemetryMessage> TelemetryMessages { get; set; }
|
||||
public virtual DbSet<TelemetryUser> TelemetryUsers { get; set; }
|
||||
public virtual DbSet<WellOperationCategory> TelemetryOperations { get; set; }
|
||||
public virtual DbSet<TelemetryAnalysis> TelemetryAnalysis { get; set; }
|
||||
public virtual DbSet<WellSectionType> WellSectionTypes { get; set; }
|
||||
public virtual DbSet<User> Users { get; set; }
|
||||
public virtual DbSet<UserRole> UserRoles { get; set; }
|
||||
public virtual DbSet<Well> Wells { get; set; }
|
||||
public virtual DbSet<WellOperation> WellOperations { get; set; }
|
||||
public virtual DbSet<WellOperationCategory> TelemetryOperations { get; set; }
|
||||
public virtual DbSet<WellSectionType> WellSectionTypes { get; set; }
|
||||
public virtual DbSet<WellType> WellTypes { get; set; }
|
||||
public virtual DbSet<LastData> LastData { get; set; }
|
||||
|
||||
//var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
// .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True")
|
||||
@ -305,6 +306,14 @@ namespace AsbCloudDb.Model
|
||||
new WellType{ Id = 2, Caption = "Горизонтальная"},
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity<MeasureCategory>(entity => {
|
||||
entity.HasData(new List<MeasureCategory> {
|
||||
new MeasureCategory{ Id = 1, Name = "Показатели бурового раствора", ShortName = "Раствор"},
|
||||
new MeasureCategory{ Id = 2, Name = "Шламограмма", ShortName = "Шламограмма"},
|
||||
new MeasureCategory{ Id = 3, Name = "ННБ", ShortName = "ННБ"},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Эти данные не должны быть в релизе
|
||||
@ -337,6 +346,14 @@ namespace AsbCloudDb.Model
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Measure>(entity => {
|
||||
entity.HasData(new List<Measure> {
|
||||
new Measure{ Id = 1, IdCategory = 1, IdWell = 1, Timestamp = DateTime.Now.AddHours(1), Data = new Dictionary<string, object>{ {"a", 1 }}},
|
||||
new Measure{ Id = 2, IdCategory = 1, IdWell = 1, Timestamp = DateTime.Now.AddHours(2), Data = new Dictionary<string, object>{ {"a", 2 }}},
|
||||
new Measure{ Id = 3, IdCategory = 2, IdWell = 1, Timestamp = DateTime.Now.AddHours(3), Data = new Dictionary<string, object>{ {"a", 3 }}},
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Telemetry>(entity =>
|
||||
{
|
||||
entity.HasData(new List<Telemetry>{
|
||||
|
@ -28,7 +28,8 @@ namespace AsbCloudDb.Model
|
||||
DbSet<WellSectionType> WellSectionTypes { get; set; }
|
||||
DbSet<WellOperation> WellOperations { get; set; }
|
||||
DbSet<WellType> WellTypes { get; set; }
|
||||
DbSet<LastData> LastData { get; set; }
|
||||
DbSet<Measure> Measures { get; set; }
|
||||
DbSet<MeasureCategory> MeasureCategories { get; set; }
|
||||
|
||||
int SaveChanges();
|
||||
int SaveChanges(bool acceptAllChangesOnSuccess);
|
||||
|
@ -7,8 +7,8 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace AsbCloudDb.Model
|
||||
{
|
||||
[Table("t_last_data"), Comment("Таблица c данными для вкладки \'Последние данные\'")]
|
||||
public class LastData : IId
|
||||
[Table("t_measure"), Comment("Таблица c данными для вкладки \'Последние данные\'")]
|
||||
public class Measure : IId
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
@ -20,21 +20,22 @@ namespace AsbCloudDb.Model
|
||||
[Column("id_category"), Comment("id категории")]
|
||||
public int IdCategory { get; set; }
|
||||
|
||||
[Column("date_insert"), Comment("время добавления")]
|
||||
public DateTime DateInsert { get; set; }
|
||||
|
||||
[Column("date_modify"), Comment("время изменения")]
|
||||
public DateTime DateModify { get; set; }
|
||||
[Column("timestamp"), Comment("время добавления")]
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
[Column("data", TypeName = "jsonb"), Comment("Данные таблицы последних данных")]
|
||||
public Dictionary<string, object> Data { get; set; }
|
||||
|
||||
[Column("is_deleted"), Comment("Пометка удаленным")]
|
||||
public bool IsDeleted { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(IdWell))]
|
||||
public virtual Well Well { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(IdCategory))]
|
||||
public virtual LastDataCategory Category { get; set; }
|
||||
[InverseProperty(nameof(Model.MeasureCategory.Measures))]
|
||||
public virtual MeasureCategory Category { get; set; }
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AsbCloudDb.Model
|
||||
{
|
||||
[Table("t_last_data_category"), Comment("Категория последних данных")]
|
||||
public class LastDataCategory : IId
|
||||
[Table("t_measure_category"), Comment("Категория последних данных")]
|
||||
public class MeasureCategory : IId
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
@ -16,5 +17,8 @@ namespace AsbCloudDb.Model
|
||||
|
||||
[Column("short_name"), Comment("Короткое название категории")]
|
||||
public string ShortName { get; set; }
|
||||
|
||||
[InverseProperty(nameof(Model.Measure.Category))]
|
||||
public virtual ICollection<Measure> Measures { get; set; }
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ namespace AsbCloudInfrastructure
|
||||
services.AddTransient<IFileService, FileService>();
|
||||
services.AddTransient<IWellOperationService, WellOperationService>();
|
||||
services.AddTransient<IWellOperationsStatService, WellOperationsStatService>();
|
||||
services.AddTransient<ILastDataService, LastDataService>();
|
||||
services.AddTransient<IMeasureService, MeasureService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services
|
||||
{
|
||||
public class LastDataService : ILastDataService
|
||||
{
|
||||
private readonly IAsbCloudDbContext db;
|
||||
private readonly CacheTable<LastDataCategory> cacheCategories;
|
||||
|
||||
public LastDataService(IAsbCloudDbContext db, Cache.CacheDb cacheDb)
|
||||
{
|
||||
this.db = db;
|
||||
cacheCategories = cacheDb.GetCachedTable<LastDataCategory>((DbContext)db);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<int, string>> GetCategoriesAsync(CancellationToken token)
|
||||
{
|
||||
var entities = await cacheCategories.WhereAsync(token).ConfigureAwait(false);
|
||||
var dto = entities.ToDictionary(e=>e.Id, e=>e.Name);
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LastDataDto>> GetAllLastAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
var entities = await db.LastData
|
||||
.Include(e => e.Category)
|
||||
.Where(e => e.IdWell == idWell)
|
||||
.AsNoTracking()
|
||||
.ToListAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
var dtos = entities.Adapt<LastDataDto, LastData>((d, s) => d.CategoryName = s.Category.Name);
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LastDataDto>> GetHisoryAsync(int idWell, int idCategory, CancellationToken token)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<int> InsertAsync(int idWell, LastDataDto data, CancellationToken token)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<int> MarkAsDeleteAsync(int idWell, int idData, CancellationToken token)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<int> UpdateAsync(int idWell, LastDataDto data, CancellationToken token)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<int> DeleteAsync(int idWell, int idData, CancellationToken token)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
141
AsbCloudInfrastructure/Services/MeasureService.cs
Normal file
141
AsbCloudInfrastructure/Services/MeasureService.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudApp.Services;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AsbCloudInfrastructure.Services
|
||||
{
|
||||
public class MeasureService : IMeasureService
|
||||
{
|
||||
private readonly IAsbCloudDbContext db;
|
||||
private readonly CacheTable<MeasureCategory> cacheCategories;
|
||||
|
||||
public MeasureService(IAsbCloudDbContext db, Cache.CacheDb cacheDb)
|
||||
{
|
||||
this.db = db;
|
||||
cacheCategories = cacheDb.GetCachedTable<MeasureCategory>((DbContext)db);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<int, string>> GetCategoriesAsync(CancellationToken token)
|
||||
{
|
||||
var entities = await cacheCategories.WhereAsync(token).ConfigureAwait(false);
|
||||
var dto = entities.ToDictionary(e=>e.Id, e=>e.Name);
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MeasureDto>> GetAllLastAsync(int idWell, CancellationToken token)
|
||||
{
|
||||
var categories = await cacheCategories.WhereAsync(token).ConfigureAwait(false);
|
||||
if (!categories.Any())
|
||||
return default;
|
||||
|
||||
var queries = categories.Select(c => db.Measures
|
||||
.Where(m => m.IdWell == idWell && m.IdCategory == c.Id && !m.IsDeleted)
|
||||
.OrderByDescending(m => m.Timestamp)
|
||||
.Take(1)
|
||||
);
|
||||
|
||||
var qi = queries.GetEnumerator();
|
||||
qi.MoveNext();
|
||||
var query = qi.Current;
|
||||
while (qi.MoveNext())
|
||||
query = query.Union(qi.Current);
|
||||
|
||||
//var query = db.Measures
|
||||
// .Where(e => e.IdWell == idWell)
|
||||
// .GroupBy(e => e.IdCategory)
|
||||
// .Select(g => g.Where(e => e.Timestamp == g.Max(m => m.Timestamp)).First());
|
||||
|
||||
//var query = db.Measures
|
||||
// .Where(m => m.IdWell == idWell && m.Timestamp == db.Measures
|
||||
// .Where(subm => subm.IdWell == m.IdWell && subm.IdCategory == m.IdCategory).Max(subm => subm.Timestamp));
|
||||
|
||||
var entities = await query
|
||||
.AsNoTracking()
|
||||
.ToListAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var dtos = entities.Adapt<MeasureDto, Measure>((d, s) => d.CategoryName = s.Category.Name);
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MeasureDto>> GetHisoryAsync(int idWell, int idCategory, CancellationToken token)
|
||||
{
|
||||
var query = db.Measures.Include(m => m.Category)
|
||||
.Where(m => m.IdWell == idWell && m.IdCategory == idCategory && !m.IsDeleted);
|
||||
|
||||
var entities = await query
|
||||
.AsNoTracking()
|
||||
.ToListAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var dtos = entities.Adapt<MeasureDto, Measure>((d, s) => d.CategoryName = s.Category.Name);
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public Task<int> InsertAsync(int idWell, MeasureDto data, CancellationToken token)
|
||||
{
|
||||
if (data.IdCategory < 1)
|
||||
throw new ArgumentException("wrong idCategory", nameof(data));
|
||||
if (data.Data is null)
|
||||
throw new ArgumentException("data.data is not optional", nameof(data));
|
||||
var entity = data.Adapt<Measure>();
|
||||
entity.Id = 0;
|
||||
entity.IdWell = idWell;
|
||||
db.Measures.Add(entity);
|
||||
return db.SaveChangesAsync(token);
|
||||
}
|
||||
|
||||
public async Task<int> UpdateAsync(int idWell, MeasureDto data, CancellationToken token)
|
||||
{
|
||||
if (data.Id < 1)
|
||||
throw new ArgumentException("wrong id", nameof(data));
|
||||
if (data.IdCategory < 1)
|
||||
throw new ArgumentException("wrong idCategory", nameof(data));
|
||||
if (data.Data is null)
|
||||
throw new ArgumentException("data.data is not optional", nameof(data));
|
||||
|
||||
var entity = await db.Measures
|
||||
.Where(m => m.Id == data.Id && !m.IsDeleted)
|
||||
.FirstOrDefaultAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (entity is null)
|
||||
throw new ArgumentException("id doesn't exist", nameof(data));
|
||||
|
||||
entity.Timestamp = data.Timestamp;
|
||||
entity.Data = data.Data;
|
||||
|
||||
return await db.SaveChangesAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<int> MarkAsDeleteAsync(int idWell, int idData, CancellationToken token)
|
||||
{
|
||||
if (idData < 1)
|
||||
throw new ArgumentException("wrong id", nameof(idData));
|
||||
|
||||
var entity = await db.Measures.Where(m => m.IdWell == idWell && m.Id == idData)
|
||||
.FirstOrDefaultAsync(token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
entity.IsDeleted = true;
|
||||
|
||||
return await db.SaveChangesAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<int> DeleteAsync(int idWell, int idData, CancellationToken token)
|
||||
{
|
||||
if (idData < 1)
|
||||
throw new ArgumentException("wrong id", nameof(idData));
|
||||
db.Measures.RemoveRange(db.Measures.Where(m => m.IdWell == idWell && m.Id == idData));
|
||||
return db.SaveChangesAsync(token);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,13 +9,13 @@ namespace AsbCloudWebApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/well/{idWell}/lastData")]
|
||||
public class LastDataController: ControllerBase
|
||||
[Route("api/well/{idWell}/measure")]
|
||||
public class MeasureController: ControllerBase
|
||||
{
|
||||
private readonly ILastDataService lastDataService;
|
||||
private readonly IMeasureService lastDataService;
|
||||
private readonly IWellService wellService;
|
||||
|
||||
public LastDataController(ILastDataService lastDataService, IWellService wellService)
|
||||
public MeasureController(IMeasureService lastDataService, IWellService wellService)
|
||||
{
|
||||
this.lastDataService = lastDataService;
|
||||
this.wellService = wellService;
|
||||
@ -55,7 +55,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> InsertAsync([FromRoute] int idWell, LastDataDto data, CancellationToken token = default)
|
||||
public async Task<IActionResult> InsertAsync([FromRoute] int idWell, MeasureDto data, CancellationToken token = default)
|
||||
{
|
||||
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
||||
@ -65,7 +65,7 @@ namespace AsbCloudWebApi.Controllers
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> UpdateAsync([FromRoute] int idWell, LastDataDto data, CancellationToken token = default)
|
||||
public async Task<IActionResult> UpdateAsync([FromRoute] int idWell, MeasureDto data, CancellationToken token = default)
|
||||
{
|
||||
if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
|
||||
return Forbid();
|
@ -23,4 +23,11 @@
|
||||
<Folder Include="Connected Services\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AsbCloudApp\AsbCloudApp.csproj" />
|
||||
<ProjectReference Include="..\AsbCloudDb\AsbCloudDb.csproj" />
|
||||
<ProjectReference Include="..\AsbCloudInfrastructure\AsbCloudInfrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,10 +1,10 @@
|
||||
//using AsbCloudApp.Data;
|
||||
//using AsbCloudDb.Model;
|
||||
//using AsbCloudInfrastructure.Services;
|
||||
//using AsbCloudInfrastructure.Services.Cache;
|
||||
using AsbCloudApp.Data;
|
||||
using AsbCloudDb.Model;
|
||||
using AsbCloudInfrastructure.Services;
|
||||
using AsbCloudInfrastructure.Services.Cache;
|
||||
//using AsbSaubReport;
|
||||
//using AutoMapper;
|
||||
//using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
|
||||
namespace ConsoleApp1
|
||||
@ -15,10 +15,13 @@ namespace ConsoleApp1
|
||||
static void Main(/*string[] args*/)
|
||||
{
|
||||
|
||||
//var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
// .UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True")
|
||||
// .Options;
|
||||
//var context = new AsbCloudDbContext(options);
|
||||
var options = new DbContextOptionsBuilder<AsbCloudDbContext>()
|
||||
.UseNpgsql("Host=localhost;Database=postgres;Username=postgres;Password=q;Persist Security Info=True")
|
||||
.Options;
|
||||
var context = new AsbCloudDbContext(options);
|
||||
|
||||
var mservice = new MeasureService(context, new CacheDb());
|
||||
var r = mservice.GetAllLastAsync(1, default).Result;
|
||||
|
||||
//var idWell = 1;
|
||||
//var dataSource = new ReportDataSourcePgCloud(context, idWell);
|
||||
@ -36,7 +39,6 @@ namespace ConsoleApp1
|
||||
//var s = generator.GetPagesCount();
|
||||
//var fileName = generator.Make();
|
||||
|
||||
|
||||
Console.WriteLine("Done. Press any key to quit.");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user