I am implementing an import routine, where a user pastes a specific formatted string into an input field, which in turn gets tranformated into an entity and then put into a database.
The algorithm checks if the entity already exists and either tries to update it or insert it into the database. Inserting works fine - updating fails.
//considered existing if Name and owning user match.
if (db.Captains.Any(cpt => cpt.Name == captain.Name && cpt.User.Id == UserId))
{
var captainToUpdate = db.Captains.Where(cpt => cpt.Name == captain.Name && cpt.User.Id == UserId).SingleOrDefault();
db.Entry(captainToUpdate).CurrentValues.SetValues(captain);
db.Entry(captainToUpdate).State = EntityState.Modified;
await db.SaveChangesAsync();
}
The problem at hand is, that written like this, it tries to update the primary key as well, (captain Id is 0, whereas captainToUpdate Id is already set) which results in an exception The property 'Id' is part of the object's key information and cannot be modified.
What do I need to change, so the enttiy gets updated properly. If it can be avoided I don't want to update every property by hand, because the table Captain contains 30ish columns.
What you can do is first set the Id of captain to be the same as the Id of captainToUpdate:
captain.Id = captainToUpdate.Id;
db.Entry(captainToUpdate).CurrentValues.SetValues(captain);
await db.SaveChangesAsync();
I would not use the entity Captain to transfer the data to the UI, but a DTO object that has all properties you want to copy and no more. You can copy values from any object. All matching properties will be copied, all other properties in captainToUpdate will not be affected.
Try something like this ?
var captainToUpdate = db.Captains.FirstOrDefault(cpt => cpt.Name == captain.Name && cpt.User.Id == UserId);
if(captainToUpdate != null){//Update captain Here
captainToUpdate.Update(captain);
}else{//Create captain here
db.Captains.Add(captain);
}
db.Savechanges();
I had the same issue and solved it by extension method and reflection, of course it will be better to create standalone class with some cachning for relfection, but performance wasn't critical in my task.
public static class EnitityFrameworkHelper
{
public static void SetValuesByReflection(this DbPropertyValues propertyValues, object o, IEnumerable<string> properties = null)
{
var reflProperties = o.GetType().GetProperties();
var prop = properties ?? propertyValues.PropertyNames;
foreach (var p in prop)
{
var refp = reflProperties.First(x => x.Name == p);
var v= refp.GetValue(o);
propertyValues[p] = v;
}
}
}
and here is example how to use it
var entry = ctx.Entry(accSet);
entry.CurrentValues.SetValuesByReflection(eParameters, entry.CurrentValues.PropertyNames.Except(new [] { "ID"}));
Also be careful with foreign keys in object which you want to update, probably you want to exclude them too.
Related
In below code , I want to add myEntity object more than once to database using EF Core. but each time with different value on property id but all other properties are the same. How can I do this? because it's only adding 1 row in the database.
I want to do this because I don't want to repeat calling GetCurrentLocalDateTime() for each iteration and also in the else statement.
var myEntity = _mapper.Map<AEntity >(entityDto);
myEntity.updatedAt = _helper.GetCurrentLocalDateTime();
myEntity.CreatedAt = _helper.GetCurrentLocalDateTime();
if (entityDto.ids != null || entityDto.ids.Count > 0)
{
foreach (var id in entityDto.ids)
{
myEntity.id = id;
await _dbContext.myEntities.AddAsync(myEntity);
}
}
else
{
await _dbContext.myEntities.AddAsync(myEntity);
}
await _dbContext.SaveChangesAsync();
You can't add a single instance of a class, change one of its properties, and add it again expecting a new instance to be added to your database. All that will happen is that you change the property of the single instance you have added.
Instead, you will need to map the DTO multiple times, so that you add multiple instance of your entity class to the DbSet.
You also need to use && instead of || in your if condition. Use OR (||) will result in a NullReferenceException if the entityDto.ids collection is null.
var now = _helper.GetCurrentLocalDateTime();
if (entityDto.ids != null && entityDto.ids.Count > 0)
{
foreach (var id in entityDto.ids)
{
var myEntity = _mapper.Map<AEntity>(entityDto);
myEntity.updatedAt = now;
myEntity.CreatedAt = now;
myEntity.id = id;
await _dbContext.myEntities.AddAsync(myEntity);
}
}
else
{
var myEntity = _mapper.Map<AEntity>(entityDto);
myEntity.updatedAt = now;
myEntity.CreatedAt = now;
await _dbContext.myEntities.AddAsync(myEntity);
}
await _dbContext.SaveChangesAsync();
It uses it to add multiple data.Look at these
.AddRangeAsync()
.AddRange()
you can try to keep your data in list and save it all at once with "addrange()"
Yes, it is possible, though I would use caution to ensure that this is only to insert a number of copies:
foreach (var id in entityDto.ids)
{
myEntity.id = id;
await _dbContext.myEntities.AddAsync(myEntity);
_dbContext.Entry(myEntity).State = EntityState.Detached;
}
By detaching the entity, the DbContext will no longer be tracking it, so updating it and adding it again will be treated as a request to add a new entity.
Normally this kind of code results in a bug where developers are trying to reuse a single entity instance to update multiple data rows. This is a rather odd requirement to insert several copies of the same data, so I would ensure it is documented well so future developers don't try repurposing it. :)
I have a database full of vehicles. Each vehicle can have many pictures. Obviously, the pictures table has a Vehicle foreign key.
Whenever I add a picture, then try to retrieve it, I cannot. This is due to the fact that the Vehicle foreign key is returned as Null. Trouble is, I don't understand why. At this point in time, understand that on a database level, all the fields in the picture table are populated as expected. There are 3 fields Id (PK, int), Picture (nVarChar(max)) and VehicleId(FK, int)). Crucially, the foreign key (VehicleId) IS populated with a valid vehicle Id.
I am using the MVC.
First of all, code for the Picture Model.
public class Picture
{
public int Id {get; set;}
public string Image {get; set;}
[ForeignKey("VehicleId")]
public virtual Vehicle Vehicle{get;set;}
}
Secondly, Picture Controller code that saves a picture. Notice how I am setting the Vehicle Foreign Key.
IMPORTANT Clue - If I put test code in Immediately before 'return Created' to retrieve a picture, I can. Vehicle Foreign Key appears to be set. If I just run the application and try to retrieve a picture straight away (i.e withoud adding a new one), the Vehicle Foreign key is null. Why though? I have included the code I call that retrieves an individual picture.
public async Task<IActionResult> PostNewPicture(int vehicleId_, PictureViewModel picture_)
{
try
{
if(ModelState.IsValid)
{
var vehicle = _vehicleRepository.GetVehicleById(vehicleId_);
if (vehicle == null) return BadRequest("Vehicle Not Found");
var newPicture = _mapper.Map<Picture>(picture_);
newPicture.Vehicle = vehicle;
_vehicleRepository.AddPicture(newPicture);
if (await _vehicleRepository.SaveChangesAsync())
{
var url = _linkGenerator.GetPathByAction("GetIndividualPicture",
"Pictures",
new {vehicleId_ = newPicture.VehicleForeignKey.Id,
pictureId_ = newPicture.Id});
var pictureViewModel = _mapper.Map<PictureViewModel>(newPicture);
return Created(url, _mapper.Map<PictureViewModel>(newPicture));
}
}
return BadRequest("Failed to save the picture");
}
catch(Exception ex)
{
return BadRequest($"Exception Thrown : {ex}");
}
}
Code to retrieve an individual picture:
public Picture GetIndividualPicture(int vehicleId_, int pictureId_)
{
_vehicleContext.Pictures.Include(vp => vp.Vehicle);
IEnumerable<Picture> pictures = from v in _vehicleContext.Pictures.ToList()
.Where(x => x.Vehicle.Id == vehicleId_
&& x.Id == pictureId_) select v;
return pictures.FirstOrDefault();
}
Why VehicleForeignKey is Null, when it is clearly set at the point of adding?
Kudos to Gert Arnold for this one. He correctly states
the second code snippet. You don't have _vehicleContext.VehiclePictures.Include(vp => vp.VehicleForeignKey)
I feel like a bit of a fraud answering this myself - again, It was Gert. Here are my check in notes on this....
12th November 2019
Pictures Controller - I could not understand why the foreign Key withing the Pictures
table was always being returned as null. The answer was because I was not Including it.
See Include in the query below. Dont include this and Vehicle is always null.
ALSO - I was using .ToList() immediately after the .Include(vp => vp.Vehicle)
dont do this - it loads the entire result set into memory BEFORE any filtering is done
public Picture GetIndividualPicture(int vehicleId_, int pictureId_)
{
IEnumerable<Picture> pictures = from v in _vehicleContext.Pictures
.Include(vp => vp.Vehicle)
.Where(x => x.Vehicle.Id == vehicleId_
&& x.Id == pictureId_) select v;
return pictures.FirstOrDefault();
}
I have 3 tables namely Ship[ShipID, Name, YearOfConstr, CompanyID(FK), TypeID(FK)] which is a bridge between Company[CompanyID, Name, Headquarter] and Type[TypeID, Description, NoPassengers]
I wanted to query the names of all Company which has a specific type = "xxx" and whose headquater = "yyy"
Below is what I have tried, but it's returning nothing and won't throw an error either.
public List<string> AllShippingCompanies(string TypeDescription, string headquarters)
{
var list = from shipcomany in dbContext.Ships.Where(x => x.Type.Description == TypeDescription && x.ShippingCompany.Headquarter == headquarters)
select shipcomany.ShippingCompany.Name;
return list.ToList();
}
What could I have been doing wrong ?
I just check and found there are no related data in my DB. The code works fine. It's correct. Thanks for your time
I have the following code:
model = new Option();
model.val1 = newVal1;
model.val2 = newVal2;
model.val3 = newVal3;
//this saves new record just fine
if (recordCount < 1)
{
context.Options.AddObject(model);
context.SaveChanges();
}
else
{
var tempID = from s in context.Options where (s.val1 == newVal1 && s.val2 == newVal2) select s.ID;
var resultsID = tempID.First();
model = context.Options.Single(m => m.ID == resultsID);
if (TryUpdateModel(model, new[] { "val3" }))
{
//this isn't updating the record
context.SaveChanges();
}
}
The database adds a new entry just fine, but isn't updating it. What am I missing? Thanks.
Looking at this code, you first make a new model and set some properties on it:
model = new Option(); // <- A
model.val1 = newVal1;
model.val2 = newVal2;
model.val3 = newVal3;
then, assuming you're heading down the "else" path you do this:
var tempID = from s in context.Options where (s.val1 == newVal1 && s.val2 == newVal2) select s.ID;
var resultsID = tempID.First();
model = context.Options.Single(m => m.ID == resultsID); // <- B
if (TryUpdateModel(model, new[] { "val3" }))
{
//this isn't updating the record
context.SaveChanges();
}
which goes out and finds the entry in context.Options that has the matching ID.
So, now that model, which you created via the new() call (which I've marked with the comment "A") is now cast adrift and you've got a different one - the one you retrieved via the call to context.Options.Single(), which I've marked with the comment "B". It has properties based on what's in the context, not what was in that object you made. That A object is gone now. You've got a new object, B, retrieved from the DB.
So now, you're calling TryUpdateModel on this retrieved object, telling it that val3 is updated, but the value hasn't changed, right? It's whatever you pulled from the context.
So, it's not going to update anything because the model object isn't the one you think it is... the one you updated is waiting to be garbage collected. The one you retrieved hasn't been updated because it still has whatever value it's got for the property val3.
Assuming I follow what you're trying to do here, that's why you're not seeing any updated values in the context.
If you want to change the value of the val3 property on that model object you've retrieved, you need to set it after you retrieve it, otherwise it is overwritten.
If you are using a global context, you will have to update the context itself because it is not soft-link to the database.
context.SaveChanges();
DbContext context = new DbContext();
Check if Configuration.AutoDetectChangesEnabled = true;
I have two database tables, one for Events and one for RecurrenceRules. Events have a FK that points to a RecurrenceRuleID.
All I want to do is Save an Event AND it's Recurrence rule from a new context, but I can't seem to accomplish this. What am I missing??
using (var context = new ScheduledEventEntities())
{
if (obj.EntityKey == null)
{
context.AddObject(obj.EntityKey.EntitySetName, obj);
}
else
{
var existingObject = context.Events.FirstOrDefault(p => p.Ident == obj.Ident);
context.ApplyCurrentValues<Event>(obj.EntityKey.EntitySetName, obj);
// How do I save obj.RecurrenceRule?
}
context.SaveChanges();
}
I would try it this way:
// ...
var existingObject = context.Events.Include("RecurrenceRule")
.FirstOrDefault(p => p.Ident == obj.Ident);
context.ApplyCurrentValues<Event>(obj.EntityKey.EntitySetName, obj);
// 1st case: Relationship to RecurrenceRule has been removed or didn't exist
if (obj.RecurrenceRule == null)
existingObject.RecurrenceRule = null;
// 2nd case: Relationship to RecurrenceRule must be set or updated
else
{
// relationship has changed
if (existingObject.RecurrenceRule == null ||
obj.RecurrenceRule.Id != existingObject.RecurrenceRule.Id)
{
var existingRecurrenceRule = context.RecurrenceRule
.SingleOrDefault(r => r.Id == obj.RecurrenceRule.Id);
if (existingRecurrenceRule != null) // RecurrenceRule exists in DB
{
// Update scalar values
context.ApplyCurrentValues<RecurrenceRule>(
obj.RecurrenceRule.EntityKey.EntitySetName, obj.RecurrenceRule);
}
else // RecurrenceRule does not exist in DB
{
// nothing to do, SaveChanges will recognize new RecurrenceRule
// and create INSERT statement
}
// set new relationship
existingObject.RecurrenceRule = obj.RecurrenceRule;
}
else // same relationship: just update scalar values
{
// Update scalar values
context.ApplyCurrentValues<RecurrenceRule>(
obj.RecurrenceRule.EntityKey.EntitySetName, obj.RecurrenceRule);
}
}
// ...
The problem is indeed that ApplyCurrentValues only cares about updating scalar properties but not navigation properties, so you have to handle updating the navigation properties yourself. (Unfortunately updating detached object graphs leads almost always to such tedious code because there is no built-in mechanism in Entity Framework to do this work for you.)