I'm using EF Core and .NET 6 and I would like to essentially upsert an entity to a table - a fairly simple ask.
I have the following code:
var countries = GetCountries();
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
foreach (var c in countries)
{
// check if the country already exists.
var exists = dbContext.Countries.Where(d => d.Id == c.Id).FirstOrDefault();
// if already exists - update (rather than add).
if (exists != null)
{
exists.Name = c.Name;
exists.DisplayName = c.DisplayName;
... // omitted other prop updates.
dbContext.Countries.Update(exists);
}
else
{
dbContext.Countries.Add(c);
}
}
await dbContext.SaveChangesAsync();
}
I was wondering - is there was a more efficient way to update without manually looking up then updating (it's not very performant).
Preferably, I was hoping there was a Merge method in EF Core but can't find anything useful (that's free...). Rather than doing the manual lookup and update in this way.
I'm probably missing something very obvious here - thanks for any pointers in advance!
EF Core do not have Merge, or similar for Upsert.
You can improve performance of your query by selecting existng items in one batch. Also you do not need to call Update, just change properties.
var countries = GetCountries();
using (var scope = scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var countyIds = countries.Select(c => c.Id);
var existing = (await dbContext.Countries.Where(d => countyIds.Contains(d.Id))
.ToListAsync())
.ToDictionary(c => c.Id);
foreach (var c in countries)
{
// check if the country already exists.
if (existing.TryGetValue(c.Id, out var toUpdate))
{
// if already exists - update (rather than add).
toUpdate.Name = c.Name;
toUpdate.DisplayName = c.DisplayName;
... // omitted other prop updates.
}
else
{
dbContext.Countries.Add(c);
}
}
await dbContext.SaveChangesAsync();
}
The answer by user19087368 seems the most straight forward. I tested it on .NET 6 and it worked perfectly - adapted a little bit to my usecase:
Here is my version of it with the "guid" as the primary key:
public async Task CreateOrUpdateApplication(Application application)
{
var itemExists = _dbContext
.Application
.Any(i => i.ApplicationGuid == application.ApplicationGuid);
_dbContext.Entry(application).State = itemExists ?
EntityState.Modified : EntityState.Added;
await _dbContext.SaveChangesAsync();
}
public void InsertOrUpdate(Entity entity)
{
using (var context = new dbContext.Countries())
{
context.Entry(entity).State = entity.Id == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
Related
I have a many-to-many relationship between Project and Member realized with join table ProjectMembers. On view in order to add particular project members I use multiple select which stores MemberId in IEnumerable<int> SelectedMembers.
Everything works correctly except I can only update (Edit) Project with new members (members which are not selected and were part of database before remain untouched). I need help with removing the existing members in ProjectMember which are connected to the specific ProjectId before posting the updated set of members. I have tried a lot but nothing worked so far. Really any advice would be appreciated.
This is my Edit post method in ProjectController:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int? id, CreateProjectViewModel viewmodel)
{
Project project = _context.Project
.Single(m => m.ProjectId == id);
project.Name = viewmodel.Name;
project.Budget = viewmodel.Budget;
project.BusinessCase = viewmodel.BusinessCase;
project.StartDate = viewmodel.StartDate;
project.FinishDate = viewmodel.FinishDate;
project.ClientId = viewmodel.ClientId;
// here I need the method to remove the existing instances of Member in ProjectMember
// part of code below is validation that there will be no conflict of PrimaryKeys on ProjectMember, probably can be removed once the Remove method is implemented
foreach (var selectedId in viewmodel.SelectedMembers)
{
var projectID = project.ProjectId;
var memberID = selectedId;
IList<ProjectMember> existingItems = _context.ProjectMembers
.Where(cm => cm.MemberId == memberID)
.Where(cm => cm.ProjectId == projectID).ToList();
if (existingItems.Count == 0)
{
_context.ProjectMembers.Add(new ProjectMember
{
ProjectId = project.ProjectId,
MemberId = selectedId,
});
}
}
_context.SaveChanges();
return RedirectToAction("Index");
}
UPDATE:
Based on similar threads I came up with following to be added before the new rows are being added into ProjectMembers with the POST Edit:
var project = _context.Project.Include(a => a.ProjectMembers)
.SingleOrDefault(m => m.ProjectId == id);
if (project != null)
{
foreach (var projectMember in project.ProjectMembers
.Where(at => viewmodel.SelectedMembers.Contains(at.MemberId)).ToList())
{
project.ProjectMembers.Remove(projectMember);
}
_context.SaveChanges();
}
Unfortunately the entries which should be deleted in ProjectMembers keep on staying, Can anyone advice what should be changed?
Got it. Apparently a much easier way to do it. Code below is to remove the entires before new ones can be passed:
var project = _context.Project.Include(a => a.ProjectMembers)
.SingleOrDefault(m => m.ProjectId == id);
foreach (var member in project.ProjectMembers.ToArray())
{
project.ProjectMembers.Remove(member);
}
I'm trying to null or remove the ID entirely of all the queried IsoDataTables before returning them to frontend. The idea is that it should behave (in this case) as a template and I don't want it returning the id's back to me, nor do I want them to be removed in the frontend.
var applicationType = await _context.ApplicationType
.Include(m => m.IsoTableData)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.Id == id);
if (applicationType == null)
{
return NotFound();
}
if (applicationType.IsoTableData != null)
{
foreach (IsoTableData isoTableData in applicationType.IsoTableData)
{
// error since it a not nullable primary key
isoTableData.Id = null;
}
}
return Ok(applicationType);
I have found a workaround in which I duplicate the objects and return them (without saving to DB) but I'm looking for a more elegant solution.
The way I did it was create a copy constructor (or basically, a new instance of an object) with the desired fields; I chose a copy constructor as this logic is recurent in other places as well. Another similar solution is creating a DTO object, but I don't need it here. Any improvements?
//in IsoFileApplicationType.cs
public IsoFileApplicationType(IsoFileApplicationType isoFileApplicationType)
{
Id = null
FullName = isoFileApplicationType.FullName;
Name = isoFileApplicationType.Name;
(...)
foreach (IsoTableData isoTableData in isoFileApplicationType.IsoTableData)
{
IsoTableData.Add(IsoTableData(isoTableData));
}
}
//in IsoTableData.cs
public IsoTableData(IsoTableData isoTableData)
{
Id = null;
Data = isoTableData.Name;
Age = isoTableData.Age;
(...)
}
// in CRUD controller
var applicationType = await _context.ApplicationType
.Include(m => m.IsoTableData)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.Id == id);
if (applicationType == null)
{
return NotFound();
}
IsoFileApplicationType newIsoFileApplicationType = IsoFileApplicationType(applicationType);
return Ok(newIsoFileApplicationType);
I am using Asp.net core Razor engine Entity Framework. I keep getting the error above and it from what I have read, it refers to the the db already being used for an operation. I am not sure why this would be happening. Is is because it is in a foreach loop? What would the workaround be? Here is my code
[HttpGet]
[Route("currentSession")]
public IActionResult CurrentSession()
{
var id = HttpContext.Session.GetInt32("Id");
if(id != null)
{
var user = _context.User.FirstOrDefault(x => x.Id == id);
ViewBag.User = user;
ViewBag.User_Id = id;
ViewBag.Auction = _context.Auction.AsEnumerable();
foreach(var item in ViewBag.Auction)
{
if(item.End_Date < DateTime.Now)
{
var seller_id = (int)item.Id_Of_Seller;
var seller = _context.User.FirstOrDefault(x => x.Id == seller_id); //this is the line that causes the error in the title
var bidder_id = (int)item.Id_Highest_Bid;
var buyer = _context.User.FirstOrDefault(x => x.Id == bidder_id); //this line also causes the same error
buyer.Wallet -= item.Bid;
seller.Wallet += item.Bid;
_context.Auction.Remove(item);
_context.SaveChanges();
}
}
return View("Home");
}
return RedirectToAction("LoginPage");
}
Can you try replacing AsEnumerable with ToList?
ViewBag.Auction = _context.Auction.ToList();
I added MultipleActiveResultSets=True to my sql server connection strings and this fixed the exception. No other changes were needed.
This fixed the async methods, the background tasks and IQueryable loops for me.
I have the following exception when saving database context : The relationship could not be changed because one or more of the foreign-key properties is non-nullable.
As stated here, this is probably due to missing cascade delete.
However, this isn't my code and I do not know which table(s) could contain orphans records. The error message doesn't say so.
Is there a way to retrieve those orphans records. (At least know in which table they are)
Then I will be able to pinpoint which part of the code I need to adjust.
In the Entity Framework when you have relation many to many, and you are trying to delete from an object like parent.Children.Remove(child) this will only detach the child from the middle connection table. So you have to find the child and remove it from DbContext ChildrenToParent entity like so DbContext.ChildrenToParent.Remove(child). If you give a some code sample and/or database diagram I think I can explain it more precise.
Can you try the following solution? The DeleteOrphans extension method must be called between DetectChanges and SaveChanges methods.
public static class DbContextExtensions
{
private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();
public static void DeleteOrphans( this DbContext source )
{
var context = ((IObjectContextAdapter)source).ObjectContext;
foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var entityType = entry.EntitySet.ElementType as EntityType;
if (entityType == null)
continue;
var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
var props = entry.GetModifiedProperties().ToArray();
foreach (var prop in props)
{
NavigationProperty navProp;
if (!navPropMap.TryGetValue(prop, out navProp))
continue;
var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
var enumerator = related.GetEnumerator();
if (enumerator.MoveNext() && enumerator.Current != null)
continue;
entry.Delete();
break;
}
}
}
private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
{
var result = type.NavigationProperties
.Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
.Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
.Where(v => v.DependentProperties.Length == 1)
.ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);
return new ReadOnlyDictionary<string, NavigationProperty>(result);
}
}
I am working on mvc project, with repository pattern and entity framework, now on my form i have a sample model
SampleModel
1) name
2) age
3) address
4) notes
5) date updated
I am displaying only following data on the edit form
1) name
2) age
3) address
now if i update the model with missing property values using the repository, the notes, dateupdated field goes null.
My question is how do i update only few selected properties using the repository ( tryupdatemodel not available in repository ) and i dont want to call the original object and map the properites with the updated model.
Is there any way, there must be.
You can update only subset of fields:
using (var context = new YourDbContext())
{
context.SamepleModels.Attach(sampleModel);
DbEntityEntry<SameplModel> entry = context.Entry(sampleModel);
entry.Property(e => e.Name).IsModified = true;
entry.Property(e => e.Age).IsModified = true;
entry.Property(e => e.Address).IsModified = true;
context.SaveChanges();
}
or in ObjectContext API:
using (var context = new YourObjectContext())
{
context.SamepleModels.Attach(sampleModel);
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(sampleModel);
entry.SetModifiedProperty("Name");
entry.SetModifiedProperty("Age");
entry.SetModifiedProperty("Address");
context.SaveChanges();
}
This is an old thread, but if anyone is interested, to extend on Ladislav's solutions, we've come up with a helpful extension method for EF 4.1 and newer:
public static void SetModified<TEntity>(
this DbEntityEntry<TEntity> entry,
IEnumerable<Expression<Func<TEntity, object>>> expressions) where TEntity : class, IEntity
{
foreach (var expression in expressions)
entry.Property(expression).IsModified = true;
}
Obviously you'll need to take away the IEntity constraint unless you're using an interface by the same name for your POCOs.
Example usage would be:
var user = new User
{
Id = Request.Id,
UserName = Request.UserName,
FirstName = Request.FirstName
};
var expressions = new List<Expression<Func<User, object>>>
{
x => x.UserName,
x => x.FirstName
};
context.Entry(user).SetModified(expressions);