[報表程式 - 4] 多個觸發點+多種資料來源 => 泛型

承上題 [報表程式 - 3] 多個觸發點+多種資料來源 => ViewModel

再假設最後我們發現每個觸發點中,連組裝資料的邏輯都完全各走各的了

所以 BaseEvent 只剩下流程控制與共用類別、參數宣告的功能

除此之外都不能共用了,都會寫在各自的 Event.cs 中

 

這個例子我會分成四種情境講解

剛好對應到寫報告程式時 最常見的四種型態

這篇是4. 兩事件 (泛型)

完整程式碼請見 https://gitlab.com/jesperlai/Report

 

故主要的變化為

1. Event 資料夾下針對每個事件各自長出自己的資料夾

2. 資料夾內放每個事件自己的資料觸發點、取資料邏輯、每回合怎麼篩選資料的邏輯
 

 

Program.cs

using Sample_4.Event;
using Sample_4.Event.MoveOut;
using Sample_4.Event.Snap;
using Sample_4.Models;
using Sample_4.ViewModels;
using System;
using System.Collections.Generic;

namespace Sample_4
{
    class Program
    {
        private static BuildEventParam MakeBuildEventParam(string eventName)
        {
            return new BuildEventParam
            {
                //為了方便講解 (通常這兩個值會從外面傳進來)---------
                STime = DateTime.Parse("2001/1/1"),
                ETime = DateTime.Parse("2001/2/1"),
                //--------------------------------------------------
                EventName = eventName,
            };
        }

        static void Main(string[] args)
        {
            var result = new List<Report>();

            //快照
            BaseEvent<Lot_Snap> snap = new Event_Snap(MakeBuildEventParam("這是一個快照事件"));
            result.AddRange(snap.Run());

            //出站
            BaseEvent<Lot_Txn> moveOut = new Event_MoveOut(MakeBuildEventParam("這是一個出站事件"));
            result.AddRange(moveOut.Run());

            //寫出檔案
            RptService myRpt = new RptService();
            myRpt.GenFile(result, @"D:\sample.csv");
        }
    }
}

 

BaseEvent.cs

using Sample_4.Models;
using Sample_4.ViewModels;
using System.Collections.Generic;

namespace Sample_4.Event
{
    public abstract class BaseEvent<T>
    {
        protected BaseDataAccessHelper<T> _helper;
        protected Repository repo;
        protected BuildEventParam builderParam;

        public BaseEvent(BuildEventParam param)
        {
            builderParam = param;
            repo = new Repository();
        }

        public List<Report> Run()
        {
            var triggerPoint = GetTriggerPoint();
            return GetData(triggerPoint);
        }

        public abstract List<T> GetTriggerPoint();

        public abstract List<Report> GetData(List<T> triggerPoints);
    }
}

 

BaseDataAccessHelper.cs

using Sample_4.ViewModels;
using System.Collections.Generic;

namespace Sample_4.Event
{
    public abstract class BaseDataAccessHelper<T>
    {
        protected Repository _repo;

        /// <summary>
        /// 通常會傳入一些參數 為了在資料庫裡撈出一些符合的結果來寫出成報表 ex: 撈某一個區間的資料
        /// </summary>
        public BaseDataAccessHelper(Repository pRepo)
        {
            _repo = pRepo;
        }

        public abstract DataSetVM ConcreteDataSet(List<T> triggerPoints);
    }
}

 

MoveOut資料夾內

0_Event_MoveOut.cs

using Sample_4.Extensions;
using Sample_4.Models;
using Sample_4.Utility;
using Sample_4.ViewModels;
using System.Collections.Generic;

namespace Sample_4.Event.MoveOut
{
    public class Event_MoveOut : BaseEvent<Lot_Txn>
    {
        public Event_MoveOut(BuildEventParam param) : base(param)
        {
            _helper = new MoveOutHelper(repo);
        }

        public override List<Lot_Txn> GetTriggerPoint()
        {
            var txns = repo.GetTxn(builderParam.STime, builderParam.ETime, "進站");

            return txns;
        }

        public override List<Report> GetData(List<Lot_Txn> triggerPoints)
        {
            //完全做自己的事 沒有共用的部份了
            var dataSet = _helper.ConcreteDataSet(triggerPoints);

            var result = new List<Report>();
            //我把Trigger Point 觸發點 縮寫成tp
            foreach (var tp in triggerPoints)
            {
                //該回合需用到的資料 我把this round 縮寫成 tRound
                var tRound = MoveOutHelper.GetThisRoundData(dataSet, tp);

                //Controller 盡量扁平 如果有複雜的運算可以放到 Utility 做
                var item = new Report
                {
                    Lot_No = tp.Lot_No,
                    Stage_Id = tp.Stage_Id,
                    Qty = tp.Qty.Trim() - 1,   //有些轉型或是常用的運算 寫成extension會比較易用
                    Po_No = tRound.So.Po_No,
                    Order_Qty = tRound.So.Qty,
                    Sod = DataUtility.GetSod(tRound.So),  //假設這個欄位有很複雜的邏輯 放到Utitlity做
                    Event_Name = builderParam.EventName,
                };

                result.Add(item);
            }

            return result;
        }
    }
}

 

1_MoveOutHelper.DataSet.cs

using Sample_4.Models;
using Sample_4.ViewModels;
using System.Collections.Generic;
using System.Linq;

namespace Sample_4.Event.MoveOut
{
    public partial class MoveOutHelper : BaseDataAccessHelper<Lot_Txn>
    {
        public MoveOutHelper(Repository pRepo) : base(pRepo)
        {
        }

        /// <summary>
        /// 其他資料都是based on 你觸發點撈出來的資料有什麼 再去補撈其他資料 
        /// 例如: 先撈出過站記錄,再透過這段時間內有哪些貨批在動 (表示要報) 再去撈出這些貨批的客戶訂單資料
        /// </summary>
        public override DataSetVM ConcreteDataSet(List<Lot_Txn> triggerPoints)
        {
            DataSetVM dataSet = new DataSetVM();

            dataSet.So = GetSo(triggerPoints);

            return dataSet;
        }

        private List<Sales_Order> GetSo(List<Lot_Txn> triggerPoints)
        {
            //通常表示你會有專屬於 MoveOut 的取 So 的條件
            var lotNos = triggerPoints.Select(q => q.Lot_No).Distinct().ToList();
            var result = _repo.GetSo(lotNos);

            return result;
        }
    }
}

 

2_MoveOutHelper.ThisRound.cs

using Sample_4.Models;
using Sample_4.ViewModels;
using System.Linq;

namespace Sample_4.Event.MoveOut
{
    public partial class MoveOutHelper : BaseDataAccessHelper<Lot_Txn>
    {
        /// <summary>
        /// 因為希望不要一直頻繁開關DB 所以前面一次把所有批號的資料都撈出來了
        /// 接下來得篩選出只屬於這回合的資料 (例如 根據這回合的過站記錄的批號 從剛剛的DataSet中找出訂單 => 不是每跑一批就查一次DB )
        /// </summary>
        public static ThisRoundDataVM GetThisRoundData(DataSetVM src, Lot_Txn tp)
        {
            ThisRoundDataVM tRound = new ThisRoundDataVM();

            tRound.So = GetSo(src, tp);

            return tRound;
        }

        private static Sales_Order GetSo(DataSetVM src, Lot_Txn tp)
        {
            var Sos = src.So.Where(q => q.Lot_No == tp.Lot_No).ToList();
            return Sos.Any() ? Sos.First() : new Sales_Order();
        }
    }
}

 

Snap資料夾內

0_Event_Snap.cs

using Sample_4.Extensions;
using Sample_4.Models;
using Sample_4.Utility;
using Sample_4.ViewModels;
using System.Collections.Generic;

namespace Sample_4.Event.Snap
{
    public class Event_Snap : BaseEvent<Lot_Snap>
    {
        public Event_Snap(BuildEventParam param) : base(param)
        {
            _helper = new SnapHelper(repo);
        }

        public override List<Lot_Snap> GetTriggerPoint()
        {
            var snaps = repo.GetSnap();

            return snaps;
        }

        public override List<Report> GetData(List<Lot_Snap> triggerPoints)
        {
            //完全做自己的事 沒有共用的部份了
            var dataSet = _helper.ConcreteDataSet(triggerPoints);

            var result = new List<Report>();
            //我把Trigger Point 觸發點 縮寫成tp
            foreach (var tp in triggerPoints)
            {
                //該回合需用到的資料 我把this round 縮寫成 tRound
                var tRound = SnapHelper.GetThisRoundData(dataSet, tp);

                //Controller 盡量扁平 如果有複雜的運算可以放到 Utility 做
                var item = new Report
                {
                    Lot_No = tp.Lot_No,
                    Stage_Id = tp.Stage_Id,
                    Qty = tp.Qty.Trim() + 1,   //有些轉型或是常用的運算 寫成extension會比較易用
                    Po_No = tRound.So.Po_No,
                    Order_Qty = tRound.So.Qty,
                    Sod = DataUtility.GetSod(tRound.So),  //假設這個欄位有很複雜的邏輯 放到Utitlity做
                    Event_Name = builderParam.EventName,
                };

                result.Add(item);
            }

            return result;
        }
    }
}

 

1_SnapHelper.DataSet.cs

using Sample_4.Models;
using Sample_4.ViewModels;
using System.Collections.Generic;
using System.Linq;

namespace Sample_4.Event.Snap
{
    public partial class SnapHelper : BaseDataAccessHelper<Lot_Snap>
    {
        public SnapHelper(Repository pRepo) : base(pRepo)
        {
        }

        /// <summary>
        /// 其他資料都是based on 你觸發點撈出來的資料有什麼 再去補撈其他資料 
        /// 例如: 先撈出過站記錄,再透過這段時間內有哪些貨批在動 (表示要報) 再去撈出這些貨批的客戶訂單資料
        /// </summary>
        public override DataSetVM ConcreteDataSet(List<Lot_Snap> triggerPoints)
        {
            DataSetVM dataSet = new DataSetVM();

            dataSet.So = GetSo(triggerPoints);

            return dataSet;
        }

        private List<Sales_Order> GetSo(List<Lot_Snap> triggerPoints)
        {
            //通常表示你會有專屬於 MoveOut 的取 So 的條件
            var lotNos = triggerPoints.Select(q => q.Lot_No).Distinct().ToList();
            var result = _repo.GetSo(lotNos);

            return result;
        }
    }
}

 

2_SnapHelper.ThisRound.cs

using Sample_4.Models;
using Sample_4.ViewModels;
using System.Linq;

namespace Sample_4.Event.Snap
{
    public partial class SnapHelper : BaseDataAccessHelper<Lot_Snap>
    {
        /// <summary>
        /// 因為希望不要一直頻繁開關DB 所以前面一次把所有批號的資料都撈出來了
        /// 接下來得篩選出只屬於這回合的資料 (例如 根據這回合的過站記錄的批號 從剛剛的DataSet中找出訂單 => 不是每跑一批就查一次DB )
        /// </summary>
        public static ThisRoundDataVM GetThisRoundData(DataSetVM src, Lot_Snap tp)
        {
            ThisRoundDataVM tRound = new ThisRoundDataVM();

            tRound.So = GetSo(src, tp);

            return tRound;
        }

        private static Sales_Order GetSo(DataSetVM src, Lot_Snap tp)
        {
            var Sos = src.So.Where(q => q.Lot_No == tp.Lot_No).ToList();
            return Sos.Any() ? Sos.First() : new Sales_Order();
        }
    }
}

 

其他的程式碼與上一題是一樣的

詳見 [報表程式 - 3] 多個觸發點+多種資料來源 => ViewModel