前情提要
前提情要請參考[DotnetCore]泛型運用系列-Model設計篇,不過筆者還是在這邊再列一下會用到的技術觀念與套件:
- AutoMapper:運用於將檔案內容對應的物件轉成
EFCore
的物件時
- Generic Class/Method: 不想寫多個
Service
去處理多個檔案,設計成泛型形式以符合各種檔案類型
Attribute
: 透過Description Attribute
,註記其對應的Comlumn
欄位順序
- Coravel:透過該套件,將寫好的
Service
掛成排程任務
- Extension Method:有一些通用的
Method
,不要落落等塞在一個Service中,因此轉換成可以共用的Extension Method
- EFCore:資料存取用開發套件
內容
前兩篇提到基礎建設,這篇來實作商業邏輯吧,筆者在覆頌一下欲實作的細節
- 從
FTP
下載檔案:實作細節可參考[DotnetCore]FTP-下載上傳,筆者就不另外說明了
- 從下載檔案逐筆讀取資料內容:這段使用
StreamReader
即可
- 透過
Generic Method
將資料內容轉換成物件清單:上兩篇中說明過
- 最後透過
AutoMapper Profile
設定轉換成EFCore物件
- 透過
EFCore
存進資料庫中
前三項已解釋過,此篇就以最後兩項為主說明其實作細節
Mapper Profile設定及注入
上一篇裡做完前置作業,已經取得Model List
,到這邊會有疑問為什麼不直接在最終的EFCore Entity
上套上Column順序設定值就好,這樣就不用透過AutoMapper
多轉一手。
第一個原因是筆者工作環境中所使用的EFCore
模式為DBFirst
的形式,那些Entities
是透過efcore scaffold
指令產生出來的,因自動產生出來的Class就不應該再手動去編輯。
第二個原因是之所以有ViewModel
或者比較多人所稱呼的DTO
物件,有其必要性,可以自由地套上顯示層級
或者Model Binding
層級上需要處理的設定,例如若從API
接到的Model
來說,可能會透過自定義的JsonConverter
轉成特定格式,就像此系列中的實作邏輯,需透過Description Attribute
套上Column
順序設定值。
以上述兩個理由,足夠理由建立額外的ViewModel
物件,處理商業邏輯處理時的物件,跟著筆者設定對應關係吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public void ConfigureServices(IServiceCollection services) { services.AddAutoMapper(AppDomain.CurrentDomain.Load("Domain.Profiles")); }
using AutoMapper;
public class DomainJobProfile : Profile { CreateMap<ADomainModel, AEntity>(); }
|
EFCore實作
已經有Mapper
可以轉換成最終EFCore
使用的Entity
物件,EFCore
部份就只要AddRange
跟SaveChanges
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
var aEntityList= _mapper.Map<List<AEntity>>(modelList); var existsDataOrNot = _db.AEntities .Any(x => x.DataDate == aEntityList.FirstOrDefault().DataDate && x.EventCode == aEntityList.FirstOrDefault().EventCode); if (existsDataOrNot) { _logger.Error($"[DomainJobService:DoProcess] 對應資料已存在!"); return; } _db.AEntities.AddRange(aEntityList); _db.SaveChanges();
|
Service完整程式碼
到這邊實作Service
告一段落,筆者就把幾篇寫好的實作完整程式碼貼出來
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public interface IDomainJobService<T> where T : BaseModel { bool CanRunOrNot(); void DoProcess(); }
public class DomainJobService<T> : IDomainJobService<T> where T : BaseModel { private readonly IFtpService _ftpService; private readonly FtpSetting _ftpSetting; private readonly IHostEnvironment _env; private readonly DBContext _db; private readonly IMapper _mapper; private static readonly ILogger _logger = LogManager.GetCurrentClassLogger(); public DomainJobService(IFtpService ftpService , IOptions<FtpSetting> options, IHostEnvironment env , DBContext db, IMapper mapper) { _ftpService = ftpService; _ftpSetting = options.Value; _env = env; _db = db; _mapper = mapper; }
public bool CanRunOrNot() { var result = true;
return result; }
public void DoProcess() { var fileName = CustomTypeExtension.GetDescription(typeof(T)); var orderDict = CustomTypeExtension.GetPropertiesWithOrderDescription(typeof(T)); var filePath = Path.Combine(_env.ContentRootPath, "upload", "Downloads", fileName); var modelList = ObjectExtension.GetModelList<T>(filePath, Encoding.GetEncoding("Big5"), orderDict, '='); var aEntityList= _mapper.Map<List<AEntity>>(modelList); var existsDataOrNot = _db.AEntities .Any(x => x.DataDate == aEntityList.FirstOrDefault().DataDate && x.EventCode == aEntityList.FirstOrDefault().EventCode); if (existsDataOrNot) { _logger.Error($"[DomainJobService:DoProcess] 對應資料已存在!"); return; } _db.AEntities.AddRange(aEntityList); _db.SaveChanges(); } }
|
上述中CanRunOrNot()
部份要解釋一下,筆者工作環境金融業內部系統,基本上都會牽扯到營業日這件事,但營業日的定義有一定的運算邏輯,通常是撇除假日後的日期,若排程的設定若要照著營業日走,CronExpression
是無法描述的,因此筆者這邊設計是每日跑,但每日排程觸發時,會呼叫一個Sql Function
確認是否需要跑排程,然而於該Sql Function
中撰寫營業日的判斷,回傳是否當日需要跑排程。
結論
到此篇該實作的皆實作完畢,預告一下下一篇將這些實作透過Startup
中設定注入至DI Container
中,也包含Coravel
的排程設定,才能完整的讓其整個流程運作起來,那我們就下篇見了。