I have these two models:
public class Film
{
public long Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Director { get; set; }
public string Synopsis { get; set; }
public int? Release { get; set; }
[Required]
public Genre Genre { get; set; }
}
public class Genre
{
public long Id { get; set; }
public string Description { get; set; }
}
And I want to be able to update a Film's Genre through a PUT method. I am currently trying this, but I get the following error:
[HttpPut]
public async Task<IActionResult> UpdateFilm(Film film)
{
var existingFilm = await _context.Films
.Include(f => f.Genre)
.FirstOrDefaultAsync(f => f.Id == film.Id);
if (existingFilm == null)
{
return NotFound(new JsonResponse { Success = false, Message = "Impossible to update, film was not found", Data = null });
}
existingFilm.Title = film.Title;
existingFilm.Synopsis = film.Synopsis;
existingFilm.Release = film.Release;
existingFilm.Director = film.Director;
if (existingFilm.Genre.Id != film.Genre.Id)
{
existingFilm.Genre.Id = film.Genre.Id;
existingFilm.Genre.Description = film.Genre.Description;
//_context.Entry(existingFilm.Genre).State = EntityState.Modified;
_context.Entry(existingFilm).CurrentValues.SetValues(film);
}
_context.Films.Update(existingFilm);
try
{
await _context.SaveChangesAsync();
}
catch (Exception e)
{
return BadRequest(new JsonResponse { Success = false, Message = e.Message, Data = null });
}
return Ok(new JsonResponse { Success = true, Message = "Film updated with success", Data = film });
}
The error message is:
System.InvalidOperationException: The property 'Id' on entity type 'Genre' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.
Anyone able to help? Thanks a lot.
According to the error, its existingFilm.Genre.Id that you cannot update the id, if its not equal to the id.
My suggestion would be ignore the id update, but if it is necessary:
if (existingFilm.Genre.Id != film.Genre.Id)
{
var genre = _context.Genre.FirstOrDefault(x => x.Id == film.Genre.Id);
// Update the fields needed except the id
existingFilm.Genre = genre;
}
Related
I'm currently learning ASP.NET Core and having some problem. Basically, I have a model like this:
public class WorkRoom
{
[Key]
public int WorkRoomId { get; set; }
[ForeignKey("Room")]
[Required]
public int RoomId { get; set; }
public Room Room { get; set; }
[ForeignKey("Employee")]
[Required]
public string Id { get; set; }
public virtual ApplicationUser Employee { get; set; }
}
and a Dto for this model cause i only want to pass two value is RoomId and Id(using identityUser).
WorkRoomDto.cs
public class WorkRoomDto
{
public int RoomId { get; set; }
public string Id { get; set; }
}
I can write POST, GET and DELETE method normally as i wish, but I have some problems writing PUT.
public async Task<Response> PutWorkRooms(int id, WorkRoomDto workRoom)
{
var respone = new Response();
var workRoomId = _context.WorkRooms.Any(i => i.WorkRoomId == id);
var userId = await userManager.FindByIdAsync(workRoom.Id);
if (workRoomId)
{
if (userId == null)
{
respone.Status = false;
respone.Message = "User not exist";
return respone;
}
var newWorkRoom = mapper.Map<WorkRoomDto, WorkRoom>(workRoom);
_context.Entry(newWorkRoom).State = EntityState.Modified;
await _context.SaveChangesAsync();
respone.Status = true;
respone.Message = "Successfully updated.";
respone.Data = workRoom;
}
else
{
respone.Status = false;
respone.Message = "WorkRoomId no exist.";
}
return respone;
}
But it showed Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Before the autoMappter I had tried
_context.Entry(workRoom).State = EntityState.Modified;
await _context.SaveChangesAsync();
But it says the Dto is not in the DbContext. How can I fix this?
From your code, WorkRoomId is 0 in newWorkRoom. You need set the value for it. Just change like below:
var newWorkRoom = mapper.Map<WorkRoomDto, WorkRoom>(workRoom);
newWorkRoom.WorkRoomId = id; //add this line...
_context.Entry(newWorkRoom).State = EntityState.Modified;
await _context.SaveChangesAsync();
Automapper give you a brand new object that created locally (not taking back from the database), therefore, not tracked by the dbContext.
So, when you try to update newWorkRoom by forcing the state to modified, the dbContext know it want to see an update operation, but unfortunately, nothing got tracked, then execute nothing.
Try this
var newWorkRoom = mapper.Map<WorkRoomDto, WorkRoom>(workRoom);
if (_context.Entry(newWorkRoom).State == EntityState.Detached)
{
var entityFromDb = await _context.WorkRooms.FindAsync(id);
_context.Entry(entityFromDb).CurrentValues.SetValues(newWorkRoom);
}
I've been following ms docs tutorial on handling concurrency conflicts in EF core. I have two models:
Movie.cs
public class Movie
{
[Key]
public int ID { get; set; }
[Required]
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Release Date")]
[DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
[Range(1, 300)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[Required]
[StringLength(5)]
[RegularExpression(#"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
public string Rating { get; set; }
public string Description { get; set; }
[Timestamp]
public byte[] ConcurrencyToken { get; set; }
public int GenreId { get; set; }
public Genre Genre { get; set; }
public override string ToString()
{
return Title;
}
}
Genre.cs
public class Genre
{
public Genre()
{
Movies = new List<Movie>();
}
[Key]
public int GenreId { get; set; }
[Required]
[StringLength(30, MinimumLength = 5)]
[Display(Name = "Genre Title")]
[RegularExpression(#"^[A-Z]+[a-zA-Z]*$")]
public string GenreTitle { get; set; }
[Timestamp]
public byte[] ConcurrencyToken { get; set; }
[Display(Name = "Number of movies")]
public ICollection<Movie> Movies { get; set; }
public override string ToString()
{
return GenreTitle;
}
}
When I tried to update my Models I faced some weird issues and could not figure why, there were no errors thrown, models were just simply not getting updated. Here is my post method for Update:
public async Task<IActionResult> OnPostAsync(int? id)
{
if (!ModelState.IsValid)
{
return Page();
}
// ConcurrencyToken may have changed.
var movieToUpdate = await _context.Movie
.Include(m => m.Genre)
.FirstOrDefaultAsync(m => m.ID == id);
if(movieToUpdate == null)
{
return HandleDeletedMovie();
}
// Set ConcurrencyToken to value read in OnGetAsync
_context.Entry(movieToUpdate).Property(
m => m.ConcurrencyToken).OriginalValue = Movie.ConcurrencyToken;
if (await TryUpdateModelAsync<Movie>(
movieToUpdate,
"movie",
m => m.Title, m => m.ReleaseDate,
m => m.Price, m => m.Rating,
m => m.GenreId))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch(DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Movie)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if(databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. The movie was deleted by another user.");
return Page();
}
var dbValues = (Movie)databaseEntry.ToObject();
await SetDbErrorMessage(dbValues, clientValues, _context);
// Save the current ConcurrencyToken so next postback
// matches unless an new concurrency issue happens.
Movie.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
// Clear model error for the next postback
ModelState.Remove($"{nameof(Movie)}.{nameof(Movie.ConcurrencyToken)}");
}
}
// Get errors from TryUpdate
var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToList();
PopelateGenresDropDownList(_context, movieToUpdate.Genre);
return Page();
}
The update was failing on the line TryUpdateModelAsync, and it did not go to try-catch. I figured out the error that was causing the issue was validation for the Genre.GenreTitle property which is Included in movie. Even though ModelState was not failing it still caused an error for updating the entity. However, when I checked the Movie.Genre property was totally fine and there should not have been any errors. Only when I removed .Include from var movieToUpdate = await _context.Movie.FirstOrDefaultAsync(id) it worked out.
Now, I'm wondering what was my mistake because in the documentation the nested object was actually .Included.
I tested your regular expression ^[A-Z]+[a-zA-Z]*$ and it does not seems to accept space caracter. So Genre Title is not a match to the expression and trigger an error.
You can add space in the [] to tell the expression that you also need to accept space character. I don't know if it is how it's supposed to be done but it works in RegexStorm or Regex101 (No c# for this last one but Regex are kinda universal I think).
I have two related tables with one to many relationship, I m trying to get a JSON response that populates ICollection<PatPar> PatPar the controller's code below reads fine as long as I have only one related record, once I have more than one related record I get a selection exception.
These are related Models
public class PatReg
{
[NotMapped]
private Int64 _FileId;
[Key, Display(Name = "File Id"), ScaffoldColumn(false), DatabaseGenerated(DatabaseGeneratedOption.None)]
public Int64 FileId
{
get
{
return this._FileId;
}
set
{
this._FileId = value;
}
}
[Required, Display(Name = "First Name")]
public string FName { get; set; }
public ICollection<PatPar> PatPar { get; set; }
}
public class PatPar
{
[Key]
public Int64 RecId { get; set; }
[Display(Name = "Patient File Id"), Required]
public Int64 FileId { set; get; }
[Display(Name = "Partner File Id"), Required]
public Int64 ParFileId { set; get; }
[Display(Name = "Start Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true), Required]
public DateTime SDate { set; get; }
[Display(Name = "End Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? EDate { set; get; }
}
This is my API Controller
[HttpGet]
public IEnumerable<PatReg> GetPatReg()
{
return _context.PatReg;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetPatReg([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var patReg = await _context.PatReg.SingleOrDefaultAsync(m => m.FileId == id);
var parinfo = await _context.PatPar.SingleOrDefaultAsync(m => m.FileId == id); // I should select many records here
if (patReg == null)
{
return NotFound();
}
var DataRes = new {
sData = patReg
};
return Ok(DataRes);
}
I know that I should use selectMany instead of using SingleOrDefaultAsync so I tried
IEnumerable<PatPar> parinfo = await _context.PatPar.SelectMany(m => m.FileId == id);
but the compiler is giving me errors, what is the way to do it?
You should be using Where instead of SelectMany:
var result = _context.PatPar.Where(m => m.FileId == id);
SelectMany is used to retrieve a collection from each item of the iterated collection. However your condition is to check if the FileId equals some id - the resulted type is a boolean. I assume what you are trying to do is return all the times that have that FileId.
To have it with the await:
var result = await _context.PatPar.Where(m => m.FileId == id).ToListAsync();
Maybe you want actually this:
IEnumerable<PatPar> parinfo = _context.PatPar.Where(m => m.FileId == id);
SelectMany description:
Projects each element of a sequence to an IEnumerable and flattens
the resulting sequences into one sequence.
So it is needed if you want to get IEnumerable<SomeSpecificField> as the result.
However, it is applicable only if PatPar had IEnumerable<SomeType> and you want to SelectMany<PatPar, SomeType>.
In your case there can be only Select
IEnumerable<Int64> parFileIds = _context.PatPar.Select(m => m.ParFileId );
I am trying to get the long value associated to an ActivityLogType it is associated to either a create, edit, and delete record in the database.
It is used for an audit / activity log that is needed.
public enum ActivityLogType
{
Create = 1,
Update = 2,
Delete = 3
}
My getter method:
public ActivityType GetType(ActivityLogType type)
{
var id = (long)type;
Console.WriteLine(id); // <---- this produces a 1 in the console. So the cast works?
return _context.ActivityTypes.Where(x => x.Id == id).FirstOrDefault(); // <-- This line throws the error
}
UPDATE 1
un-lucky suggested using (long)ActivityLogType.Create to get the desired output. I tried this: (Still not working)
public ActivityType GetType(ActivityLogType type)
{
switch (type)
{
case ActivityLogType.Create:
return _context.ActivityTypes.Where(x => x.Id == (long)ActivityLogType.Create).FirstOrDefault();
case ActivityLogType.Update:
return _context.ActivityTypes.Where(x => x.Id == (long)ActivityLogType.Update).FirstOrDefault();
case ActivityLogType.Delete:
return _context.ActivityTypes.Where(x => x.Id == (long)ActivityLogType.Delete).FirstOrDefault();
default:
return null;
}
}
UPDATE 2
Here is the ActivityType entity
public class ActivityType
{
public ActivityType()
{
this.Activities = new HashSet<Activity>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Display(Name = "Label")]
public string Label { get; set; }
[Display(Name = "Display Order")]
public int Order { get; set; }
[Display(Name = "Is Active")]
public bool IsActive { get; set; }
[Display(Name = "Activities")]
public virtual ICollection<Activity> Activities { get; set; }
}
You can try this:
long id= Int64.Parse(Convert.ChangeType(type, Enum.GetUnderlyingType(type.GetType())).ToString());
I'm developing a WCF REST service using Entity Framework Code First data layer and I have a navigation property.
User class:
[DataContract]
public class User
{
[DataMember]
public int UserId { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
[DataMember]
public string City { get; set; }
[DataMember]
public string Country { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string InterestIn { get; set; }
[DataMember]
public virtual ICollection<User> Friends { get; set; }
[DataMember]
public virtual ICollection<User> FromWhomIsFriend { get; set; }
}
ServiceContract method:
public List<User> GetUserFriends(string user_id)
{
int userId;
OutgoingWebResponseContext ctx =
WebOperationContext.Current.OutgoingResponse;
if ((user_id == null) ||
(!Int32.TryParse(user_id, out userId)) ||
(userId < 1))
{
ctx.StatusCode = System.Net.HttpStatusCode.BadRequest;
ctx.StatusDescription = "user_id parameter is not valid";
throw new ArgumentException("GetUserFriends: user_id parameter is not valid", "user_id");
}
List<User> friends = null;
try
{
using (var context = new AdnLineContext())
{
context.Configuration.ProxyCreationEnabled = false;
context.Configuration.LazyLoadingEnabled = false;
var users = from u in context.Users.Include("Friends")
where u.UserId == userId
select u;
if ((users != null) &&
(users.Count() > 0))
{
User user = users.First();
//friends = user.Friends.ToList();
friends = new List<User>();
foreach (User f in user.Friends)
{
User us = new User()
{
UserId = f.UserId,
Name = f.Name,
Age = f.Age,
City = f.City,
Country = f.Country,
Email = f.Email,
InterestIn = f.InterestIn,
Friends = f.Friends,
FromWhomIsFriend = f.FromWhomIsFriend
};
friends.Add(us);
}
}
ctx.StatusCode = System.Net.HttpStatusCode.OK;
}
}
catch (Exception ex)
{
ctx.StatusCode = System.Net.HttpStatusCode.InternalServerError;
ctx.StatusDescription = ex.Message;
ctx.SuppressEntityBody = true;
}
return friends;
}
This method doesn't return anything. If I comment this line FromWhomIsFriend = f.FromWhomIsFriend it works.
FromWhomIsFriend is a navigation property to the user from whom I am his friend. To represent user relationship I have this table:
UserID | FriendID
---------+----------
3 | 1
---------+----------
1 | 2
If I ask about friends from user 1, I get user 2, and its FromWhomIsFriend pointing to user 1. And user 1 Friends navigation property pointing to user 2, and continues.
Do you know how why I don't return anything?
You have to enable proxy creation in order to support the lazy loading. What you can do is to use Include in your query to load the navigation property .
var users = from u in context.Users.Include(u=>u.Friends)
where u.UserId == userId
select u;
Or else the other solution is using separate object model as the WCF contracts(DTO). Then you can enable lazy loading and then copy all the required values form the EF entity object(proxy) to your new Data Transfer Object. You can use something like Automaper to map the object easily.