EF core Update entry then Insert - c#

So i want to update an entry's Valid to column, and then insert a new copy of it with a new value. The issue is it seems to skip the update statement, and just inserts a new copy of it.
foreach (Model data in Data)
{
var entry = context.table.Where(x=>x.id == data.id).FirstOrDefault();
entry.ValidTo = DateTime.Now;
ctx.Update(entry);
entry.id = 0;
entry.ValidTo = new DateTime(9999, 12, 31);
entry.ValidFrom = DateTime.Now;
entry.Value = adjustmentmodel.Value;
ctx.Add(entry);
}
ctx.SaveChanges();
I tried inserting a saveChanges after ctx.update(entry), and that works, but is pretty slow. So i wanted to hear if there was a way, to only have to save the changes at the end?
I am using dotnet 5 and EF core 5.0.17

Separate your entity references, there is no reason to re-use it.
foreach (Model data in Data)
{
// Update the entity
var entry = context.table.Where(x => x.id == data.id).FirstOrDefault();
entry.ValidTo = DateTime.Now;
// Add a new entry
ctx.Add(new Entry
{
// what you need for the new entry
});
}
// Save the changes within a single transaction
ctx.SaveChanges();

Please try using UpdateRange and AddRange method once.
var entry = Context.table.Where(x => Data.Select(y => y.id).Contains(x.id)).ToList();
entry = entry.ForEach(res =>
{
res.ValidTo = DateTime.Now
}).ToList();
ctx.UpdateRange(entry);
entry = entry.ForEach(res =>
{
res.ValidTo = new DateTime(9999, 12, 31);
res.ValidFrom = DateTime.Now;
res.Value = adjustmentmodel.Value;
}).ToList();
ctx.AddRange(entry);
ctx.SaveChanges();

Related

How to add distinct value in database using Entity Framework

IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
}
}
db.SaveChanges();
I want to add only distinct values to database in above code. Kindly help me how to do it as I am not able to find any solution.
IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
var a = db.WebsiteWebPages.Where(i => i.WebPage == value.WebPage.ToString()).ToList();
if (a.Count == 0)
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
db.SaveChanges();
}
}
}
This is the code that I used to add distinct data.I hope it helps
In addition to the code sample Furkan Öztürk supplied, Make sure your DB has a constraint so that you cannot enter duplicate values in the column. Belt and braces approach.
I assume that by "distinct values" you mean "distinct value.WebPage values":
// get existing values (if you ever need this)
var existingWebPages = db.WebsiteWebPages.Select(v => v.WebPage);
// get your pages
var webPages = GetWebPages().Where(v => v.WebPage.Contains(".htm"));
// get distinct WebPage values except existing ones
var distinctWebPages = webPages.Select(v => v.WebPage).Distinct().Except(existingWebPages);
// create WebsiteWebPage objects
var websiteWebPages = distinctWebPages.Select(v =>
new WebsiteWebPage { WebPage = v, WebsiteId = websiteid});
// save all at once
db.WebsiteWebPages.AddRange(websiteWebPages);
db.SaveChanges();
Assuming that you need them to be unique by WebPage and WebSiteId
IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
if (db.WebsiteWebPages.All(c=>c.WebPage != value.WebPage|| c.WebsiteId != websiteid))
{
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
}
}
}
db.SaveChanges();
UPDATE
To optimize this (given that your table contains much more data than your current list), override your equals in WebsiteWebPage class to define your uniqueness criteria then:
var myWebsiteWebPages = data.select(x=> new WebsiteWebPage { WebPage = x.WebPage, WebsiteId = websiteid}).Distinct();
var duplicates = db.WebsiteWebPages.Where(x=> myWebsiteWebPage.Contains(x));
db.WebsiteWebPages.AddRange(myWebsiteWebPages.Where(x=> !duplicates.Contains(x)));
this is a one database query to retrieve ONLY duplicates and then removing them from the list
You can use the following code,
IEnumerable<WebsiteWebPage> data = GetWebPages();
var templist = new List<WebsiteWebPage>();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
templist.Add(pagesinfo);
}
}
var distinctList = templist.GroupBy(x => x.WebsiteId).Select(group => group.First()).ToList();
db.WebsiteWebPages.AddRange(distinctList);
db.SaveChanges();
Or you can use MoreLINQ here to filter distinct the list by parameter like,
var res = tempList.Distinct(x=>x.WebsiteId).ToList();
db.WebsiteWebPages.AddRange(res);
db.SaveChanges();

Getting double data in DB

I pull student data from 2 databases. 1 from an online SOAP API which can handle async calls and 1 from a local DB with an older services that doesnt support async.
I compare these databases and write the differences in a local sqlDB through EF.
Problem:
I get double entries in my EF DB. He puts the correct data and amount in arrays inside the method, but it looks like once he hits the db.savechanges() he jumps back up a few line and saves again.
I don't even know where this extra thread comes from.
Some code might be still there from numerous tries to solve it. For instance I tried with addrange but I get an error when he tries to add the FullVarianceList.
public async Task<bool> FullStudentCompare(string date) //format DD/MM/YYYY
{
try
{
//DB context
using (var db = new SchoolDbContext())
{
//GET DATA
//SMT (async)
List<SmtStudent> smtStdudentList = await GetAllSmartschoolStudents();
//Wisa (sync)
//on date, or if emty on current systemdate
List<WisaStudent> wisaList;
if (date == "")
{
wisaList = GetWisaStudentData(DateTime.Now.ToShortDateString());
}
else
{
wisaList = GetWisaStudentData(date);
}
//Flags and props needed for DB entry after compare
bool existsInLocalDb = false;
List<Variance> vList = new List<Variance>();
//Full list to add to DB outside foreach
List<Variance> fullVarianceList = new List<Variance>();
//Full List of new Students to write to DB outside foreach
List<DbStudent> fullStudentList = new List<DbStudent>();
//Compare lists
foreach (WisaStudent wstd in wisaList)
{
//determine correct classCode
string klasCode;
if (wstd.klasgroep.Trim() == "Klasgroep 00")
{
klasCode = wstd.klas.Trim();
}
else
{
klasCode = wstd.klasgroep.Trim();
}
//Create SmtStudent object for compare
SmtStudent tempStd = new SmtStudent(true,
wstd.voornaam.Trim(),
wstd.naam.Trim(),
wstd.stamboeknummer.Trim(),
wstd.geslacht.Trim(),
wstd.geboortedatum.Trim(),
wstd.straat.Trim(),
wstd.huisnummer.Trim(),
wstd.busnummer.Trim(),
wstd.postcode.Trim(),
wstd.gemeente.Trim(),
wstd.emailadres.Trim(),
wstd.GSM_nummer.Trim(),
wstd.levensbeschouwing.Trim(),
wstd.coaccountmoedervoornaam.Trim(),
wstd.coaccountmoedernaam.Trim(),
wstd.coaccountmoederemailadres.Trim(),
wstd.coaccountmoederGSM_nummer.Trim(),
wstd.coaccountvadervoornaam.Trim(),
wstd.coaccountvadernaam.Trim(),
wstd.coaccountvaderemailadres.Trim(),
wstd.coaccountvaderGSM_nummer.Trim(),
klasCode,
wstd.nationaliteit,
wstd.geboorteGemeente,
wstd.geboorteLand
);
//Find matching SmtStudent
SmtStudent smtStd = smtStdudentList.Find(i => i.Internnummer == wstd.stamboeknummer);
//Find matching Std in local DB
DbStudent dbStd = await db.Students.Where(i => i.Stamboeknummer == wstd.stamboeknummer).FirstOrDefaultAsync();
//if none exists in the local DB create an entity to update and write to DB
if (dbStd == null)
{
dbStd = new DbStudent(wstd.voornaam.Trim(),
wstd.naam.Trim(),
wstd.stamboeknummer.Trim(),
wstd.geslacht.Trim(),
wstd.geboortedatum.Trim(),
wstd.straat.Trim(),
wstd.huisnummer.Trim(),
wstd.busnummer.Trim(),
wstd.postcode.Trim(),
wstd.gemeente.Trim(),
wstd.emailadres.Trim(),
wstd.GSM_nummer.Trim(),
wstd.levensbeschouwing.Trim(),
wstd.coaccountmoedervoornaam.Trim(),
wstd.coaccountmoedernaam.Trim(),
wstd.coaccountmoederemailadres.Trim(),
wstd.coaccountmoederGSM_nummer.Trim(),
wstd.coaccountvadervoornaam.Trim(),
wstd.coaccountvadernaam.Trim(),
wstd.coaccountvaderemailadres.Trim(),
wstd.coaccountvaderGSM_nummer.Trim(),
klasCode,
wstd.loopbaanDatum,
wstd.nationaliteit,
wstd.geboorteGemeente,
wstd.geboorteLand
);
db.Students.Add(dbStd);
fullStudentList.Add(dbStd);
}
else
{
existsInLocalDb = true;
}
if (smtStd == null)
{
//Std doesn't exist in Smt -> New student
dbStd.IsNewStudent = true;
dbStd.ClassMovement = true;
//remove from wisaList
wisaList.Remove(wstd);
}
else
{
//clear vlist from previous iterations
vList.Clear();
//get all properties on the obj, cycle through them and find differences
PropertyInfo[] props = smtStd.GetType().GetProperties();
vList.AddRange(props.Select(f => new Variance
{
Property = f.Name,
ValueA = f.GetValue(smtStd),
ValueB = f.GetValue(tempStd),
Checked = false
})
.Where(v => !v.ValueA.Equals(v.ValueB) && v.ValueB != null)
.ToList());
//If the users allrdy exists in LocalDb delete all previously recorded variances
if (existsInLocalDb)
{
if (db.Variances.Where(j => j.Student.StudentId.Equals(dbStd.StudentId)).FirstOrDefault() != null)
{ //if the student allready exists we will recreate the variancelist, hence deleting all current items first
List<Variance> existingList = db.Variances.Where(j => j.Student.StudentId.Equals(dbStd.StudentId)).ToList();
foreach (Variance v in existingList)
{
db.Variances.Remove(v);
}
}
}
//Add new variances if vList is not empty
if (vList.Count > 0)
{
//Check if KlasCode is a variance -> set classmovement to true
if (vList.Where(i => i.Property == "KlasCode").FirstOrDefault() != null)
{
dbStd.ClassMovement = true;
}
else
{
dbStd.ClassMovement = false;
}
//add the StudentObject to the variance to link them 1-many
foreach (Variance v in vList)
{
v.Student = dbStd;
fullVarianceList.Add(v);
db.Variances.Add(v);
}
}
}
}
//add the full lists of variances and new students to DB
//db.Variances.AddRange(fullVarianceList);
//db.Students.AddRange(fullStudentList);
db.SaveChanges();
return true;
}
}
catch(Exception ex)
{
return false;
}
}
A couple of things:
It is important to understand that EF uses a unit of work pattern where none of the changes to the entities are persisted until SaveChanges is called which explains the "once he hits the db.Savechanges() he jumps back up" phenomenon.
When you have a 1 to many relationsship and you assign a collection of entities to a navigation property on another entity and then add that parent entity to the DbContext, EF marks those child entities to be added too. In your case dbStd is added at the line "db.Students.Add(dbStd);" and at the line "v.Student = dbStd;". This is most likely what is causing your duplicates.

Update multiple columns in Entity Framework

I want to update multiple columns in Entity Framework. I now use this :
var user = new Relations { Id=1, Status = 1, Date = DateTime.Now, Notification = 0 };
db.Relations.Attach(user);
db.Entry(user).Property(x => x.Status).IsModified = true;
db.Entry(user).Property(x => x.Notification).IsModified = true;
db.Entry(user).Property(x => x.Date).IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
Is there a better way to update columns without repeating the code db.Entry(user).Property several times ?
you can Use EntityState Like this:
var user=db.users.Find(userId);
user.name="new name";
user.age=txtAge.text;
user.address=txtAddress.text;
context.Entry(user).State=Entitystate.Modified;
I prefer use:
var existingUser = context.Set<User>().Where(u => u.Id == 1);
context.Entry(existingUser).CurrentValues.SetValues(user);
Or you can use a 3rd lib like GraphDiff.
Yo update an entity you don't need to do this:
// build your entity object with new values
var user = new Relations { Id=1, Status = 1, Date = DateTime.Now, Notification = 0 };
//attach as modified in one single step
db.Entry(user).State = EntityState.Modified;
//execute update
db.SaveChanges();
This assumes you are setting all entity fields and there isn't RowVersion field in your entity. Extra steps would be required to manage these other situations.
Try this,
using (var db = new YourDb())
{
try
{
db.Entry(user).State = EntityState.Modified;
}
catch (Exception)
{
return false;
}
db.SaveChanges();
return true;
}
When an item is fetched via the context it is
automatically tracked in that context unless you change the default behavior.
So you could simple do:
var txtInputAge = 18;
var txtAdrressLine1 = "Some cool place"
//fetch the user
var user = db.Users.Find(userId);
//change the properties
user.Name = "new cooler name";
user.Age = txtInputAge;
user.Address = txtAdrressLine1;
//call save changes
db.SaveChanges();
Update - Add would look like
//create new entry
User user = new User();
user.Name = "new cooler name";
user.Age = txtInputAge;
user.Address = txtAdrressLine1;
//add to context
db.Users.Add(user);
//call save changes
db.SaveChanges();
using (var dbcontext = new MyModel())
{
var matchedRecords = dbcontext.DummyTable.Where(e => e.code.Equals(entry.code) &&
e.isValid.Equals(true)).ToList();
matchedRecords.ForEach(e => e.isValid = false);
dbcontext.SaveChanges();
}

SQL Server CE EF Optimization

I have the following code which updates / adds data to a SQL Server CE database with EF6, which is working fine for small number of records. However when the volume of records exceeds 1000~2000 the transaction become very slow (10~15sec). Is there any way to optimize it?
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0", "", MyProject.ConnectionString);
ProjectContext context = new ProjectContext();
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
using (var db = new ProjectContext())
{
foreach (var item in MyProject.Brands)
{
if (!db.Brands.Any(i => i.Name == item.Name))
{
// Add
db.Brands.Add(item);
}
else
{
// Update
var found = db.Brands.First(i => i.Name == item.Name);
found = item;
}
}
db.SaveChangesAsync();
This way you save 50% on your Linq queries, and your update might actually work (your MyProject.Brands are not attached in any way to your new ProjectContext that you are using, the found that you extract from there is attached, but in your original code, you overwrote it with yuor item, meaning it will be ignored.
context I have left out completely, as you were not doing anything with it, as your using creates a new ProjectContext anyway.
using (var db = new ProjectContext())
{
foreach (var item in MyProject.Brands)
{
var found = db.Brands.FirstOrdefault(i => i.Name == item.Name);
if (found == null)
{
// Add
db.Brands.Add(item);
}
else
{
// Update
found.prop1 = item.prop1;
found.prop2 = item.prop2;
// ... etc, for all and any updatable properties
}
}
db.SaveChangesAsync();
}

Unable to determine the principal end of the etaxiDataModel relationship. Multiple added entities may have the same primary key

I have been struggling with this problem for some time now and am not able to resolve this. Have read multiple posts and tried several different solutions but nothing has worked. Now I feel like I have come to a stop and really need help with this.
I am using EF 5+ with an DB first and edmx file. I have 3 different tables in my DB:
1. Settlement
2. Cost
3. Shift
Settlement has a collection of both Cost and Shift (with a link table) connected by Association in my edmx file.
I need to insert a new Settlement in my db with a reference to an already existing Cost and Shift collections.
Shifts and Costs included in my Settlement entity I am trying to insert contains all there related data and none of those are modified in any way (same as I retrieved from db).
Here in my method of inserting the entity into my db.
public bool CreateSettlement(Settlement settlement)
{
bool _success;
var _context = new EtaxiEnteties();// ObjectFactory.Get<IETaxiEntitiesContext>();
try
{
var _newSettlement = new Settlement
{
CreateDate = settlement.CreateDate,
Driver = settlement.Driver,
DriverID = settlement.DriverID,
Car = settlement.Car,
CarID = settlement.CarID,
DocPath = settlement.DocPath
};
foreach (var _shift in settlement.Shifts)
{
//var _sh = _context.Shifts.Find(_shift.ShiftID);
//_context.Entry(_sh).CurrentValues.SetValues(_shift);
_newSettlement.Shifts.Add(_shift);
}
foreach (var _cost in settlement.Costs)
{
////var _sh = _context.Costs.Find(_cost.CostID);
////_context.Entry(_sh).CurrentValues.SetValues(_cost);
_newSettlement.Costs.Add(_cost);
}
_context.Settlements.Add(_newSettlement);
_success = _context.SaveChanges() > 0;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return _success;
}
Any help on the issue would be MUCH appreciated.
Here is how I am adding the Cost and Shift to my collection:
I create a Settlement in page:
_settlement = new Settlement
{
CreateDate = DateTime.Now,
Driver = _driver,
DriverID = _driver.DriverID,
Car = _car,
CarID = _car.CarID,
DocPath = _path
};
then when I create a pdf file with selected rows from 2 separated grid views:
foreach (GridDataItem _selectedRow in gwShifts.MasterTableView.Items)
{
if (_selectedRow.Selected)
{
var _shift =
_diaryRepository.GetShiftByID((int) _selectedRow.GetDataKeyValue("ShiftID")).FirstOrDefault();
if (_shift != null)
{
_settlement.Shifts.Add(_shift);
_settlementData.Shifts.Add(_shift);
_settlementData.SplitPercentace = GetTemplateValue(_selectedRow, "lblSplit");
_settlementData.SettlementAmount = GetTemplateValue(_selectedRow, "lblSettlementAmount");
if (_settlementData.Shifts != null)
{
_tableShifts.AddCell(
new PdfPCell(
new Phrase(
_settlementData.Shifts.FirstOrDefault().ShiftDate.ToShortDateString(),
_bodyFont)) {Border = 0});
_tableShifts.AddCell(
new PdfPCell(
new Phrase(
string.Format("{0:c}", _settlementData.Shifts.FirstOrDefault().GrossAmount),
_bodyFont)) {Border = 0});
_tableShifts.AddCell(
new PdfPCell(
new Phrase(
string.Format("{0:c}", _settlementData.Shifts.FirstOrDefault().MoneyAmount),
_bodyFont)) {Border = 0});
_tableShifts.AddCell(new PdfPCell(new Phrase(_settlementData.SplitPercentace, _bodyFont))
{Border = 0});
_tableShifts.AddCell(
new PdfPCell(new Phrase(_settlementData.SettlementAmount, _boldTableFont))
{Border = 0});
_totalAmount.AddRange(new[]
{
Convert.ToInt32(
_settlementData.SettlementAmount.Replace(".", "").
Replace(",", "").Replace("kr", ""))
});
_settlementData.Shifts.Remove(_shift);
}
}
}
}
var _summaryCell =
new PdfPCell(new Phrase("Upphæð: " + string.Format("{0:c}", _totalAmount.Sum()), _boldTableFont))
{
Border = 0,
Colspan = 5,
HorizontalAlignment = Element.ALIGN_RIGHT,
Padding = 5,
BorderWidthTop = 1
};
_tableShifts.AddCell(_summaryCell);
if (_totalAmount.Count != 0)
_totalAmount.Clear();
}
there you see how I add the Shift to this Settlement entity:
var _shift =
_diaryRepository.GetShiftByID((int) _selectedRow.GetDataKeyValue("ShiftID")).FirstOrDefault();
if (_shift != null)
{
_settlement.Shifts.Add(_shift);
then I send this to the reporistory (see method above)
if(_driverRepository.CreateSettlement(_settlement))
{
SetMessage("Uppgjör hefur verið skapað og sent bílstjóra ef e-póstur er skráður á viðkomandi bílstjóra.", "Uppgjör skapað");
pnlSettlement.Visible = false;
pnlDocCreation.Visible = false;
pnlResult.Visible = false;
}
I also tried to simply add param settlement directly to the context but got similar error.
I think this has to do with how you're populating the Shifts and Costs collection. You're trying to add already created records (i.e. they already have their primary key values set) to be saved with the new Settlement entity, but I believe that Entity Framework isn't trying to create new ones that are linked to your new Settlement entity but rather save them to the table as is. In such a case you would indeed have a situation where multiple entities have the same primary key.
I would try the following (I'll show you using the Shifts loop only, but you should be able to apply it to the Costs loop as well):
foreach (var _shift in settlement.Shifts)
{
var newShift = new Shift { /*Copy all of the values from _shift here*/ };
_newSettlement.Shifts.Add(_shift);
context.Shifts.Add(newShift);
}
If that doesn't work I would suggest debugging Costs and Shifts to make sure that you don't have any duplicates in those collections.
If you don't want new Shifts & Costs then I can only assume you need to repoint the existing ones to the new Settlement
foreach (var shift in settlement.Shifts)
{
//either
shift.Settlement = newSettlement;
//or
shift.SettlementId = newSettlement.SettlementId;
//depending on your object model
}
I've just realised that I have misunderstood the question. There are 2 additional tables not shown in the diagram (Costs & Shifts). The problem is trying to create SettlementCost and SettlementShift entities that connect Settlement to Costs\Shifts.
OK, came up with a "ugly" solution on this..but it is resolved.
Changed the Model to be one(Settlement) -> many(Shift or Cost) relationship.
I create a new Settlement and save this one to the DB.
Retrieve each Shift and Cost from the DB update SettlementID on each and save these to the DB.
try
{
var _newSettlement = new Settlement
{
CreateDate = settlement.CreateDate,
DriverID = settlement.DriverID,
CarID = settlement.CarID,
DocPath = settlement.DocPath
};
Add(_newSettlement);
_success = SaveWithSuccess() > 0;
var _settlement = GetAll().FirstOrDefault(x => x.SettlementID == _newSettlement.SettlementID);
if (_success)
{
foreach (var _shift in settlement.Shifts)
{
var _sh = _diaryRepository.GetShiftByID(_shift.ShiftID).FirstOrDefault();
_sh.SettlementID = _settlement.SettlementID;
_diaryRepository.UpdateShift(_sh);
}
foreach (var _cost in settlement.Costs)
{
var _ch = _costRepository.GetCostByID(_cost.CostID);
_ch.SettlementID = _settlement.SettlementID;
_costRepository.UpdateCost(_ch);
}
}
}
not a pretty one, but it resolves the problem.
I am not concerned about the DB request load..it will not be that high in this case.
I would think there is a nicer solution to this but I was not able to find it at this time.
Thanks for all your efforts to help :)

Categories