Bulk Insert : Collection was modified; enumeration operation may not execute - c#

I'm using this code for saving data into database (my data comes from a text file that I extract fields in that file using Regex) :
var list = new List<IdiomExample>();
using (var db = new MyDbContext())
{
foreach (Match match in r.Matches(input))
{
string val = match.Groups[1].Value; // Idiom
string val2 = match.Groups[2].Value; // Meaning
string val3 = match.Groups[3].Value; // Desc
foreach (Capture c in match.Groups["my"].Captures)
{
list.Add(new IdiomExample{Item = c.Value});
}
db.Idioms.Add(new Idiom
{
Verb = val,
Meaning = val2,
Description = val3,
IdiomExamples = list
});
db.SaveChanges();
}
}
but when I run my code I get this exception :
Collection was modified; enumeration operation may not execute.
My models :
public class Idiom
{
public Int32 Id { get; set; }
public string Verb { get; set; }
public string Meaning { get; set; }
public string Description { get; set; }
public IList<IdiomExample> IdiomExamples { get; set; }
}
public class IdiomExample
{
public Int32 Id { get; set; }
public string Item { get; set; }
}
when I check my table's data two records just insertedو Whereas it should be 500 records in Idioms's Table and about 1000 records in IdiomExamples's Table. How can I solve my problem?
Thanks in advance.

give it a shot:
using (var db = new MyDbContext())
{
foreach (Match match in r.Matches(input))
{
var list = new List<IdiomExample>();
string val = match.Groups[1].Value; // Idiom
string val2 = match.Groups[2].Value; // Meaning
string val3 = match.Groups[3].Value; // Desc
foreach (Capture c in match.Groups["my"].Captures)
{
list.Add(new IdiomExample{Item = c.Value});
}
db.Idioms.Add(new Idiom
{
Verb = val,
Meaning = val2,
Description = val3,
IdiomExamples = list
});
}
db.SaveChanges();
}

Related

Sort values in entity framework based on substring values from a column

I have Department table values in EF model and one of the column has value has value values like DEPTName YEAR-EMPid 'ACCT 2020-012', 'SVC 2021-014', 'MRKT 2022-001' . How do I sort to get values in the below order in EF using LINQ. The prefix for department is sometimes 3 or 4
'MRKT 2022-001'
'SVC 2021-014'
'ACCT 2020-012'
Please advise.
var depPrefix ='XYZ "
var rs = qry.select(x=> new DeptDto { DeptId = x.DeptId , Dep = x.Dep, DeptEmp=x.Dep.SubString(0, x.Dep.Length - depPrefix.length)});
The above works well with prefix has only 3 dept abbreviation; but fails when more/less than 3.
Use IComparable
static void Main(string[] args)
{
string[] input = {"MRKT 2022-001", "SVC 2021-014", "ACCT 2020-012"};
var results = input.Select(x => new MyCompare(x)).OrderByDescending(x => x).Select(x => x.input).ToArray();
}
public class MyCompare : IComparable<MyCompare>
{
public string input { get; set; }
string prefix { get; set; }
int year { get; set; }
int suffix { get; set; }
const string pattern = #"^(?'prefix'[^\s]+)\s+(?'year'\d{4})-(?'suffix'.*)";
public MyCompare(string input)
{
this.input = input;
Match match = Regex.Match(input, pattern);
prefix = match.Groups["prefix"].Value;
year = int.Parse(match.Groups["year"].Value);
suffix = int.Parse(match.Groups["suffix"].Value);
}
public int CompareTo(MyCompare other)
{
if (this.year != other.year)
return this.year.CompareTo(other.year);
if (this.prefix != other.prefix)
return this.prefix.CompareTo(other.prefix);
return this.suffix.CompareTo(other.suffix);
}
}

Join sub variables in a List

I have my variables class with constructor
public class Gdf2Lines
{
public string line { get; set; }
public int linenumber { get; set; }
public string columnNumber { get; set; }
public string columnName { get; set; }
public Gdf2Lines()
{
line = "";
linenumber = -1;
columnNumber = ""; // prefer to keep as the string read from the text source
columnName = "";
}
}
I have my class that creates of list of the above class and populates the variables within for each line from a file
class GDF2
{
Gdf2Lines Data = new Gdf2Lines();
List<Gdf2Lines> gdf2 = new List<Gdf2Lines>();
public GDF2(string[] arrFile)
{
int count = 0;
foreach (String line in arrFile)
{
Data.line = line;
Data.linenumber = count;
Data.columnNumber = GetColumnNumber(line);
Data.columnName = GetColumnName(line);
count++;
gdf2.Add(Data);
}
}
}
I know a "normal" list can be joined into a string by:
String.Join(Environment.Newline.ToString(), List);
But is there an equally easy way to join one of the (sub) variables within my list, such as
String.Join(",", gdf2.columnName);
Currently I am using a for loop.
Something like this should work:
String.Join(",", gdf2.Select(x => x.columnName));
This uses LINQ to extract a list of columnName values from the list of Gdf2Line.

Parsing txt file to List

I have a txt file that want to store into a array. I'm running into a problem parsing when I have additional items on single line, as the pipe(|) would indicate another item to be stored.
Inv # Date term qty description price Tax
3221409:2017/01/12:215|10:WD2002:2TB Hard Drive:121.66:N|20:KG240S:240GB SSD:125.10:N|20:KG120S:120GB SSD:78.75:N
I'm trying to first try to open the file and show each element to the console. getting index outside the bounds of the array. The file doesn't have headers I provided them for information.
//this object lets you read from a file.
StreamReader streamReader = null;
string lineData;
string[] lineElements;
if (File.Exists(path))
{
Console.WriteLine("Woohoo file found");
try
{
int invoice;
String invoicedate;
int term;
int qty;
string description;
Boolean tax;
streamReader = new StreamReader(path);
while (streamReader.Peek() > 0)
{
lineData = streamReader.ReadLine();
var parts = lineData.Split('|');
lineElements = parts.First().Split(':');
invoice = int.Parse(lineElements[0]);
invoicedate = (lineElements[1]);
term = int.Parse(lineElements[2]);
qty = int.Parse(lineElements[3]);
Console.WriteLine(invoice);
Console.WriteLine(invoicedate);
Console.WriteLine(term);
Console.WriteLine(qty);
}
First define a structure that can hold the informations
public class InventoryData
{
public string Inv { get; set; }
public DateTime Date { get; set; }
public string Term { get; set; }
public IList<InventoryArticle> Articles { get; set; }
}
public class InventoryArticle
{
public int Quantity { get; set; }
public string Whatever { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public bool Tax { get; set; }
}
then two methods, that will parse a single line into the structure
static InventoryData ParseInventoryData( string data )
{
var parts = data.Split( '|' );
var headparts = parts.First().Split( ':' );
var result = new InventoryData
{
Inv = headparts[0],
Date = DateTime.ParseExact( headparts[1], "yyyy/MM/dd", System.Globalization.CultureInfo.InvariantCulture ),
Term = headparts[2],
Articles = parts.Skip( 1 ).Select( part => ParseInventoryArticle( part ) ).ToList(),
};
return result;
}
static InventoryArticle ParseInventoryArticle( string data )
{
var parts = data.Split( ':' );
var result = new InventoryArticle
{
Quantity = int.Parse( parts[0] ),
Whatever = parts[1],
Description = parts[2],
Price = decimal.Parse( parts[3], System.Globalization.CultureInfo.InvariantCulture ),
Tax = parts[4] == "Y",
};
return result;
}
and finally how to parse the content of a file
string[] content = {
"Inv # Date term qty description price Tax",
"3221409:2017/01/12:215|10:WD2002:2TB Hard Drive:121.66:N|20:KG240S:240GB SSD:125.10:N|20:KG120S:120GB SSD:78.75:N" };
var data = content
// skip the header row
.Skip( 1 )
// parse the content
.Select( row => ParseInventoryData( row ) )
.ToList();

Entity to LINQ upload CSV file where single rows can have multiple values in columns

I'm currently working on parsing a csv file that was exported by another application. This application exported the data in a strange way. This export is from accoutning and it looks similar to this..
I'm trying to figure out a way to read the csv file, then split up the multiple 'All Accounts' values and 'Amt' Values so that M200 and 300.89 is another entry, M300 and 400.54 are another entry, and M400 and 100.00 are another entry. So after inserting this single row into the database, I should actually have 4 rows like so..
This is how I'm currently reading and inserting into the database.
List<RawData> data = new List<RawData>();
try
{
string text = File.ReadAllText(lblFileName.Text);
string[] lines = text.Split('\n');
int total = 0, reduced = 0;
foreach (string line in lines)
{
RawData temp = new RawData(line);
total++;
if (!(temp.FirstAccount.Length == 0 || temp.FirstAccount == "1ST-ACCT-NO"))
{
reduced++;
data.Add(temp);
}
}
}
catch (IOException ex)
{
Console.WriteLine("Unable to read file. " + ex.ToString());
MessageBox.Show(ex.ToString());
}
try
{
foreach (RawData rData in data)
{
tCarsInTransit cit = new tCarsInTransit
{
FIRST_ACCT_NO = rData.FirstAccount,
ACCOUNT_NO_DV = rData.AccountNoDv,
ACCT_NO = rData.AcctNo,
ACCT_NO_L = rData.AccNoL,
ACCT_NUM_DV = rData.AcctNumDv,
ACCT_PFX = rData.AcctPfx,
ACCT_PFX_PRT = rData.AcctPfxPrt,
ACCT_TYPE_DV = rData.AcctTypeDv,
ADV_NO = rData.AdvNo,
ALL_PRT_FLAG = rData.AllPrtFlag,
AMT = rData.Amt,
AMT_GLE = rData.AmtGle,
BASE_GLE = rData.BaseGle,
CNT_CAT = rData.CntCat,
COLD_PRT_FLAG = rData.ColdPrtFlag,
COST_DV = rData.CostDv,
COST_OVRD_FLAG_DV = rData.CostOvrdFlagDv,
CR_ACCT_DV = rData.CrAcctDv,
CR_ACCT_DV_GLE = rData.CrAcctDvGle,
CROSS_POSTING_FLAG = rData.CrossPostingFlag,
CROSS_POST_CAT = rData.CrossPostCat,
CTRL_NO = rData.CtrlNo,
CTRL_TYPE_DV = rData.CtrlTypeDv,
DESC_REQD_DV = rData.DescReqdDv,
DR_ACCT_DV = rData.DrAcctDv,
GL_DIST_ACCT_DV = rData.GLDistAcctDv,
GL_DIST_DV = rData.GLDistDv,
GRP_NO_DV = rData.GrpNoDv,
ID_PORT_DATE_TIME_FMT_CAT = rData.IdPortDateTimeFmtCat,
INACTIVITY_DV = rData.InactivityDv,
JOIN_COL = rData.JoinCol,
JRNL_DATE = rData.JrnlDate,
JRNL_PFX = rData.JrnlPfx
};
tCIT.tCarsInTransits.Add(cit);
tCIT.SaveChanges();
lblMessage.ForeColor = System.Drawing.Color.Green;
lblMessage.Text = "Finished uploading. ";
}
}
catch (DbEntityValidationException ex)
{
foreach (var eve in ex.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
I am not sure how to accomplish this. The above currently inserts the csv file into Sql Server the exact way the csv file was exported. Any ideas would greatly be appreciated! Thanks!
EDIT: Here is the RawData class.
class RawData
{
public string FirstAccount { get; set; }
public string AccountNoDv { get; set; }
public string AcctNo { get; set; }
public string AccNoL { get; set; }
public string AcctNumDv { get; set; }
public string AcctPfx { get; set; }
public string AcctPfxPrt { get; set; }
public string AcctTypeDv { get; set; }
public string AdvNo { get; set; }
public string AllPrtFlag { get; set; }
public string Amt { get; set; }
public string AmtGle { get; set; }
public string BaseGle { get; set; }
public string CntCat { get; set; }
public string ColdPrtFlag { get; set; }
public string CostDv { get; set; }
public string CostOvrdFlagDv { get; set; }
public string CrAcctDv { get; set; }
public string CrAcctDvGle { get; set; }
public string CrossPostingFlag { get; set; }
public string CrossPostCat { get; set; }
public string CtrlNo { get; set; }
public string CtrlTypeDv { get; set; }
public string DescReqdDv { get; set; }
public string DrAcctDv { get; set; }
public string GLDistAcctDv { get; set; }
public string GLDistDv { get; set; }
public string GrpNoDv { get; set; }
public string IdPortDateTimeFmtCat { get; set; }
public string InactivityDv { get; set; }
public string JoinCol { get; set; }
public string JrnlDate { get; set; }
public string JrnlPfx { get; set; }
public RawData(string csvString)
{
string[] citData = csvString.Replace(", ", "").Replace(".,", ".").Split(',');
try
{
FirstAccount = citData[0];
AccountNoDv = citData[1];
AcctNo = citData[2];
AccNoL = citData[3];
AcctNumDv = citData[4];
AcctPfx = citData[5];
AcctPfxPrt = citData[6];
AcctTypeDv = citData[7];
AdvNo = citData[8];
AllPrtFlag = citData[9];
Amt = citData[10];
AmtGle = citData[11];
BaseGle = citData[12];
CntCat = citData[13];
ColdPrtFlag = citData[14];
CostDv = citData[15];
CostOvrdFlagDv = citData[16];
CrAcctDv = citData[17];
CrAcctDvGle = citData[18];
CrossPostingFlag = citData[19];
CrossPostCat = citData[20];
CtrlNo = citData[21];
CtrlTypeDv = citData[22];
DescReqdDv = citData[23];
DrAcctDv = citData[24];
GLDistAcctDv = citData[25];
GLDistDv = citData[26];
GrpNoDv = citData[27];
IdPortDateTimeFmtCat = citData[28];
InactivityDv = citData[29];
JoinCol = citData[30];
JrnlDate = citData[31];
JrnlPfx = citData[32];
}
catch (Exception ex)
{
Console.WriteLine("Something went wrong. " + ex.ToString());
}
}
}
EDIT 2: AllAccounts in the images is acutally 'AccountNoDv' and there are actually many different fields that have multiples like 'AccountNoDv'(AllAccounts) but we might be removing those as this is not a final export. As of right now the two fields I'm worried most about are AccountNoDv and Amt.
Try something like this:
foreach (string line in lines)
{
RawData temp = new RawData(line);
var AllAccounts = temp.AccountNoDv.split(' ');
var Amts = temp.Amt.split(' ');
if (AllAccounts.Length() == Amts.Length() && Amts.Length() > 1) {
// We have multiple values!
reduced++;
for (int i = 0; i < AllAccounts.Length(); i++) {
RawData temp2 = RawDataCopy(temp); // Copy the RawData object
temp2.AccountNoDv = AllAccounts[i];
temp2.Amt = Amts[i];
total++;
data.Add(temp2);
}
}
else {
total++;
if (!(temp.FirstAccount.Length == 0 || temp.FirstAccount == "1ST-ACCT-NO"))
{
reduced++;
data.Add(temp);
}
}
}
And:
private RawData RawDataCopy(RawData copyfrom) {
// Write a function here that returns an exact copy from the one provided
// You might have to create a parameterless constructor for RawData
RawData RawDataCopy = new RawData();
RawDataCopy.FirstAccount = copyfrom.FirstAccount;
RawDataCopy.AccountNoDv = copyfrom.AccountNoDv;
RawDataCopy.AcctNo = copyfrom.AcctNo;
// . . . . . . . .
RawDataCopy.JrnlPfx = copyfrom.JrnlPfx;
return RawDataCopy;
}
Then also add a parameterless constructor to your RawData class:
public RawData()
{
}
Perhaps it would be sexier to implement the ICloneable interface and call the Clone() function instead of the RawDataCopy function, but it gets the idea across.
In Linq you can use SelectMany to increase the number of elements in a list. Here is a rough example of how this could be done. I make the assumption that the number of sub elements in AllAccounts and Amt is the same. A more robust solution would check for these issues.
So after you have loaded your data list:
var expandedData =
data.SelectMany(item =>
// split amount (will just return one item if no spaces)
item.Amt.Split(" ".ToCharArray())
// join this to the split of all accounts
.Zip(item.AllAccounts.Split(" ".ToCharArray()),
// return the joined item as a new anon object
(a,b) => new { amt=a, all=b }),
// take the original list item and the anon object and return our new item
(full,pair) => { full.Amt = pair.amt; full.AllAccounts = pair.all; return full; }));
You will now have a list of your data items with the multiple items expanded into the list.
I don't have test data to test so I might have some minor typos -- but I put in lots of comments to make the Linq as clear as possible.
Here is is simple example I wrote in LinqPad for myself to make sure I understood how SelectMany worked:
string [] list = { "a b c d","e","f g" };
var result = list.SelectMany((e) =>
e.Split(" ".ToCharArray()),
(o,item) => new { org = o, item = item}).Dump();

compare properties in classes of list in class

What I've got are two classes which each contain Lists of Classes with propperties of different types. The first list is an updated version of the second and i need to find all differences (deleted/added classes in lists and updated classes).
public class ClassOfKb
{
public List<Data> KbData {get;set;}
public List<Info> KbInfo {get;set;}
}
class Data
{
public Guid ID {get;set}
public byte[] file {get;set}
public string name {get;set}
}
class Info
{
public Guid ID {get;set}
public string text {get;set}
public DateTime date {get;set}
}
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
first KbA and KbB will be filled from the same DataSet, then i delete, add and modify some of KbA Child-Classes.
now i need to compare KbA with KbB to find out where the differences are. i need the ID of deleted or added classes in KbA and the exact changes of modified Child-Classes properties. How would i do this? Preffered with Linq.
I suggest that create two comparers one for Data and one for Info
class DataComparer : IEqualityComparer<Data>
{
public bool Equals(Data x, Data y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Data d)
{
//logic to return a hash code
}
}
class InfoComparer : IEqualityComparer<Info>
{
public bool Equals(Info x, Info y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Info i)
{
//logic to return a hash code
}
}
The you can use Intersect and Except LINQ methods
IEnumerable<Data> DataInAandNotInB = KbA.KbData.Except(KbB.KbData,new DataComparer());
IEnumerable<Info> InfoInAandInB = KbA.KbInfo.Intersect(KbB.KbInfo,new InfoComparer ());
For simplicity, I skipped comparison of the byte array and DateTime data membes, only left the IDs and the string data members, but to add them you will need some small modification.
The test is very-very basic, but shows all three of the changes options:
static void Main(string[] args)
{
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
// Test data --------
Data data1 = new Data() { ID = Guid.NewGuid(), name = "111" };
Data data2 = new Data() { ID = Guid.NewGuid(), name = "222" };
Data data2_changed = new Data() { ID = data2.ID, name = "222_changed" };
Data data3 = new Data() { ID = Guid.NewGuid(), name = "333" };
Info info1 = new Info() { ID = Guid.NewGuid(), text = "aaa" };
Info info2 = new Info() { ID = Guid.NewGuid(), text = "bbb" };
Info info2_changed = new Info() { ID = info2.ID, text = "bbb_changed" };
Info info3 = new Info() { ID = Guid.NewGuid(), text = "ccc" };
KbA.KbData.Add(data1);
KbA.KbData.Add(data2);
KbA.KbInfo.Add(info1);
KbA.KbInfo.Add(info2);
KbB.KbData.Add(data2_changed);
KbB.KbData.Add(data3);
KbB.KbInfo.Add(info2_changed);
KbB.KbInfo.Add(info3);
// end of test data ---------
// here is the solution:
var indexes = Enumerable.Range(0, KbA.KbData.Count);
var deleted = from i in indexes
where !KbB.KbData.Select((n) => n.ID).Contains(KbA.KbData[i].ID)
select new
{
Name = KbA.KbData[i].name,
KbDataID = KbA.KbData[i].ID,
KbInfoID = KbA.KbInfo[i].ID
};
Console.WriteLine("deleted:");
foreach (var val in deleted)
{
Console.WriteLine(val.Name);
}
var added = from i in indexes
where !KbA.KbData.Select((n) => n.ID).Contains(KbB.KbData[i].ID)
select new
{
Name = KbB.KbData[i].name,
KbDataID = KbB.KbData[i].ID,
KbInfoID = KbB.KbInfo[i].ID
};
Console.WriteLine("added:");
foreach (var val in added)
{
Console.WriteLine(val.Name);
}
var changed = from i in indexes
from j in indexes
where KbB.KbData[i].ID == KbA.KbData[j].ID &&
(//KbB.KbData[i].file != KbA.KbData[j].file ||
KbB.KbData[i].name != KbA.KbData[j].name ||
//KbB.KbInfo[i].date != KbA.KbInfo[j].date ||
KbB.KbInfo[i].text != KbA.KbInfo[j].text
)
select new
{
Name = KbA.KbData[j].name,
KbDataID = KbA.KbData[j].ID,
KbInfoID = KbA.KbInfo[j].ID
};
Console.WriteLine("changed:");
foreach (var val in changed)
{
Console.WriteLine(val.Name);
}
Console.ReadLine();
}
}
public class ClassOfKb
{
public List<Data> KbData = new List<Data>();
public List<Info> KbInfo = new List<Info>();
}
public class Data
{
public Guid ID { get; set; }
public byte[] file { get; set; }
public string name { get; set; }
}
public class Info
{
public Guid ID { get; set; }
public string text { get; set; }
public DateTime date { get; set; }
}

Categories

Resources