Gremlin/Gremlin_BlazorServer/Data/DBClasses/GenericImporter.cs

1206 lines
40 KiB
C#

using Gremlin_BlazorServer.Data.EntityClasses;
using Gremlin_BlazorServer.Services;
using Gremlin_BlazorServer.Services.GUClasses;
using Microsoft.VisualBasic.FileIO;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using static Gremlin_BlazorServer.Data.EntityClasses.Enums;
namespace Gremlin_BlazorServer.Data.DBClasses
{
public static class GenericImporter
{
//Private members
private static readonly DateTime FarInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
private static Encoding encoding = Encoding.UTF8;
private static TextFieldParser csvParser = new(Filepath, encoding, true);
private static string filepath = string.Empty;
internal static GremlinContext db = new();
//Public properties
public static string Filepath
{
get => filepath;
set
{
filepath = value;
if (encoding != null)
{
TextFieldParser _csvParser = new(Filepath, encoding, true);
_csvParser.SetDelimiters(Separators);
_csvParser.HasFieldsEnclosedInQuotes = true;
}
}
}
public static string[] Separators
{
get => csvParser.Delimiters!;
set
{
if (value.Length > 0)
{
csvParser.SetDelimiters(value);
}
}
}
public static bool DataHasHeadings { get; set; }
public static Encoding Encoding
{
get => encoding;
set
{
encoding = value;
ResetParser();
}
}
////Constructors
//public GenericImporter(string filepath)
//{
// Filepath = filepath;
// _dataHasHeadings = true;
// _encoding = FileIO.GetEncoding(Filepath);
// TextFieldParser _csvParser = new(Filepath, _encoding);
// _csvParser.SetDelimiters(Separators);
// _csvParser.HasFieldsEnclosedInQuotes = true;
//}
//public GenericImporter(string filepath, Encoding encoding)
//{
// Filepath = filepath;
// _dataHasHeadings = true;
// _encoding = encoding;
// TextFieldParser _csvParser = new(Filepath, _encoding);
// _csvParser.SetDelimiters(Separators);
// _csvParser.HasFieldsEnclosedInQuotes = true;
//}
//public GenericImporter(string filepath, string[] separators)
//{
// Filepath = filepath;
// _dataHasHeadings = true;
// _encoding = FileIO.GetEncoding(Filepath);
// TextFieldParser _csvParser = new(Filepath, _encoding);
// Separators = separators;
// _csvParser.HasFieldsEnclosedInQuotes = true;
//}
//public GenericImporter(string filepath, Encoding encoding, string[] separators)
//{
// Filepath = filepath;
// _encoding = encoding;
// _dataHasHeadings = true;
// TextFieldParser _csvParser = new(Filepath, _encoding);
// Separators = separators;
// _csvParser.HasFieldsEnclosedInQuotes = true;
//}
//Public methods
public static void SetFilepath()
{
//Filepath = FileIO.GetFilepathFromUser();
}
public static void ResetParser()
{
if (Filepath != "")
{
TextFieldParser _newParser = new(Filepath, encoding);
_newParser.SetDelimiters(Separators);
_newParser.HasFieldsEnclosedInQuotes = true;
csvParser = _newParser;
}
}
public static bool Run(string filepath, string separator, string encoding)
{
GenericImporter.filepath = filepath.Replace(@"\\", @"\");
try
{
Encoding = Encoding.GetEncoding(encoding);
}
catch (Exception)
{
Encoding = FileService.GetEncoding(GenericImporter.filepath);
}
Separators = new string[] { separator };
return ImportFile();
}
public static async Task<bool> RunAsync(string filepath, string separator, string encoding)
{
if (filepath is "" or null)
{
return false;
}
GenericImporter.filepath = filepath.Replace(@"\\", @"\");
try
{
Encoding = Encoding.GetEncoding(encoding);
}
catch (Exception)
{
Encoding = FileService.GetEncoding(GenericImporter.filepath);
}
Separators = new string[] { separator };
return await Task.Run(ImportFile);
}
public static string GuessSeparator(string filepath)//, string encoding)
{
if (filepath is "" or null)
{
return string.Empty;
}
string[] candidates = new string[] { "|", ",", ";" };
int numberOfCandidates = candidates.Length;
int numberOfLinesToEvaluate = 100;
int[,] score = new int[numberOfLinesToEvaluate, numberOfCandidates];
GenericImporter.filepath = filepath.Replace(@"\\", @"\");
//if (csvParser == null)
//{
// try
// {
// Encoding = Encoding.GetEncoding(encoding);
// }
// catch (Exception)
// {
// Encoding = FileIO.GetEncoding(GenericImporter.filepath);
// }
//}
using (csvParser)
{
string line;
for (int i = 0; i < numberOfLinesToEvaluate; i++)
{
if (!csvParser.EndOfData)
{
line = csvParser.ReadLine()!;
for (int j = 0; j < numberOfCandidates; j++)
{
score[i, j] = line.Split(candidates[j]).Length;
}
}
}
}
List<(string, int, float)> scoreBoard = new(); //Item1 = Separator, Item2 = Score (Anzahl aufeinanderfolgender Zeilen mit gleicher Anzahl von Fields), Item3 = Count (durchschnittliche Anzahl von Fields in Zeile)
int x;
float average;
for (int j = 0; j < numberOfCandidates; j++)
{
x = 0;
average = 0;
for (int i = 0; i < numberOfLinesToEvaluate - 1; i++)
{
if (score[i, j] == score[i + 1, j] && score[i, j] > 1)
{
x++;
}
average += score[i, j];
}
average += score[numberOfLinesToEvaluate - 1, j];
average /= numberOfLinesToEvaluate;
scoreBoard.Add((candidates[j], x, average));
}
ResetParser();
return scoreBoard.Find(f => f.Item2 == scoreBoard.Max(x => x.Item2) && f.Item3 == scoreBoard.Max(x => x.Item3)).Item1;
}
public static bool ImportFile()
//Ein (möglichst) generischer Importer
//1. Dateipfad erfassen
//2. Column <-> Property Mapping
//3. Typ der zu importierenden Daten herausfinden
//4. Daten einlesen, konvertieren, validieren, Metadaten setzen und alles in Liste(n) speichern
//5. Datenliste(n) in DB speichern
{
if (filepath == "")
{
SetFilepath();
}
if (filepath == "")
{
return false;
}
using (csvParser)
{
//für geneerischen Code:
//int numberOfLines = File.ReadAllLines(filepath).Length;
//Assembly Gremlin = Assembly.GetExecutingAssembly();
//dynamische Spaltenzuordnung in Dictonary speichern
string[] fields = csvParser.ReadFields()!;
Dictionary<string, string> MappingDictionary = ReadMappingDictionaryFromFile();
Dictionary<string, int> mappingTable = MapDataHeading(fields, MappingDictionary);
//determine data type to be imported
DataIdentificator dataIdentificator = new(mappingTable);
List<string> detectedDataTypes = dataIdentificator.Identify();
//check for detectedDataTypes = empty (possible when DataIdentificator.Identify(MustMatchAllQualifer = true) and no dataset matches all qualifiers of a type)
if (detectedDataTypes.Count == 0)
{
Debug.WriteLine($"DataIdentificator.Identify() konnte die Datenart nicht bestimmen!{Environment.NewLine}Versuchen Sie den Import mit einer dezidierten Importerfunktion.");
return false;
}
return detectedDataTypes[0] switch
{
"ProductLine" => ImportProductLine(csvParser, mappingTable),
"AccountType" => ImportAccountType(csvParser, mappingTable),
"SubMarket" => ImportSubMarket(csvParser, mappingTable),
"Account" => ImportAccounts(mappingTable),
"Contact" => ImportContacts(mappingTable),
"LSAG" => ImportLSAG(mappingTable),
"Product" => ImportProducts(mappingTable),
"CustomDescription" => ImportCustomDescriptions(),// mappingTable);
_ => false,
};
}
}
public static bool ImportFile(string filepath)
{
GenericImporter.filepath = filepath;
return ImportFile();
}
private static bool ImportCustomDescriptions()//Dictionary<string, int> mappingTable)
{
List<CustomDescription> cDsReadFromFile = new(2500);
Encoding = Encoding.GetEncoding("UTF-8"); //Custom-Descriptions-CSV hat festes Encoding.
using (csvParser)
{
// Skip the row with the column names:
_ = csvParser.ReadLine();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
string[] fields = csvParser.ReadFields()!;
CustomDescription ImportedCD = new()
{
ProductNumber = fields[0],
OptionNumber = fields[1],
Heading = fields[3],
DescriptionText = fields[4],
CoverletterText = fields[5],
Notes = fields[6],
DataModificationByUser = "Importer",
DataStatus = Status.Active.ToString(),
DataValidUntil = FarInTheFuture,
DataVersionComment = "Initial Importer by CD-ImporterFomCsv",
Products = new List<Product>(),
Supplier = new()
{
AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2]
}
};
ImportedCD.DataCreationDate = ImportedCD.DataValidFrom = ImportedCD.DataModificationDate = DateTime.Now;
ImportedCD.DataVersionNumber = 1;
cDsReadFromFile.Add(ImportedCD);
}
//Eingelesenen Custum Desciptions in DB schreiben:
//Check for 3PPs that not yet in the db.
//Step 1a: Add 3PP-Products from CD-List to list
//Step 1b: Add list to db.
//Step 2: Add CDs to products.
using (GremlinContext db = new())
{
//Step 1a
List<Product> thirdPartyProductsFromImportedCDs = new();
foreach (CustomDescription CD in cDsReadFromFile)
{
if (CD.Supplier.AccountName != "Agilent Technologies")
{
Product new3PPProduct = new()
{
CustomDescription = CD,
HasBreakPrices = false,
ListPrice = 0,
ProductNumber = CD.ProductNumber,
OptionNumber = CD.OptionNumber,
ProductStatus = Status.Active.ToString(),
SapLongDescription = "",
SapShortDescription = "",
Weight = 0,
IntroductionDate = DateTime.Now.Date,
BreakRangeFrom = 0,
BreakRangeTo = 0,
ProductLine = DbHelper.ResolveProductLine(db, "3P")
};
new3PPProduct.CustomDescription.Supplier = DbHelper.ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);
_ = MetaDataSetter.ForImport(new3PPProduct, "GenericImporter-Method", "Created at import from Custom Descriptions.");
thirdPartyProductsFromImportedCDs.Add(new3PPProduct);
}
};
//Step 1b
db.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions!
_ = db.SaveChanges();
// Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
//Step 2
List<CustomDescription> importedCDsWithEFReferences = new(100000);
List<CustomDescription> cDsWithoutEFReferences = new(100); //nur zur Kontrolle, wird nicht verwendet.
List<Product> productsInDb = db.Products.ToList();
Account agilent = db.Accounts.Where(a => a.AccountName == "Agilent Technologies").Single();
foreach (CustomDescription cD in cDsReadFromFile)
{
if (cD.Products == null) { continue; }
//Skip Desciptions, if it has been already imported above (as part from 3PP)
if (thirdPartyProductsFromImportedCDs.Intersect(cD.Products).Any()) { continue; }
//Establish EF Reference. If no PN/Opt found, then skip this custom description.
//CD.Product = GetProduct(db, CD.ProductNumber, CD.OptionNumber); //ResolveXY-functions return null, if no match is found in db.
cD.Products = productsInDb.Where(product => product.ProductNumber == cD.ProductNumber && product.OptionNumber == cD.OptionNumber).ToList();
if (cD.Products == null)
{
cDsWithoutEFReferences.Add(cD);
continue;
}
//Establish EF Reference: If no Supplier-Account found, then skip this custom description (shouldn't happen), else set Supplier to Agilent (3PP-Supplier have been processed before and their products should not be part of this list).
if (cD.Supplier == null)
{
cDsWithoutEFReferences.Add(cD);
continue;
}
cD.Supplier = agilent;
//prepare properties
_ = MetaDataSetter.ForImport(cD, "GenericImporter-Method", "Initial import by CSV Importer (Function GenericImporter.ImportCustomDescriptions)");
//add to final list of CDs, that will go into the db.
importedCDsWithEFReferences.Add(cD);
}
db.CustomDescriptions.AddRange(importedCDsWithEFReferences);
_ = db.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {importedCDsWithEFReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt.");
}
return true;
}
}
private static bool ImportProducts(Dictionary<string, int> mappingTable)
{
List<Product> ProductsReadFromFile = new(ParseProductFile(mappingTable));
return InsertProducts(ProductsReadFromFile);
}
private static bool InsertProducts(List<Product> products)
{
using (GremlinContext db = new())
{
List<ProductLine> productLines = db.ProductLines.ToList();
foreach (Product product in products)
{
//var query = db.ProductLines
// .Where(a => a.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation)
// .First();
//product.ProductLine = query;
//product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation);
product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode) ?? new ProductLine();
_ = MetaDataSetter.ForImport(product, "GenericImporter-Method");
}
db.Products.AddRange(products);
_ = db.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt.");
return true;
}
}
private static List<Product> ParseProductFile(Dictionary<string, int> columnNumberOf)
///Importiert Produkt-Daten aus CSV (erstellt aus PriceSurfer-CPL)
/// - Encoding: Latin1
/// - Separator = ;
/// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
/// 1) Position = ID/PK
/// 2) Partnumber
/// 3) Option
/// 4) (Short) Description
/// 5) Current Month Price (EUR)
/// 6) ProductLineID
/// 7) Status
/// 8) (Long) Description
///Kommentarzeichen: # (hardcoded, string array)
{
List<Product> results = new(100000);
using (csvParser)
{
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
Product ImportedProduct = new()
{
ProductLine = new()
};
string[] fields = csvParser.ReadFields()!;
//Kontrolle, ob Trennzeichen in Daten vorhanden ist:
if (fields.Length > 27) //27 ist der Normalfall
{
//Sonderfall "EnVisionTM G|2":
//suche fields[x], das auf " G" endet und auf das field[x+1] mit "2 " beginnt, kombiniere die beiden und schiebe alle darauffolgenden Felder in diesem Datensatz eins nach links.
for (int i = 0; i < fields.Length - 1; i++) //fields.Length - 1, um durch i+1 nicht aus dem Index zu laufen
{
if (fields[i].EndsWith(" G"))
{
if (fields[i + 1].StartsWith("2 "))
{
fields[i] = fields[i] + fields[i + 1];
for (int j = i + 2; j < fields.Length; j++)
{
fields[j - 1] = fields[j];
}
}
}
}
}
//NICHT ausgewertete Felder:
//fields[0] = Position [in CPL]
//fields[8] = Warranty
//fields[10] = PH Code
//fields[11] = PH Description
//fields[15] = Country of Manufacturing
//fields[16] = ECCL
//fields[17] = M41
//fields[18] = First Supplier Code
//fields[19] = Harmonized Tarif Schedule
//fields[20] = Hazardous Good Flag
//fields[21] = Order instructions
//fields[23] = End of Production Date
//fields[24] = End of Support Date
ImportedProduct.ProductNumber = fields[columnNumberOf["ProductNumber"]];
if (fields[columnNumberOf["OptionNumber"]].Length == 4) //Optionsnummer mit führendem Apostroph
{
ImportedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]].Substring(1); //schneidet erstes Zeichen/Apostroph weg
}
else if (fields[columnNumberOf["OptionNumber"]].Length == 3) //3-stellige Optionsnummer übernehmen; keine Aktion bei leerem Feld (keine Optionsnummer)
{
ImportedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]];
}
ImportedProduct.SapShortDescription = fields[columnNumberOf["SapShortDescription"]];
ImportedProduct.ListPrice = decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change?
//if (fields[columnNumberOf["ListPrice"]] != "")
// if (decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")) != ImportedProduct.ListPrice)
// listpriceHasChanged = true;
ImportedProduct.BreakRangeFrom = fields[columnNumberOf["BreakRangeFrom"]] == "" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeFrom"]]);
ImportedProduct.HasBreakPrices = ImportedProduct.BreakRangeFrom > 0;
ImportedProduct.BreakRangeTo = fields[columnNumberOf["BreakRangeTo"]] is "" or "+" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeTo"]]); //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich)
ImportedProduct.ProductLine.ProductLineCode = fields[columnNumberOf["ProductLineCode"]];
switch (fields[columnNumberOf["ProductStatus"]])
{
case "Active":
ImportedProduct.DataStatus = Status.Active.ToString();
ImportedProduct.ProductStatus = Status.Active.ToString();
break;
case "New Product" or "New":
ImportedProduct.DataStatus = Status.Active.ToString();
ImportedProduct.ProductStatus = Status.New.ToString();
break;
case "Price Changed":
ImportedProduct.DataStatus = Status.Active.ToString();
ImportedProduct.ProductStatus = Status.PriceUpdated.ToString();
break;
default:
ImportedProduct.DataStatus = Status.StatusUpdated.ToString();
break;
}
if (fields[columnNumberOf["Weight"]] != null)
{
ImportedProduct.Weight = ParseWeight(fields[columnNumberOf["Weight"]]);
}
if (fields[columnNumberOf["WeightUnit"]] == "G")
{
ImportedProduct.Weight /= 1000; //Umrechnung g in kg
}
if (fields[columnNumberOf["IntroductionDate"]] != "")
{
ImportedProduct.IntroductionDate = DateTime.Parse(fields[columnNumberOf["IntroductionDate"]]);
}
ImportedProduct.SapLongDescription = fields[columnNumberOf["SapLongDescription"]];
results.Add(ImportedProduct);
}
}
return results;
}
private static float ParseWeight(string input)
{
StringBuilder sb = new();
if (input.StartsWith("."))
{
input = sb.Append(0).Append(input).ToString();
}
try
{
return float.Parse(input, new CultureInfo("en-us"));
}
catch (Exception ex)
{
Debug.WriteLine(ex, "Unhandled Error in Function 'ParseWeight'");
return 0;
}
}
private static bool ImportLSAG(Dictionary<string, int> mappingTable)
{
bool result;
result = ImportAccounts(mappingTable);
ResetParser();
_ = csvParser.ReadFields(); //Skip Heading
result = result && ImportContacts(mappingTable);
return result;
}
public static bool ImportContacts(Dictionary<string, int> columnNumberOf)
{
List<Contact> ContactsReadFromFile = new(8000);
using (csvParser)
{
while (!csvParser.EndOfData)
{
Contact ImportedContact = new();
string[] fields = csvParser.ReadFields()!;
//No conversion
ImportedContact.AcademicTitle = fields[columnNumberOf["AcademicTitle"]];
ImportedContact.FirstName = fields[columnNumberOf["FirstName"]];
ImportedContact.LastName = fields[columnNumberOf["LastName"]];
ImportedContact.EMail = fields[columnNumberOf["EMail"]];
ImportedContact.Department = fields[columnNumberOf["Department"]];
ImportedContact.Room = fields[columnNumberOf["Room"]];
ImportedContact.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
ImportedContact.Function = fields[columnNumberOf["Function"]];
ImportedContact.MobileNumber = fields[columnNumberOf["MobileNumber"]];
//Convert Gender
ImportedContact.Gender = fields[columnNumberOf["Gender"]] == "M"
? (byte)Gender.Male
: fields[columnNumberOf["Gender"]] == "F"
? (byte)Gender.Female
: (byte)Gender.Unknown;
//Convert OptIn Status
ImportedContact.OptInStatus = fields[columnNumberOf["OptInStatus"]] switch
{
"Opt In" => true,
"Opt Out" => false,
_ => false,
};
//Convert "SAP Contact Number"
try
{
ImportedContact.SAPContactNumber = Convert.ToInt32(fields[columnNumberOf["SAPContactNumber"]], CultureInfo.CurrentCulture);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "")
{
errorRaiser = "No Contact Number in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "")
{
errorRaiser = "No Contact Number in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
//Convert "Account Created on"
int year = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(0, 4), CultureInfo.CurrentCulture);
int month = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(4, 2), CultureInfo.CurrentCulture);
int day = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(6, 2), CultureInfo.CurrentCulture);
ImportedContact.SAPContactCreationDate = new DateTime(year, month, day);
//Convert "No Phone Calls"
if (fields[columnNumberOf["NoPhoneCalls"]] == "1")
{
ImportedContact.NoPhoneCalls = true;
}
//Convert "No Hardcopy Mailing"
if (fields[columnNumberOf["NoHardcopyMailing"]] == "1")
{
ImportedContact.NoHardcopyMailing = true;
}
//SAPAccountID in Contact.Notes speichern, um den entsprechenden Account aus DB heraussuchen zu können:
ImportedContact.Notes = fields[columnNumberOf["SAPAccountNumber"]];
ContactsReadFromFile.Add(ImportedContact);
}
//Eingelesenen Account in DB schreiben:
using (GremlinContext db = new())
{
foreach (Contact contact in ContactsReadFromFile)
{
// AccountID aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
contact.Account = DbHelper.ResolveAccountById(db, Convert.ToUInt32(contact.Notes));
contact.Notes = "";
_ = MetaDataSetter.ForImport(contact, "GenericImporter-Method");
}
db.Contacts.AddRange(ContactsReadFromFile);
_ = db.SaveChanges();
}
}
//Bestätigung senden
Debug.WriteLine($"Es wurden {ContactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt.");
return true;
}
public static bool ImportAccounts(Dictionary<string, int> columnNumberOf)
///Importiert Account Daten aus CSV (erstellt aus LSAG_Contact_List_Tool.xlsx)
///
/// - Encoding: Latin1/ISO-8859-1
/// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
/// 1) SAPAccountNumber
/// 2) AccountName
/// 3) ZIP
/// 4) City
/// 5) Street
/// 6) AccountSubMarketCode
/// 7) AccountTypeCode
/// 8) PhoneNumber
/// 9) AccountsCreatedInSAP
///Kommentarzeichen: # (hardcoded, string array)
///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'.
///
///Argumente:
/// 1.
{
List<Account> AccountsReadFromFile = new(1000);
while (!csvParser.EndOfData)
{
bool DataHasError = false;
// Read current line fields, pointer moves to the next line.
Account ImportedAccount = new();
{
ImportedAccount.SubMarket = new();
ImportedAccount.AccountType = new();
ImportedAccount.Contacts = new List<Contact>();
}
string[] fields = csvParser.ReadFields()!;
//Konvertierung erforderlich:
try
{
ImportedAccount.SAPAccountNumber = Convert.ToUInt32(fields[columnNumberOf["SAPAccountNumber"]]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPAccountNumber"]];
if (errorRaiser == "")
{
errorRaiser = "No Account Number in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPAccountNumber"]];
if (errorRaiser == "")
{
errorRaiser = "No Account Number in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
try
{
ImportedAccount.ZIP = Convert.ToUInt32(fields[columnNumberOf["ZIP"]]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["ZIP"]];
if (errorRaiser == "")
{
errorRaiser = "No ZIP in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["ZIP"]];
if (errorRaiser == "")
{
errorRaiser = "No ZIP in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
//Konvertierung für AccountCreatedinSAPOn Property:
//Test auf Inhalt bzw. Eintrag in der Mapping-Table (optionales Feld)
if (columnNumberOf.TryGetValue("AccountCreatedinSAPOn", out int columnNumber))
{
if (fields[columnNumberOf["AccountCreatedinSAPOn"]].Length != 0)
{
int year = Convert.ToInt32(fields[columnNumber].Substring(0, 4));
int month = Convert.ToInt32(fields[columnNumber].Substring(4, 2));
int day = Convert.ToInt32(fields[columnNumber].Substring(6, 2));
ImportedAccount.AccountCreatedInSAPOn = new DateTime(year, month, day);
}
}
//Convert City von Großschreibung zu Normalschreibung
ImportedAccount.City = fields[columnNumberOf["City"]].First().ToString() + fields[columnNumberOf["City"]].Substring(1).ToLower();
//keine Konvertierung nötig:
ImportedAccount.AccountName = fields[columnNumberOf["AccountName"]];
ImportedAccount.Street = fields[columnNumberOf["Street"]];
ImportedAccount.SubMarket.SubMarketCode = fields[columnNumberOf["SubMarketCode"]];
ImportedAccount.SubMarket.DataStatus = Status.Active.ToString();
ImportedAccount.AccountType.AccountTypeCode = fields[columnNumberOf["AccountTypeCode"]];
ImportedAccount.AccountType.DataStatus = Status.Active.ToString();
ImportedAccount.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
ImportedAccount.DataStatus = Status.Active.ToString();
//Validierungen:
if (ImportedAccount.AccountName == ""
|| ImportedAccount.City == ""
|| ImportedAccount.Street == ""
|| ImportedAccount.SubMarket.SubMarketCode == ""
|| ImportedAccount.AccountType.AccountTypeCode == ""
|| ImportedAccount.SAPAccountNumber == 0)
{
DataHasError = true;
}
//Validierten Account der Liste hinzufügen:
if (DataHasError == false)
{
_ = ImportedAccount.AddIfUniqueTo(AccountsReadFromFile);
}
}
//Eingelesenen Account in DB schreiben:
DateTime now = DateTime.Now;
foreach (Account account in AccountsReadFromFile)
{
// AccountType aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
AccountType accountType = db.AccountTypes
.Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
.First();
account.AccountType = accountType;
account.AccountType = DbHelper.ResolveAccountType(db, account.AccountType.AccountTypeCode);
SubMarket subMarket = db.SubMarkets
.Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode)
.First();
account.SubMarket = subMarket;
account.SubMarket = DbHelper.ResolveSubmarket(db, account.SubMarket.SubMarketCode);
_ = MetaDataSetter.ForImport(account, "GenericImporter-Method");
//account.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)";
}
db.Accounts.AddRange(AccountsReadFromFile);
_ = db.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {AccountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt.");
return true;
}
public static bool ImportProductLine(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
{
//foreach line in file:
//read seed data, parse/split, save to object with metadata
//add object to list
//add list to context, savechanges
List<ProductLine> productLinesReadFromFile = new(50);
using (csvParser)
{
while (!csvParser.EndOfData)
{
ProductLine importedProductLine = new();
string[] fields = csvParser.ReadFields()!;
importedProductLine.ProductLineCode = fields[mappingTable["ProductLineCode"]];
importedProductLine.ProductLineDescription = fields[mappingTable["ProductLineDescription"]];
productLinesReadFromFile.Add(importedProductLine);
_ = MetaDataSetter.ForImport(importedProductLine, "GenericImporter-Method");
}
}
db.ProductLines.AddRange(productLinesReadFromFile);
_ = db.SaveChanges();
return true;
}
public static bool ImportAccountType(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
{
//foreach line in file:
//read seed data, parse/split, save to object with metadata
//add object to list
//add list to context, savechanges
List<AccountType> accountTypesReadFromFile = new(20);
using (csvParser)
{
while (!csvParser.EndOfData)
{
AccountType importedAccountType = new();
string[] fields = csvParser.ReadFields()!;
importedAccountType.AccountTypeCode = fields[mappingTable["AccountTypeCode"]];
importedAccountType.AccountTypeDescription = fields[mappingTable["AccountTypeDescription"]];
accountTypesReadFromFile.Add(importedAccountType);
_ = MetaDataSetter.ForImport(importedAccountType, "GenericImporter-Method");
}
}
db.AccountTypes.AddRange(accountTypesReadFromFile);
_ = db.SaveChanges();
return true;
}
public static bool ImportSubMarket(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
{
//foreach line in file:
//read seed data, parse/split, save to object with metadata
//add object to list
//add list to context, savechanges
List<SubMarket> subMarketsReadFromFile = new(20);
using (csvParser)
{
while (!csvParser.EndOfData)
{
SubMarket importedSubMarket = new();
string[] fields = csvParser.ReadFields()!;
importedSubMarket.SubMarketCode = fields[mappingTable["SubMarketCode"]];
importedSubMarket.SubMarketDescription = fields[mappingTable["SubMarketDescription"]];
subMarketsReadFromFile.Add(importedSubMarket);
_ = MetaDataSetter.ForImport(importedSubMarket, "GenericImporter-Method");
}
}
db.SubMarkets.AddRange(subMarketsReadFromFile);
_ = db.SaveChanges();
return true;
}
//private static void SetProperty(object gremlinEntity, PropertyInfo propertyInfo, string value)
//{
// propertyInfo.SetValue(gremlinEntity, value);
//}
//private static Array GetNewGremlinTypeArray(Assembly assembly, string detectedDataType, int length)
//{
// Type GremlinType = GetNewGremlinType(assembly, detectedDataType).GetType();
// //Create an one-dimensional array with n+1 or n+2 elements (n = number of datasets, plus heading and/or emptyline at EOF):
// return Array.CreateInstance(GremlinType, length);
// //Das geht alles nicht:
// //List<GremlinType> test = new();
// //List<typeof(GremlinType)> test = new();
// //List<GremlinType.GetType()> test = new();
// ////Das geht:
// ////Create iList:
// //Type listType = typeof(List<>).MakeGenericType(GremlinType);
// //var list = (IList)Activator.CreateInstance(listType);
// //list.Add(new Account() { AccountName = "Test" });
// //list.Add(GremlinClassObject);
//}
//private static object GetNewGremlinType(Assembly Gremlin, string detectedDataType)
//{
// return Activator.CreateInstance(Gremlin.ToString(), "Gremlin." + detectedDataType).Unwrap();
//}
public static Dictionary<string, string> ReadMappingDictionaryFromFile()
{
Dictionary<string, string> result = new();
string fileInput = FileService.ReadResource("MappingDictionary.txt");
string[] lines = fileInput.Split(Environment.NewLine);
foreach (string line in lines)
{
string[] fields;
fields = line.Split("|");
result.Add(fields[0], fields[1]);
}
return result;
}
public static Dictionary<string, int> MapDataHeading(string[] headings, Dictionary<string, string> columnPropertyMapping)
{
Dictionary<string, int> result = new();
for (int i = 0; i < headings.Length; i++)
{
string heading = headings[i].ToLower(CultureInfo.CurrentCulture)
.Trim()
.Replace(" ", "")
.Replace("-", "")
.Replace("_", "")
.Replace(".", "")
.Replace(":", "");
if (columnPropertyMapping.TryGetValue(heading, out string? value))
{
result.Add(value, i);
}
}
return result;
}
//private static void BasimodosCodeDump()
//{
// //Unfertiger, generischer Code aus der ImportFile() Methode.
// //Aufbewahren für später, wenn Zeit ist, das ordentlich zu machen.
// //while (!csvParser.EndOfData)
// //{
// // //read
// // //new data instance
// // //convert
// // //validate
// // //set metadata: SetMetadataForImport(IMetadata)
// // //add to list
// // fields = csvParser.ReadFields(); // Read current line fields, pointer moves to the next line.
// // if (fields[0] == "") break; // Am Ende hängt eine leere Zeile, die im Parser einen Fehler auslösen würde.
// // //TO DO: vor der foreach "Account" bevorzugt behandeln (Sonderfall bei LSAG: Liste deduplizieren, und als erstes importieren, da sonst keine Contacts importiert werden können).
// // foreach (string detectedDataType in detectedDataTypes)
// // {
// // //"Account" überspringen, da das vor der foreach schon verarbeitet worden ist.
// // if (detectedDataType == "Account") continue;
// // Array GremlinTypeArray = GetNewGremlinTypeArray(Gremlin, detectedDataType, numberOfLines);
// // Type GremlinType = GetNewGremlinType(Gremlin, detectedDataType).GetType();
// // PropertyInfo[] GremlinTypeProperties = GremlinType.GetProperties();
// // foreach (PropertyInfo property in GremlinTypeProperties)
// // {
// // if (mappingTable.TryGetValue(property.Name, out int columnNumber))
// // {
// // //convert:
// // GremlinTypeConverter.Convert(fields[columnNumber]);
// // }
// // }
// // }
// //}
// //using (GremlinContext db = new())
// //{
// // //add to context
// // //savechanges
// //}
//}
//#########################################################################################
//Kann gelöscht werden, sobald die generische Funktion zuverlässig funktioniert:
//#########################################################################################
//private static string RecognizeDataStatic(Dictionary<string, int> mappingDictionary)
//{
// //Logik zur Kategorisierung des Datensatzes:
// //Alle verpflichtenden Angaben zu einer Klasse vorhanen?
// //Alle vom Importer erwarteten Angaben vorhanden?
// //Bei den Enums zusätzlich noch proüfem, dass nur zwei Spalten vorhanden sind, sonst könnten LSAG-Daten falsch identifiziert werden.
// //
// //
// //Products
// if (//required by DB:
// mappingDictionary.ContainsKey("ProductNumber")
// && mappingDictionary.ContainsKey("ListPrice")
// //required by importer:
// && mappingDictionary.ContainsKey("Weight")
// //unique identifier:
// && mappingDictionary.ContainsKey("BreakRangeFrom")
// )
// {
// return "Product";
// }
// //LSAG Contact List Tool List
// if (//required by DB:
// mappingDictionary.ContainsKey("SAPAccountNumber")
// && mappingDictionary.ContainsKey("SAPContactNumber")
// && mappingDictionary.ContainsKey("AccountTypeCode")
// && mappingDictionary.ContainsKey("SubMarketCode")
// && mappingDictionary.ContainsKey("LastName")
// && mappingDictionary.ContainsKey("AccountName")
// //required by importer:
// //unique identifier:
// && mappingDictionary.ContainsKey("MA_ProductInterests")
// )
// {
// return "LSAG Contact List Tool List";
// }
// //Accounts
// if (//required by DB:
// mappingDictionary.ContainsKey("AccountName")
// && mappingDictionary.ContainsKey("Street")
// && mappingDictionary.ContainsKey("ZIP")
// && mappingDictionary.ContainsKey("City")
// && mappingDictionary.ContainsKey("PhoneNumber")
// && mappingDictionary.ContainsKey("SAPAccountNumber")
// && mappingDictionary.ContainsKey("AccountCreatedInSAPOn")
// && mappingDictionary.ContainsKey("AccountTypeCode")
// && mappingDictionary.ContainsKey("SubMarketCode")
// //required by importer:
// )
// {
// return "Account";
// }
// //Contacts
// if (//required by DB:
// mappingDictionary.ContainsKey("LastName")
// && mappingDictionary.ContainsKey("SAPContactNumber")
// //required by importer:
// && mappingDictionary.ContainsKey("FirstName")
// && mappingDictionary.ContainsKey("Gender")
// )
// {
// return "Contact";
// }
// //Custom Description
// if (//required by DB:
// mappingDictionary.ContainsKey("Heading")
// //required by importer:
// && mappingDictionary.ContainsKey("ProductNumber")
// && mappingDictionary.ContainsKey("OptionNumber")
// && mappingDictionary.ContainsKey("DescriptionText")
// //unique identifier:
// && mappingDictionary.ContainsKey("CoverletterText")
// )
// {
// return "CustomDescription";
// }
// //Pseudo-Enums AccountTypes, SubMarkets, ProductLines for DB-initializing
// if (mappingDictionary.Count == 2)
// {
// if (mappingDictionary.ContainsKey("AccountTypeCode")
// && mappingDictionary.ContainsKey("AccountTypeDescription")
// && mappingDictionary.Count == 2)
// {
// return "AccountType";
// }
// if (mappingDictionary.ContainsKey("SubMarketCode")
// && mappingDictionary.ContainsKey("SubMarketDescription")
// && mappingDictionary.Count == 2)
// {
// return "SubMarket";
// }
// if (mappingDictionary.ContainsKey("ProductLineCode")
// && mappingDictionary.ContainsKey("ProductLineDescription")
// && mappingDictionary.Count == 2)
// {
// return "ProductLine";
// }
// }
// return "No entity type unambigiously identified!";
//}
}
}