Data model: Song - Artists (N:N)
I'm trying to seed my database, but for some reason my Artists are getting duplicate after the seed method has ran. I'm not sure why...
CreateSong("COLDPLAY", "Viva la vida", context);
CreateSong("COLDPLAY", "UP & UP", context);
CreateSong("COLDPLAY", "UP & UP", context);
CreateSong method:
public Song CreateSong(String artistName, String title, NeosicDbContext context)
{
var ret = context.Songs.FirstOrDefault(s => s.Title == title && s.Artists.FirstOrDefault(a => a.Name == artistName) != null);
if(ret != null)
{
return ret;
}
**var artists = new List<Artist>();
var artist = context.Artists.FirstOrDefault(a => a.Name == artistName);
if (artist == null)
{
artist = new Artist { Name = artistName };
context.Artists.Add(artist);
}
artists.Add(artist);**
var tags = new List<Tag>();
tags.Add(defaultTag);
ret = new Song {
Title = title,
Artists = artists,
Tags = tags
};
context.Songs.Add(ret);
//context.MarkAsModified(ret);
return ret;
}
Db result:
3 artists, while I'm only expecting one.
Tried both with context.Artists.Add(artist); and without but results remains the same
the problem comes here:
var artist = context.Artists.FirstOrDefault(a => a.Name == artistName);
this query systematically hits the db. But, before SaveChanges, the db is empty.
So, you should do something like:
//check the context (I would like to say the *cache*, but it is a false friend in this case)
var artist = context.Artists.Local.FirstOrDefault(a => a.Name == artistName);
if ( artist == null) {
//then hit the db
artist = context.Artists.FirstOrDefault(a => a.Name == artistName);
}
Related
I got the error in the title while editing relational table between many-to-many relationship. It will not let duplicate on table, so I try to remove rows and then create new one but it didn't work.
public void Update(ThermoformProduct entity, int[] thermoformCategoryIds)
{
using (var context = new ShopContext())
{
var product = context.ThermoformProducts
.Include(i => i.ThermoformProductCategories)
.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
product.Code = entity.Code;
product.Culture = entity.Culture;
product.Renk = entity.Renk;
product.UstGenislik = entity.UstGenislik;
product.UstCap = entity.UstCap;
product.AltCap = entity.AltCap;
product.TbCap = entity.TbCap;
product.Yukseklik = entity.Yukseklik;
product.Hacim = entity.Hacim;
product.TamHacim = entity.TamHacim;
product.Baski = entity.Baski;
product.SosisIciAdet = entity.SosisIciAdet;
product.KoliIciAdet = entity.KoliIciAdet;
product.ImageUrl = entity.ImageUrl;
product.ThermoformProductCategories.RemoveAll(s=>s.ProductId == product.ProductId);
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
ThermoformProduct = product,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
context.SaveChanges();
}
}
}
EF cannot track two different instance of an entity type with same primary key. You have included the related ThermoformProductCategory entities, so they are being tracked by the context. When you remove them, they are cleared from the ThermoformProductCategories property of that product, but they are not removed from the context, and are still being tracked. Finally when you create the new list of ThermoformProductCategory, some of the new ones' primary key are matching the previous ones' (which already exist in the context)
Since you are creating the entire list again, you don't need to fetch the related entities in the first place. Simply assign a new list and EF will replace the entire list of related entities -
var product = context.ThermoformProducts.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
// set all the properties
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
CategoryId = catid
}).ToList();
context.SaveChanges();
}
Two things:
The filter is redundant - the ThermoformProductCategories navigation property should already be filtered.
product.ThermoformProductCategories.RemoveAll(s=>s.ProductId == product.ProductId);
instead do this:
product.ThermoformProductCategories.RemoveAll(); // Or .Clear()
Don't set navigation properties in this case, just the foreign key values - this should resolve your issue:
Instead of:
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
ThermoformProduct = product,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
Do:
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId, // Even this might be redundant since you're adding to the product navigation property list.
CategoryId = catid
}).ToList();
both answers was solution here is my final form if anyone gets trouble
public void Update(ThermoformProduct entity, int[] thermoformCategoryIds)
{
using (var context = new ShopContext())
{
var product = context.ThermoformProducts
.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
product.Code = entity.Code;
product.Culture = entity.Culture;
product.Renk = entity.Renk;
product.UstGenislik = entity.UstGenislik;
product.UstCap = entity.UstCap;
product.AltCap = entity.AltCap;
product.TbCap = entity.TbCap;
product.Yukseklik = entity.Yukseklik;
product.Hacim = entity.Hacim;
product.TamHacim = entity.TamHacim;
product.Baski = entity.Baski;
product.SosisIciAdet = entity.SosisIciAdet;
product.KoliIciAdet = entity.KoliIciAdet;
product.ImageUrl = entity.ImageUrl;
var cmd = "delete from ThermoformProductCategory where ProductId=#p0";
context.Database.ExecuteSqlRaw(cmd, product.ProductId);
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
context.Entry(product).State = EntityState.Modified;
context.SaveChanges();
}
}
}
I am trying to move a card I have found using the search function from one list to another on the same board.
I have tried to set up a new list using the a new factory.list and searching for the list using FirstorDefault LINQ Query but keep getting an Error.
Below is some code I have tried to move my cards.
string query = jnum;
var search = factory.Search(query, 1, SearchModelType.Cards, new IQueryable[] { board });
await search.Refresh();
var CardList = search.Cards.ToList();
foreach (var card in CardList)
{
string tName = card.Name.Substring(0, 6);
if (tName == jnum.Trim())
{
cardid = card.Id;
}
}
var FoundCard = factory.Card(cardid);
string FoundListid = FoundCard.List.Id;
var fromlist = factory.List(FoundListid);
if (Item != null)
{
if (mail.Body.ToUpper().Contains("Approved for Print".ToUpper()))
{
//var ToList = factory.List("5db19603e4428377d77963b4");
var ToList = board.Lists.FirstOrDefault(l => l.Name == "Signed Off");
FoundCard.List = ToList;
// from on proof
//MessageBox.Show("Approved for Print");
}
else if (mail.Body.ToUpper().Contains("Awaiting Review".ToUpper()))
{
//var ToList = factory.List("5db19603e4428377d77963b3");
var ToList = board.Lists.FirstOrDefault(l => l.Name == "On Proof");
FoundCard.List = ToList;
// from in progress or to start
// MessageBox.Show("Awaiting Review");
}
else if (mail.Body.ToUpper().Contains("Amends".ToUpper()))
{
var ToList = factory.List("5dc9442eb245e60a39b3d4a7");
FoundCard.List = ToList;
// from on proof
//MessageBox.Show("Amends");
}
else
{
// non job mail
}
}
I keep getting a is not a valid value Error.
Thanks for help
I'm having a student model in which i have list of phone numbers and addresses.When i update the student the related data(phone and address) needs to be updated. I have written a PUT action in my student controller for that. It works fine, but I'm concerned about the efficiency of the query. Please check the code and suggest me improvisation if any. Thanks
public async Task<IActionResult> Put(long id, [FromBody] Student student)
{
var p = await _Context.Students
.Include(t => t.PhoneNumbers)
.Include(t => t.Addresses)
.SingleOrDefaultAsync(t => t.Id == id);
if (p == null)
{
return NotFound();
}
_Context.Entry(p).CurrentValues.SetValues(student);
#region PhoneNumber
var existingPhoneNumbers = p.PhoneNumbers.ToList();
foreach (var existingPhone in existingPhoneNumbers)
{
var phoneNumber = student.PhoneNumbers.SingleOrDefault(i => i.Id == existingPhone.Id);
if (phoneNumber != null)
_Context.Entry(existingPhone).CurrentValues.SetValues(phoneNumber);
else
_Context.Remove(existingPhone);
}
// add the new items
foreach (var phoneNumber in student.PhoneNumbers)
{
if (existingPhoneNumbers.All(i => i.Id != phoneNumber.Id))
{
p.PhoneNumbers.Add(phoneNumber);
}
}
#endregion
#region Address
var existingAddresses = p.Addresses.ToList();
foreach (var existingAddress in existingAddresses)
{
var address = student.Addresses.SingleOrDefault(i => i.Id == existingAddress.Id);
if (address != null)
_Context.Entry(existingAddress).CurrentValues.SetValues(address);
else
_Context.Remove(existingAddress);
}
// add the new items
foreach (var address in student.Addresses)
{
if (existingAddresses.All(i => i.Id != address.Id))
{
p.Addresses.Add(address);
}
}
#endregion
await _Context.SaveChangesAsync();
return NoContent();
}
Searching over small in-memory collections is not normally something you would worry about. So if a Student has dozens or hundreds of addresses, the repeated lookups are not going to be significant, especially compared with the time required to write to the database.
If you did want to optimize, you can copy the students Addresses to a Dictionary. Like this:
var existingAddresses = p.Addresses.ToList();
var studentAddresses = student.Addresses.ToDictionary(i => i.Id);
foreach (var existingAddress in existingAddresses)
{
if (studentAddresses.TryGetValue(existingAddress.Id, out Address address))
{
_Context.Entry(existingAddress).CurrentValues.SetValues(address);
}
else
{
_Context.Remove(existingAddress);
}
}
A query over an in-memory collection like this:
var address = student.Addresses.SingleOrDefault(i => i.Id == existingAddress.Id);
Will simply iterate all the student.Addresses comparint the Ids. A Dictionary<> acts like an index, providing very fast lookups.
As I build a project with Entity Framework 6 (using EF for the first time), I noticed that when I only Update the relationships of an Entity, EF updates the main Entity too.
I can tell this is happening because I'm using System Versioned tables on Sql Server 2017.
This is a made up scenario, but most of the concept is here.
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
location.Name = locationsViewModel.Name;
// ... other properties
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
I assume, that by marking the entire Entity as Modified, EF will call the update statement.
How can I avoid an UPDATE to the parent Entity, if no properties have changed on the parent, but still Add/Update the child relationships?
I assume I have to check that each property has not changed and therefore I should not be setting location state to Modified, but how would I handle the newly added Times?
Update #1
So I tried what I mentioned and it works, but is this the correct way to do this?
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
/*******************
This is new part
*******************/
if (
location.Name != locationsViewModel.Name
// || ... test other properties
) {
location.Name = locationsViewModel.Name;
// ... other properties
_db.Entry(location).State = EntityState.Modified;
} else {
_db.Entry(location).State = EntityState.Unchanged;
}
/*******************/
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
/* removed, added above */
//_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
So after trial and error, I guess I misunderstood how EF handles the EntityState. I though if a child was Modified, you had to set the parent as Modified as well.
Gladly, that's not the case and the code below works as desired.
public async Task<ActionResult> Edit([Bind(Include="Id,Name,LocationTimes")] LocationViewModel locationVM) {
if (ModelState.IsValid) {
var location = await _db.Locations.FirstOrDefaultAsync(l => l.Id == locationsViewModel.Id && l.UserId == UserId);
if (location == null) {
return HttpNotFound();
}
/*******************
This is new part
check if at least one property was changed
*******************/
if (
location.Name != locationsViewModel.Name
|| location.Ref != locationsViewModel.Ref
// || ... test other properties
) {
location.Name = locationsViewModel.Name;
location.Ref = locationsViewModel.Ref;
// ... other properties
// Tell EF that the Entity has been modified (probably not needed, but just in case)
_db.Entry(location).State = EntityState.Modified;
} else {
// Tell EF that the Entity has *NOT* been modified
_db.Entry(location).State = EntityState.Unchanged;
}
/*******************/
foreach (var day in locationsViewModel.LocationTimes.Days) {
var time = new Time {
Day = day.Day,
OpenTime = day.OpenTime,
CloseTime = day.CloseTime,
};
// Find current Time or keep newly created
time = await time.FindByTimeAsync(time, _db) ?? time;
// Find LocationTime with same day
var locationTime = location.LocationTimes.FirstOrDefault(lt => lt.Time.Day == day.Day);
// If all times are the same, skip (continue)
if (locationTime != null && locationTime.Time.OpenTime == time.OpenTime && locationTime.Time.CloseTime == time.CloseTime)
continue;
if (locationTime != null && (locationTime.Time.OpenTime != time.OpenTime || locationTime.Time.CloseTime != time.CloseTime)) {
// Remove, At least one of the Times do not match
locationTime.Time = time;
_db.Entry(locationTime).State = EntityState.Modified;
} else {
location.LocationTimes.Add(new LocationTime {
Location = location,
Time = time,
});
}
}
/* removed, added above */
//_db.Entry(location).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
Basically I retrieved the records from the table and wanted to updated one column.
var query = cdrContext.tabless.Where(c => c.FacilityID == facilityID && c.FilePath != null && c.TimeStationOffHook < oldDate)
.OrderBy(c => c.TimeStationOffHook)
.Skip(size)
.Take(pageSize)
.Select(c => new { c.FilePath, c.FileName })
.ToList();
So this query has only two fields: FilePath and FileName, then next I want to assign FilePath = null;
foreach (var y in query)
{
y.FilePath = null;
}
cdrContext.SaveChanges();
Then I got an error:
Property or indexer 'AnonymousType#1.FilePath' cannot be assigned to -- it is read only
You select from query anonymous class. You cannot set properties of anonymous class. To do what you want you should get whole entity:
var query = cdrContext.tabless.Where(c => c.FacilityID == facilityID && c.FilePath != null && c.TimeStationOffHook < oldDate)
.OrderBy(c => c.TimeStationOffHook)
.Skip(size)
.Take(pageSize)
.ToList();
foreach (var y in query)
{
y.FilePath = null;
}
cdrContext.SaveChanges();