public virtual void ProcessRecord(TDomain domain, ImportValueDictionary <TDomain> values, bool isNew) { List <Exception> exceptions = new List <Exception>(); foreach (var prop in Properties.Where(x => !x.IsIgnoredFor(domain))) { if (prop != _idProperty && (prop.Update || isNew)) { try { prop.SetValue(domain, isNew, values.Get(prop), Context, values); } catch (Exception ex) { exceptions.Add(ex); } } } if (exceptions.Any()) { throw new AggregateException(exceptions); } }
public abstract void SetValue(TDomain record, bool isNew, object value, TContext context, ImportValueDictionary <TDomain> values);
public virtual TDomain CreateRecord(ImportValueDictionary <TDomain> values) { return(Activator.CreateInstance <TDomain>()); }
public void ImportFromDataTable(ImportType importType, DataTable dt, bool isFromExcel) { var import = this; Func <DataRow, IImportProperty, string> formatRowNumber = (dr, prop) => { if (import.ByCellReference) { return(prop?.Description == null ? null : (" for '" + prop.Description + "'")); } else { int row = import.RowStart + dt.Rows.IndexOf(dr) + 1; if (isFromExcel) { row++; //Account for header row } if (import is IHasMetadataRow <TDomain> ) { row++; } return($" for {(isFromExcel ? "row" : "record")} {row}"); } }; List <DataRow> rows = import.Sort(dt).ToList(); Dictionary <DataRow, ImportValueDictionary <TDomain> > valuesDict = new Dictionary <DataRow, ImportValueDictionary <TDomain> >(); //Parse the values foreach (DataRow dr in rows) { var values = new ImportValueDictionary <TDomain>(); foreach (var prop in import.Properties) { object val = dr[prop.FieldName]; if (val == DBNull.Value) { val = null; } //Data table stores nullable Enum as int. Have to convert it to the underlying enum type if (Nullable.GetUnderlyingType(prop.PropertyType)?.IsEnum == true && val != null) { val = Enum.ToObject(Nullable.GetUnderlyingType(prop.PropertyType), val); } values[prop.ExpressionKey] = prop.Parse(val); } valuesDict[dr] = values; } import.BeforeImport(valuesDict.Values); var exceptions = new List <Exception>(); var importedRecords = new List <ImportedRecord <TDomain> >(); foreach (DataRow dr in rows) { //skip rows that are all null if (dr.Table.Columns.OfType <DataColumn>().All(c => dr.IsNull(c))) { continue; } try { var values = valuesDict[dr]; TId id = import.CanUpdate ? values.Get(IdExpression) : default(TId); bool idIsNull = EqualityComparer <TId> .Default.Equals(id, default(TId)); //Validate the import type if (idIsNull && importType == ImportType.Update) { exceptions.Add(new Exception($"Valiation failed{formatRowNumber(dr, null)}: Attempting to update a non-existing record.")); } else if (!idIsNull && importType == ImportType.Create) { exceptions.Add(new Exception($"Valiation failed{formatRowNumber(dr, null)}: Attempting to create an existing record.")); } //Don't continue to validate this record if there are errors at this point. if (exceptions.Any()) { continue; } //Get the domain object TDomain domain = null; if (!idIsNull) { domain = FindById(id); } else { domain = import.CreateRecord(values); } bool isNew = EqualityComparer <TId> .Default.Equals(GetId(domain), default(TId)); //Validate required properties foreach (var prop in import.Properties.Where(x => !x.IsIgnoredFor(domain))) { if (prop.Update || isNew) //Do not validate properties if they will not be updated and this is an update import { if (prop.Required && values.Get(prop) == null) { exceptions.Add(new Exception($"Validation failed{formatRowNumber(dr, prop)}: {prop.FieldName} is missing.")); } } } //Validate Regex foreach (var prop in import.Properties.Where(x => x.ValidateRegex != null && !x.IsIgnoredFor(domain))) { string str = values.Get(prop)?.ToString(); if (str != null) { if (!prop.ValidateRegex.IsMatch(str)) { exceptions.Add(new Exception($"Validation failed{formatRowNumber(dr, prop)}: {prop.FieldName} must match /{prop.ValidateRegex}/")); } } } //Don't process this record if there are validation errors at this point. if (exceptions.Any()) { continue; } BeforeProcessRecord(domain); //Process the row import.ProcessRecord(domain, values, isNew); //Validate the resulting record try { //Validate conditionally required properties foreach (var prop in import.Properties.Where(x => x.RequiredIf != null && !x.IsIgnoredFor(domain))) { if (prop.RequiredIf(domain) && values.Get(prop) == null) { exceptions.Add(new Exception($"Validation failed{formatRowNumber(dr, prop)}: {prop.FieldName} is missing.")); } } import.ValidateRecord(domain); } catch (Exception ex) { exceptions.Add(new Exception($"Validation failed{formatRowNumber(dr, null)}: {ex.Message}", ex)); } importedRecords.Add(new ImportedRecord <TDomain>() { Record = domain, Values = values, IsNew = isNew }); } catch (Exception ex) { string messages = ex.Message; if (ex is AggregateException ae) { messages = string.Join("; ", ae.InnerExceptions.Select(x => x.Message)); } exceptions.Add(new Exception($"Import failed{formatRowNumber(dr, null)}: {messages}", ex)); } } if (exceptions.Any()) { throw new AggregateException(exceptions); } import.AfterImport(new AfterImportArgs <TDomain>(importedRecords, importType)); }