< Summary

Class:WinSolutions.Sveta.Server.Data.DataLoading.BaseRecordsLoader`2
Assembly:WinSolutions.Sveta.Server
File(s):/opt/dev/sveta_api_build/WinSolutions.Sveta.Server/Data/DataLoading/BaseRecordsLoader.cs
Covered lines:0
Uncovered lines:254
Coverable lines:254
Total lines:427
Line coverage:0% (0 of 254)
Covered branches:0
Total branches:98
Branch coverage:0% (0 of 98)

Metrics

MethodLine coverage Branch coverage
.ctor(...)0%100%
GetTemplateHeader(...)0%0%
get_BeforeAddRecordAction()0%100%
GetTemplate(...)0%0%
GetTemplateInternal(...)0%0%
SaveChanges(...)0%100%
Load(...)0%0%
AfterLoad()0%100%
Compare(...)0%0%
Rollback(...)0%0%
Commit(...)0%0%
SetState(...)0%0%
AddNew(...)0%0%

File(s)

/opt/dev/sveta_api_build/WinSolutions.Sveta.Server/Data/DataLoading/BaseRecordsLoader.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Text;
 5using WinSolutions.Sveta.Server.Data.DataModel.Contexts;
 6using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 7using WinSolutions.Sveta.Server.Data.DataLoading.Parsers;
 8using System.Linq;
 9using WinSolutions.Sveta.Server.Data.DataLoading.Records;
 10using System.Diagnostics.CodeAnalysis;
 11using System.Globalization;
 12using ClosedXML.Excel;
 13using WinSolutions.Sveta.Common;
 14
 15namespace WinSolutions.Sveta.Server.Data.DataLoading
 16{
 17    public class LoaderException:Exception
 18    {
 19        public LoaderException(string msg):base(msg)
 20        {
 21        }
 22    }
 23
 24    public class LoadingResult
 25    {
 26        public long UploadId{get;set;}
 27
 28        public List<string> TemplateHeader { get; set; }
 29
 30        public IEnumerable<ParsingError> Errors{get;set;}
 31
 32        public int AffectedCount {get;set;}
 33
 34        public byte[] GetResultFile()
 35        {
 36            var result = new List<string[]>();
 37
 38            if (TemplateHeader != null && TemplateHeader.Any())
 39            {
 40                var line = new List<string>(TemplateHeader[0].Split(';')) { "" };
 41                result.Add(line.ToArray());
 42
 43                line = new List<string>(TemplateHeader[1].Split(';')) { "Ошибка" };
 44                result.Add(line.ToArray());
 45
 46                line = new List<string>(TemplateHeader[2].Split(';')) { "Error" };
 47                result.Add(line.ToArray());
 48            }
 49
 50            if (Errors != null)
 51            {
 52                foreach (var e in Errors)
 53                {
 54                    var line = new List<string>(e.Line) { e.Exception.Message };
 55                    result.Add(line.ToArray());
 56                }
 57            }
 58
 59            using (MemoryStream ms = new MemoryStream())
 60            {
 61                CsvUtil.ToExcelStream(result).CopyTo(ms);
 62                return ms.ToArray();
 63            }
 64        }
 65    }
 66
 67    public abstract class BaseRecordsLoader<TParser,TRecord>: IDataLoader  where TParser: IParser, new() where TRecord: 
 68    {
 69        long currentUserId;
 70
 071        public BaseRecordsLoader(long currentUserId)
 072        {
 073            this.currentUserId = currentUserId;
 074        }
 75
 76        public abstract string GetTemplateDescription();
 77
 78        public List<string> GetTemplateHeader(bool csvFormat)
 079        {
 080            var result = new List<string>();
 081            result.Add(GetTemplateDescription());
 82
 083            var map = new TRecord().GetMapping().ToList();
 84
 085            var line = "";
 086            for (int i = 0; i < map.Count; i++)
 087            {
 088                line += (csvFormat ? $"\"{map[i].NameRus}\"" : map[i].NameRus);
 089                if (i < map.Count - 1)
 090                {
 091                    line += ";";
 092                }
 093            }
 094            result.Add(line);
 95
 096            line = "";
 097            for (int i = 0; i < map.Count; i++)
 098            {
 099                line += (csvFormat ? $"\"{map[i].PropertyInfo.Name}\"" : map[i].PropertyInfo.Name);
 0100                if (i < map.Count - 1)
 0101                {
 0102                    line += ";";
 0103                }
 0104            }
 0105            result.Add(line);
 106
 0107            return result;
 0108        }
 109
 0110        public Action<BaseRecord> BeforeAddRecordAction {get;set;}
 111
 112        public void GetTemplate(SvetaDbContext context, Stream stream, bool csvFormat = true)
 0113        {
 0114            var template = GetTemplateInternal(csvFormat);
 115
 0116            if (csvFormat)
 0117            {
 0118                using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true))
 0119                {
 0120                    template.ForEach(x => writer.WriteLine(string.Join(';', x)));
 0121                }
 0122            }
 123            else
 0124            {
 0125                using (var book = CsvUtil.ToExcel(template))
 0126                {
 0127                    book.SaveAs(stream);
 0128                }
 0129            }
 0130        }
 131
 132        List<string[]> GetTemplateInternal(bool csvFormat)
 0133        {
 0134            var map = new TRecord().GetMapping().ToList();
 0135            var writer = new List<string[]>();
 136
 0137            GetTemplateHeader(csvFormat).ForEach(x =>
 0138            {
 0139                writer.Add(x.Split(';'));
 0140            });
 141
 0142            var nf = new NumberFormatInfo();
 0143            var rnd = new Random();
 0144            for (int x = 0; x < 4; x++)
 0145            {
 0146                var line = new List<string>();
 0147                for (int i = 0; i < map.Count; i++)
 0148                {
 0149                    var val = String.Format(nf, map[i].SampleData, rnd.Next(0, 1000));
 150
 0151                    if (csvFormat && map[i].PropertyInfo.PropertyType == typeof(string))
 0152                    {
 0153                        val = $"\"{val}\"";
 0154                    }
 155
 0156                    line.Add(val);
 0157                }
 158
 0159                writer.Add(line.ToArray());
 0160            }
 161
 0162            return writer;
 0163        }
 164
 165        protected int SaveChanges(SvetaDbContext context)
 0166        {
 0167            var task = context.SaveChangesAsync(currentUserId);
 0168            task.Wait();
 0169            return task.Result;
 0170        }
 171
 0172        List<BaseRecord> added = null;
 173        public LoadingResult Load(SvetaDbContext context, Stream stream)
 0174        {
 0175            var upload = new Upload
 0176            {
 0177                Items = new List<UploadItem>(),
 0178                SourceFileName = stream is FileStream ? (stream as FileStream).Name : null
 0179            };
 180
 0181            LoadingResult res = new LoadingResult();
 182
 183            try
 0184            {
 0185                added = new List<BaseRecord>();
 186
 187
 188
 0189                var parser = new TParser();
 0190                var list = parser.Parse(stream).ToList();
 191
 0192                var list2 = list.Where(x =>
 0193                {
 0194                    if (x is IExternalRecord r)
 0195                        return r.Exception == null;
 0196
 0197                    return true;
 0198                }).ToList();
 199
 0200                var res2 = Load(context, list2);
 0201                var pl = res2.ToList();
 202
 203
 204
 0205                if(pl.Any())
 0206                    res.AffectedCount =  SaveChanges(context);
 207
 208
 0209                res.Errors =  list.Where(x =>
 0210                {
 0211                    if(x is IExternalRecord r)
 0212                        return r.Exception != null;
 0213
 0214                    return true;
 0215                })
 0216                .Cast<IExternalRecord>()
 0217                .Select(x => new ParsingError
 0218                {
 0219                    LineNumber = x.LineNumber,
 0220                    Line = x.Line,
 0221                    Exception = x.Exception
 0222                });
 223
 0224                res.TemplateHeader = GetTemplateHeader(false);
 225
 0226                upload.Items = new List<UploadItem>
 0227                (
 0228                    added.Select(x => new UploadItem
 0229                    {
 0230                        EndityId = x.Id,
 0231                        EntityName = x.GetType().Name
 0232                    })
 0233                );
 234
 0235                upload.ResultFile = res.GetResultFile();
 0236                upload.UploadedRecordCount = res.AffectedCount;
 0237            }
 0238            catch(Exception ex)
 0239            {
 0240                res.Errors = new List<ParsingError>(new []{new ParsingError{Exception = ex} });
 0241                var lines = new List<string[]>();
 0242                lines.Add(new string[] { "Ошибка загрузки. Обратитесь к администратору системы, приложив данный файл с о
 0243                lines.Add(new string[] { ex.Message });
 0244                lines.Add(new string[] { ex.StackTrace });
 0245                var ms = new MemoryStream();
 0246                CsvUtil.ToExcelStream(lines).CopyTo(ms);
 0247                upload.ResultFile = ms.ToArray();
 0248            }
 249
 0250            context.Uploads.Add(upload);
 0251            SaveChanges(context);
 0252            res.UploadId = upload.Id;
 253
 0254            AfterLoad();
 0255            SaveChanges(context);
 256
 0257            return res;
 0258        }
 259
 260        protected virtual void AfterLoad()
 0261        {
 262
 0263        }
 264
 265        class TypeComparer : IComparer<Type>
 266        {
 267            public int Compare([AllowNull] Type x, [AllowNull] Type y)
 0268            {
 0269                var px = x.GetProperties().Where(x => x.PropertyType.IsSubclassOf(typeof(BaseRecord))).ToList();
 0270                var py = y.GetProperties().Where(x => x.PropertyType.IsSubclassOf(typeof(BaseRecord))).ToList();
 271
 0272                if(px.Count == 0 && py.Count == 0)
 0273                    return 0;
 274
 0275                foreach (var p in px)
 0276                {
 0277                    if(p.PropertyType == y || p.PropertyType.IsSubclassOf(y))
 0278                        return 1;
 0279                }
 280
 0281                foreach (var p in py)
 0282                {
 0283                    if(p.PropertyType == x || p.PropertyType.IsSubclassOf(x))
 0284                        return -1;
 0285                }
 286
 0287                return 0;
 0288            }
 289        }
 290
 291        public void Rollback(SvetaDbContext context,long uploadId)
 0292        {
 0293            if(!context.Uploads.Any(x => x.Id == uploadId))
 0294                throw new LoaderException($"Загрузка с Id = {uploadId} не найдена.");
 295
 296
 0297            var status = context.Uploads.Where(x => x.Id == uploadId)
 0298                .Select(x => x.Status)
 0299                .FirstOrDefault();
 300
 0301            if(status == UploadStatus.Cancelled)
 0302                throw new LoaderException($"Загрузка с Id = {uploadId} уже отменена.");
 303
 0304            var types = new Dictionary<string,Type>();
 305
 306
 0307            var added = context.UploadItems
 0308                .Where(x => x.UploadId == uploadId)
 0309                .OrderBy(x => x.Id)
 0310                .ToList();
 311
 0312            foreach (var typeName in added.GroupBy(x => x.EntityName).Select(x => x.Key).ToList())
 0313            {
 0314                types[typeName] = Type.GetType($"WinSolutions.Sveta.Server.Data.DataModel.Entities.{typeName}",true);
 0315            }
 316
 317
 0318            var typeOrder = types.OrderBy(x => x.Value,new TypeComparer()).ToList();
 0319            typeOrder.Reverse();
 320
 0321            foreach (var t in typeOrder)
 0322            {
 0323                foreach (var r in added.Where(x => x.EntityName == t.Key).ToList())
 0324                {
 325
 0326                    var rec = (BaseRecord)context.Find(t.Value,r.EndityId);
 327
 0328                    if(rec != null)
 0329                    {
 0330                        context.Remove(rec);
 331                        //context.Entry(rec).State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
 0332                    }
 0333                }
 334
 0335                SaveChanges(context);
 0336            }
 337
 0338            var upload = new Upload
 0339            {
 0340                Id = uploadId,
 0341                Status = UploadStatus.Cancelled
 0342            };
 343
 0344            context.Uploads.Attach(upload);
 0345            context.Entry(upload).Property(x => x.Status).IsModified = true;
 0346            SaveChanges(context);
 0347        }
 348
 349
 350        public void Commit(SvetaDbContext context,long uploadId)
 0351        {
 0352            if(!context.Uploads.Any(x => x.Id == uploadId))
 0353                throw new LoaderException($"Загрузка с Id = {uploadId} не найдена.");
 354
 355
 0356            var status = context.Uploads.Where(x => x.Id == uploadId)
 0357                .Select(x => x.Status)
 0358                .FirstOrDefault();
 359
 0360            if(status == UploadStatus.Cancelled)
 0361                throw new LoaderException($"Загрузка с Id = {uploadId} уже отменена.");
 362
 0363            if(status == UploadStatus.Commited)
 0364                throw new LoaderException($"Загрузка с Id = {uploadId} уже подтверждена.");
 365
 0366            var types = new Dictionary<string,Type>();
 367
 368
 0369            var added = context.UploadItems
 0370                .Where(x => x.UploadId == uploadId)
 0371                .OrderBy(x => x.Id)
 0372                .ToList();
 373
 0374            foreach (var typeName in added.GroupBy(x => x.EntityName).Select(x => x.Key).ToList())
 0375            {
 0376                types[typeName] = Type.GetType($"WinSolutions.Sveta.Server.Data.DataModel.Entities.{typeName}",true);
 0377            }
 378
 379
 380
 0381            var activeState = context.refRecordsState.Where(x => x.Code == "Active").Single();
 382
 0383            foreach (var r in added)
 0384            {
 0385                var type = types[r.EntityName];
 386
 0387                var rec = (BaseRecord)context.Find(type,r.EndityId);
 388
 0389                if(rec != null)
 0390                {
 0391                    rec.RecState = activeState;
 0392                }
 0393            }
 394
 0395            SaveChanges(context);
 396
 0397            var upload = new Upload
 0398            {
 0399                Id = uploadId,
 0400                Status = UploadStatus.Commited
 0401            };
 402
 0403            context.Uploads.Attach(upload);
 0404            context.Entry(upload).Property(x => x.Status).IsModified = true;
 0405            SaveChanges(context);
 0406        }
 407
 408        protected abstract IEnumerable<BaseRecord> Load(SvetaDbContext context,IEnumerable<BaseRecord> list);
 409
 0410        RecordsState inactiveState = null;
 411        void SetState(SvetaDbContext context, BaseRecord rec)
 0412        {
 0413            if(inactiveState == null)
 0414                inactiveState = context.refRecordsState.Where(x => x.Code == "Inactive").Single();
 415
 0416            rec.RecState = inactiveState;
 0417        }
 418
 419        protected virtual void AddNew(SvetaDbContext context,BaseRecord rec)
 0420        {
 0421            BeforeAddRecordAction?.Invoke(rec);
 0422            SetState(context,rec);
 0423            context.Add(rec);
 0424            added.Add(rec);
 0425        }
 426    }
 427}