I'm trying to create a update for EF Core 6 many-to-many on SQL Server but I am really confused. I have stock.cs class and location.cs class
public class Stock : BaseModel
{
public Stock()
{
this.Locations = new List<Location>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Guid { get; set; } = string.Empty;
public string RackBarNumber { get; set; } = string.Empty;
public string ShelveNumber { get; set; } = string.Empty;
public string ShelveName { get; set; } = string.Empty;
public virtual List<Location>? Locations { get; set; }
}
public class Location : BaseModel
{
public Location()
{
this.Stocks = new List<Stock>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public virtual List<Stock>? Stocks { get; set; }
}
I use this as my DTO for getting all my current locations
public class StockLocations
{
public Stock Stock { get; set; }
public virtual ICollection<Location> currentLocations { get; set; }
}
Now the StockController is the piece of code which updates the fields, I am able to create and delete in the StockLocation table that EF Core creates. But when I try many updates at once it just goes haywire.
This is my last attempt:
[HttpPut("{id}")]
public async Task<IActionResult> PutStock(int id, StockLocations stockLocation)
{
await _userService.ConfirmUser(User);
stockLocation.Stock.UpdatedAt = DateTime.Now;
List<Location> removedLocations = new List<Location>();
if (id != stockLocation.Stock.Id)
{
return BadRequest();
}
_context.Entry(stockLocation.Stock).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
// Add new items to the database
foreach (var item in stockLocation.Stock.Locations)
{
if (!stockLocation.currentLocations.Any(x => x.Id == item.Id))
{
_context.Entry(item).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
}
// Create a list of removed locations to be removed from the database
foreach (Location location in stockLocation.currentLocations)
{
if (!stockLocation.Stock.Locations.Any(x => x.Id == location.Id))
{
removedLocations.Add(location);
}
}
foreach (var item in removedLocations)
{
/*
Stock stock = _context.Stocks.Include(x => x.Locations).Single(x => x.Id == id);
Location locationToDelete = stock.Locations.Find(x => x.Id == item.Id);
stock.Locations.Remove(locationToDelete);
await _context.SaveChangesAsync();
*/
}
}
catch (DbUpdateConcurrencyException)
{
return NoContent();
}
return NoContent();
}
Anyone who is willing to tell me how I can approach this properly?
Since you need to update StockLocations I will recommend that tyou just pull the record from the database such as:
var record = await _context.StockLocations
.Include(a=>a.Location).Include(a=>a.Stock)
.FirstOrDefaultAsync(a=>a.id == id);
if(record == null) {
thrown new NotFoundException();
}
stockLocation.Stock.UpdatedAt = DateTime.Now;
await _context.SaveChangesAsync();
// other code
// add new location to the db if they don't exist
var locations = stockLocation.Location;
foreach(var loc in locations) {
var findLocation =
_context.Locations.FirstOrDefault(a=>a.Name.ToLower() ==
loc.Name.ToLower()) ;
if(findLocation == null){
// does not exist and can be added
}
}
Related
I'm new to EF (table first) and I don't know why these related entities are not saving at all to my database.
These are the related entities, UserProfile has a set of Carts
public partial class UserProfile
{
public UserProfile()
{
Cart = new HashSet<Cart>();
Naquestions = new HashSet<Naquestions>();
}
public int Id { get; set; }
public string BotUserId { get; set; }
public int? PrestashopId { get; set; }
public bool Validated { get; set; }
public int Permission { get; set; }
public DateTime CreationDate { get; set; }
public ICollection<Cart> Cart { get; set; }
public ICollection<Naquestions> Naquestions { get; set; }
}
Cart has a set of OrderLines
public partial class Cart
{
public Cart()
{
OrderLine = new HashSet<OrderLine>();
OrderRequest = new HashSet<OrderRequest>();
}
public int Id { get; set; }
public int UserId { get; set; }
public bool Active { get; set; }
public UserProfile User { get; set; }
public ICollection<OrderLine> OrderLine { get; set; }
public ICollection<OrderRequest> OrderRequest { get; set; }
}
And when I try to add them:
public async Task AddOrderLineToUser(string botId, OrderLine orderLine)
{
using (var context = ServiceProvider.CreateScope())
{
var db = context.ServiceProvider.GetRequiredService<GretaDBContext>();
var user = await UserController.GetUserByBotIdAsync(botId);
var latestCart = user.Cart.OrderByDescending(c => c.Id).FirstOrDefault();
if (latestCart != null && latestCart.Active)
{
latestCart.OrderLine.Add(orderLine);
}
else
{
var newCart = new Cart()
{
Active = true,
};
newCart.OrderLine.Add(orderLine);
user.Cart.Add(newCart);
}
await db.SaveChangesAsync();
}
}
Nothing is saving to the database once db.SaveChangesAsync() is called.
As #Caius Jard said in the comments it seems that user comes from another context. Try
if (latestCart != null && latestCart.Active)
{
orderLine.CartId = latestCart.Id;
db.OrderLines // I assume it is name of your orderlines DbSet
.Add(orderLine);
}
else
{
var newCart = new Cart()
{
Active = true,
UserId = user.Id,
};
newCart.OrderLine.Add(orderLine);
db.Carts // also assuming name of DbSet
.Add(newCart);
}
Also you can take a look at Attach method.
But I would say that in general you are doing something not good. Usually creating new scope is not needed, and db context should be injected in corresponding class via ctor. If you still need to create new scope it would make sense to resolve UserController also. Also is UserController an ASP controller?
I have the following models in my API:
namespace API.Models
{
public class StudentDetailsViewModel
{
[Key]
public int StudentId { get; set; }
public AddressViewModel Address { get; set; }
public List<CoursesViewModel> Courses { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public int StudentId { get; set; }
public string Address { set; set; }
}
public CoursesViewModel
{
public int CourseId { get; set; }
public int StudentId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Grade { get; set; }
}
}
I am writing a PUT method for StudentDetailsViewModel. The list in this model could have a number of records removed or added or a number of fields in one of the records updated. For example, grade for one of the courses updated or a course added or dropped.
What is the best approach in updating a model containing an object list like the above? Is it best to delete the entire list and re-add them?
I have the following thus far:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutStudenDetailsViewModel(StudentDetailsViewModel studentDetailsViewModel)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var address = new DataAccess.Address
{
AddressID = studentDetailsViewModel.Address.AddessId,
StudentID = studentDetailsViewModel.Address.StudentId,
Address = studentDetailsViewModel.Address.Address
};
_context.Entry(address).State = EntityState.Modified;
// TODO: This is where the list Course entity needs to be updated
try
{
await _context.SaveChangesAsync();
}
catch(DbUpdateConcurrencyException)
{
if(!AddressViewModelExists(address.AddressID))
return NotFound();
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
Just an example from MS documentation for EF Core
public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
var existingBlog = context.Blogs
.Include(b => b.Posts)
.FirstOrDefault(b => b.BlogId == blog.BlogId);
if (existingBlog == null)
{
context.Add(blog); //or 404 response, or custom exception, etc...
}
else
{
context.Entry(existingBlog).CurrentValues.SetValues(blog);
foreach (var post in blog.Posts)
{
var existingPost = existingBlog.Posts
.FirstOrDefault(p => p.PostId == post.PostId);
if (existingPost == null)
{
existingBlog.Posts.Add(post);
}
else
{
context.Entry(existingPost).CurrentValues.SetValues(post);
}
}
}
context.SaveChanges();
}
I'm new to Entity Framework. I'm using the code first option. Below I have a basic model. The database was created properly and the records persist properly but when I run the test method (defined after the model) a second time my header record loads fine, but my navigation property, Details, does not reload. What am I doing wrong?
class Header
{
public int HeaderId { get; set; }
public string Name { get; set; }
public virtual ICollection<Detail> Details { get; set; } = new List<Detail>();
}
class Detail
{
public int DetailId { get; set; }
public string Name { get; set; }
public int HeaderId { get; set; }
[ForeignKey("HeaderId")]
public virtual Header Header { get; set; }
}
class MyContext : DbContext
{
public MyContext() : base("EfTest")
{
this.Configuration.LazyLoadingEnabled = true;
}
public virtual DbSet<Header> Headers { get; set; }
}
private static void DoIt()
{
using (var ctx = new MyContext())
{
var hdr = (
from header in ctx.Headers
where header.Name == NAME
select header).FirstOrDefault();
if (hdr == null)
{
hdr = new Header();
hdr.Name = NAME;
ctx.Headers.Add(hdr);
MessageBox.Show("Header not found; created.");
}
else
{
MessageBox.Show("Header found!");
}
var det = hdr.Details.FirstOrDefault();
if (det == null)
{
det = new Detail() { Name = "Hi" };
hdr.Details.Add(det);
MessageBox.Show("Detail not found; created.");
}
else
{
MessageBox.Show("Detail found!");
}
ctx.SaveChanges();
}
}
Here Entity Framework use Lazy-Loading. In order to get details you should use Eager-Loading.You should use Include method in System.Data.Entity namespace to accomplish Eager-Loading.Change your query like the following.
var hdr = (
from header in ctx.Headers
where header.Name == NAME
select header).Include(h=>h.Details).FirstOrDefault();
using .net 4.5.2, MVC5, Entity framework 6 and visual studio 2015.
I have a repository pattern set up with Ninject as my DI here is the common file.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
kernel.Bind<IUserBlueRayLists>().To<UserBlueRayListRepository>().InRequestScope();
kernel.Bind<IBlueRays>().To<BlueRaysRepository>().InRequestScope();
}
my Context
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public IDbSet<UserBlueRayList> UserBlueRayLists { get; set; }
public IDbSet<BlueRays> BlueRays { get; set; }
public new void SaveChanges()
{
base.SaveChanges();
}
}
public interface IDevTestContext
{
IDbSet<UserBlueRayList> UserBlueRayLists { get; set; }
IDbSet<BlueRays> BlueRays { get; set; }
void SaveChanges();
}
Then my Repository update method.
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.FirstOrDefault(x => x.Id == item.Id);
if(userBRList != null)
{
userBRList = item;
//_db.Entry(userBRList).State = EntityState.Modified;
_db.SaveChanges();
return true;
}
return false;
}
Now when i save via my controller and call the repository update method, nothing is updated.
So i use
_db.Entry(userBRList).State = EntityState.Modified;
But i get an error,
Additional information: Attaching an entity of type 'myapp.Models.UserBlueRayList' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values... etc
Many to many models, userlist model.
public class UserBlueRayList
{
public UserBlueRayList()
{
this.BlueRays = new HashSet<BlueRays>();
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string UserId { get; set; }
public virtual ICollection<BlueRays> BlueRays { get; set; }
}
And the
public class BlueRays
{
public BlueRays()
{
this.UserBlueRayList = new HashSet<UserBlueRayList>();
}
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
public virtual ICollection<UserBlueRayList> UserBlueRayList { get; set; }
}
Question is why this wont update, and why it errors if i try to set state to modified.
Since you are using EF6, you may try to use auto property mapping built into EF6
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.FirstOrDefault(x => x.Id == item.Id);
if(userBRList != null)
{
_dbContext.Entry(userBRList).CurrentValues.SetValues(item);
return return _dbContext.SaveChanges() > 0;
}
return false;
}
Cheers
First Solution you have to update like that
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.FirstOrDefault(x => x.Id == item.Id);
if(userBRList != null)
{
userBRList.Name = item.Name;
//value assign to other entity
_db.Entry(userBRList).State = EntityState.Modified;
_db.SaveChanges();
return true;
}
return false;
}
if this will not solve your problem you Find method instead of FirstorDefault()
public bool Update(UserBlueRayList item)
{
var userBRList = _db.UserBlueRayLists.Find(x => x.Id == item.Id);
if(userBRList != null)
{
userBRList.Name = item.Name;
//value assign to other entity
_db.Entry(userBRList).State = EntityState.Modified;
_db.SaveChanges();
return true;
}
return false;
}
I have two entities, and when I update or add only one all is ok
db.Entry(user1).State = EntityState.Modified;
foreach (var userAudio in user1.Audios)
{
db.Audios.AddOrUpdate(userAudio);
}
db.Users.AddOrUpdate(user1);
db.SaveChanges();
But if I try add/update few entities:
db.Entry(user1).State = EntityState.Modified;
foreach (var userAudio in user1.Audios)
{
db.Audios.AddOrUpdate(userAudio);
}
db.Users.AddOrUpdate(user1);
db.Entry(user2).State = EntityState.Modified;
foreach (var userAudio in user2.Audios)
{
db.Audios.AddOrUpdate(userAudio);
}
db.Users.AddOrUpdate(user2);
db.SaveChanges();
It is throwing exception:
Attaching an entity of type 'EfTest.Entities.Audio' failed because
another entity of the same type already has the same primary key
value...
Maybe it is because I have same audios in user1 and user2, and EF can't insert them...Anyone have ideas how to get round this? thanks!
Entities
namespace EfTest.Entities
{
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public List<Audio> Audios { get; set; }
}
public class Audio
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Artist { get; set; }
public string Title { get; set; }
public List<User> Users { get; set; }
}
}
Unfortunately I see only one way to solve this. It is adding entities independently and then adding relations:
var userAudios = new List<Audio>();
// Key userId
// Value list of audiosIds
var userAudiosRelations = new List<Relation>();
foreach (var user in users)
{
foreach (var audio in user.Audios)
{
if (!userAudios.Any(x => x.Id == audio.Id))
{
userAudios.Add(audio);
}
userAudiosRelations.Add(new Relation
{
User_Id = user.Id,
Audio_Id = audio.Id
});
}
user.Audios = null;
db.Users.AddOrUpdate(user);
}
foreach (var audio in userAudios)
{
db.Audios.AddOrUpdate(audio);
}
db.SaveChanges();
var existingRelations = db.Database.SqlQuery<Relation>("SELECT * FROM dbo.UserAudios").ToList();
var relationsToAdd =
userAudiosRelations.Where(
x => existingRelations.All(y => x.User_Id != y.User_Id || x.Audio_Id != y.Audio_Id)).ToList();
foreach (var relation in relationsToAdd)
{
db.Database.ExecuteSqlCommand("INSERT INTO dbo.UserAudios (User_Id, Audio_Id) VALUES (#p0, #p1)",
relation.User_Id, relation.Audio_Id);
}
Where relation model:
public class Relation
{
public int User_Id { get; set; }
public int Audio_Id { get; set; }
}