I´m working on a database with movies and genres (Many to Many). I have created an API and I´m struggling with the DTO's and the Many to Many relationship.
public class MovieDto
{
public int Id { get; set; }
public string Name { get; set; }
public string OriginalTitel { get; set; }
public DateTime ReleseDate { get; set; }
public int Duration { get; set; }
public string Description { get; set; }
public int? Rating { get; set; }
public int? PersonalRating { get; set; }
public int AgeRestrictionId { get; set; }
public ICollection<GenreDto> Genres { get; set; }
}
public class GenreDto
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsChecked { get; set; }
}
With the help of another Post, I have created this Mapping
public MappingProfile()
{
Mapper.CreateMap<Movie, MovieDto>();
Mapper.CreateMap<Genre, GenreDto>()
.ForMember(d => d.Id, opt => opt.MapFrom(s => s.Id))
.ForMember(d => d.Name, opt => opt.MapFrom(s => s.Name));
Mapper.CreateMap<MovieDto, Movie>()
.AfterMap((s, d) =>
{
foreach (var Genre in d.Genres)
Genre.Id = s.Id;
});
Mapper.CreateMap<GenreDto, Movie>()
.ForMember(d => d.Id, opt => opt.MapFrom(s => s.Id));
}
And this is my API Controller
public class MoviesController : ApiController
{
private ApplicationDbContext _context;
public MoviesController()
{
_context = new ApplicationDbContext();
}
// GET /api/movies
public IHttpActionResult GetMovies()
{
return Ok(_context.Movies.ToList().Select(Mapper.Map<Movie, MovieDto>));
}
//GET /api/movies/1
public IHttpActionResult GetMovie(int id)
{
var movie = _context.Movies.SingleOrDefault(m => m.Id == id);
if (movie == null)
return NotFound();
return Ok(Mapper.Map<Movie, MovieDto>(movie));
}
//POST /api/movies
[HttpPost]
public IHttpActionResult CreateMovie (MovieDto movieDto)
{
if (!ModelState.IsValid)
return BadRequest();
var movie = Mapper.Map<Movie>(movieDto);
_context.Movies.Add(movie);
_context.SaveChanges();
return Created(new Uri(Request.RequestUri + "/" + movie.Id), movieDto);
}
//PUT /api/movies/1
[HttpPut]
public void UpdateMovie(int id, MovieDto movieDto)
{
if (!ModelState.IsValid)
throw new HttpResponseException(HttpStatusCode.BadRequest);
var movieInDb = _context.Movies.SingleOrDefault(m => m.Id == id);
if (movieInDb == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
Mapper.Map(movieDto, movieInDb);
_context.SaveChanges();
}
//DELETE api/movies/1
[HttpDelete]
public void DeleteMovie(int id)
{
var movieInDb = _context.Movies.SingleOrDefault(m => m.Id == id);
if (movieInDb == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
_context.Movies.Remove(movieInDb);
_context.SaveChanges();
}
}
If I check the functionality with Postman, I can get the Data from the Database but if I try to Post something I get an error. The reason is the Genre but I don´t know if my mapping is wrong or if I need a kind of connection to my viewModel for the combination of the Movie and the Genre.
I tried a lot of things but I don´t really understand how to go with an API, Many to Many, Dtos, viewModel and Automapper. Can somebody give me an example and an explanation?
Thanks a lot!
Related
I know similar question have been asked many times but unfortunately after days of research still haven't been able to find a solution for my problem.
I try to explain in details what the issue is.
The following is a simple project which can reproduce the issue!
The problem:
I am using a custom Date Tracker so that I can not only add Created Date or Updated date at the time of saving entities but also soft delete my records.
However when I use this I get the following error
System.InvalidOperationException: 'The instance of entity type 'City' cannot be tracked because another instance with the key value '{Id: a6606535-76a5-4a23-b204-08d882481a95}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'
The strange thing is, it only happens sometimes, I'd say when I run the project for the first time it happens 90% of the time but then if I bypass the exception and hit the endpoint then it will happen only 10% of the time.
The code:
Models:
public class CitySync : ICreatedDateTracking
{
public long Id { get; set; }
public Guid CityId { get; set; }
public virtual City City { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public int Added { get; set; }
}
public partial class School : IDateTracking
{
public string Id { get; set; }
public string Name { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public virtual City City { get; set; }
public Guid CityId { get; set; }
public DateTimeOffset? DeletedDate { get; set; }
public DateTimeOffset UpdatedDate { get; set; }
}
public partial class City : IDateTracking
{
public Guid Id { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public string OwnerName { get; set; }
public virtual List<School> Schools { get; set; }
public virtual ICollection<CitySync> CitySync { get; set; }
public DateTimeOffset? DeletedDate { get; set; }
public DateTimeOffset UpdatedDate { get; set; }
}
DbContext:
public class ApplicationDbContext : DbContext
{
private readonly IChangeTracker _changeTracker;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IChangeTracker changeTracker)
: base(options)
{
_changeTracker = changeTracker;
}
public void DetachAllEntities()
{
var changedEntriesCopy = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.ToList();
foreach (var entry in changedEntriesCopy)
entry.State = EntityState.Detached;
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
Task.Run(async () => await _changeTracker.BeforeSaveChanges(this));
var saveChanges = base.SaveChanges(acceptAllChangesOnSuccess);
Task.Run(async () => await _changeTracker.AfterSaveChanges(this));
return saveChanges;
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
{
await _changeTracker.BeforeSaveChanges(this);
var saveChangesAsync = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
await _changeTracker.AfterSaveChanges(this);
return saveChangesAsync;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<School>(entity =>
{
entity.Property(e => e.Id).HasMaxLength(50);
entity.Property(e => e.Name).HasMaxLength(255);
entity.HasOne(x => x.City).WithMany(x => x.Schools);
entity.HasKey(x => new
{
x.Id,
x.CitytId
});
});
modelBuilder.Entity<City>(entity =>
{
entity.Property(e => e.OwnerName).HasMaxLength(255);
entity.HasMany(x => x.Schools).WithOne(x => x.City);
});
modelBuilder.Entity<CitySync>(entity =>
{
entity.HasOne(x => x.City).WithMany(x => x.CitySync);
});
foreach (var entityType in modelBuilder.Model.GetEntityTypes().Where(e => typeof(IDateTracking).IsAssignableFrom(e.ClrType)))
{
if (entityType.BaseType == null)
{
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(ConvertFilterExpression<IDateTracking>(e => e.DeletedDate == null, entityType.ClrType));
}
}
}
private static LambdaExpression ConvertFilterExpression<TInterface>(Expression<Func<TInterface, bool>> filterExpression, Type entityType)
{
var newParam = Expression.Parameter(entityType);
var newBody = ReplacingExpressionVisitor.Replace(filterExpression.Parameters.Single(), newParam, filterExpression.Body);
return Expression.Lambda(newBody, newParam);
}
public virtual DbSet<School> Schools { get; set; }
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<CitySync> CitySync { get; set; }
}
DateChangeTracker:
public class DateChangeTracker : IChangeTracker
{
public static EntityState[] AuditedEntityStates = { EntityState.Added, EntityState.Modified, EntityState.Deleted };
public DateChangeTracker()
{
}
public virtual Task BeforeSaveChanges(DbContext dbContext)
{
var now = DateTimeOffset.UtcNow;
foreach (var entry in dbContext.ChangeTracker.Entries().Where(e => AuditedEntityStates.Contains(e.State)).ToList())
{
if (entry.Entity is ICreatedDateTracking createdDateTracking)
{
if (entry.State == EntityState.Added)
{
createdDateTracking.CreatedDate = now;
}
}
if (entry.Entity is IUpdatedCreatedDateTracking updatedCreatedDateTracking)
{
updatedCreatedDateTracking.UpdatedDate = now;
}
if (entry.Entity is IDateTracking dateTracking)
{
if (entry.State == EntityState.Added)
{
dateTracking.CreatedDate = now;
}
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
dateTracking.DeletedDate = now;
}
}
}
return Task.CompletedTask;
}
public virtual Task AfterSaveChanges(DbContext dbContext)
{
return Task.CompletedTask;
}
}
DbContext Factory:
public class ApplicationDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new ApplicationDbContext(builder.Options, new DateChangeTracker());
}
}
IDate interfaces, implement created, updated, deleted fileds
SchoolService:
public class SchoolService : ISchoolService
{
private readonly ApplicationDbContext applicationDbContext;
public SchoolService(ApplicationDbContext applicationDbContext)
{
this.applicationDbContext = applicationDbContext;
}
public Guid PopulateCity()
{
var existingCity = applicationDbContext.Cities.FirstOrDefault();
if (existingCity == null)
{
var city = new Models.City { CreatedDate = DateTimeOffset.Now, OwnerName = "test" };
applicationDbContext.Cities.Add(city);
applicationDbContext.SaveChanges();
return city.Id;
}
return existingCity.Id;
}
public void ProcessSchools(Guid cityId)
{
var city = applicationDbContext.Cities.FirstOrDefault(x => x.Id == cityId);
if (city == null)
throw new Exception("City doesnt exist");
var citySync = new CitySync
{
CityId = city.Id,
CreatedDate = DateTimeOffset.Now,
};
var existingSchools = applicationDbContext.Schools.Where(x => x.CitytId == cityId).ToList();
// update schools if the exists
// add new ones if they dont
var schools = new List<School>();
if (!existingSchools.Any())
{
schools.Add(new School { CitytId = cityId, CreatedDate = DateTimeOffset.Now, Id = "1", Name = "school1" });
schools.Add(new School { CitytId = cityId, CreatedDate = DateTimeOffset.Now, Id = "2", Name = "school2" });
schools.Add(new School { CitytId = cityId, CreatedDate = DateTimeOffset.Now, Id = "3", Name = "school3" });
}
else
{
foreach (var item in existingSchools)
{
item.UpdatedDate = DateTimeOffset.Now;
}
applicationDbContext.SaveChanges();
}
var additions = schools.Except(existingSchools, new SchoolComparer()).ToList();
foreach (var school in additions)
{
school.CitytId = city.Id;
}
applicationDbContext.Schools.AddRange(additions);
city.UpdatedDate = DateTimeOffset.Now;
city.OwnerName = "Updated Name";
citySync.Added = additions.Count;
applicationDbContext.CitySync.Add(citySync);
applicationDbContext.SaveChanges();
}
}
public class SchoolComparer : IEqualityComparer<School>
{
public bool Equals(School x, School y)
{
return x?.Id == y?.Id;
}
public int GetHashCode(School obj)
{
return 0;
}
}
The error happens right on the following line
*applicationDbContext.CitySync.Add(citySync);*
Startup:
public class Startup
{
public Startup(IConfiguration configuration, IHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IChangeTracker), typeof(DateChangeTracker));
services.AddTransient<ISchoolService, SchoolService>();
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(connectionString); options.EnableSensitiveDataLogging(); }, ServiceLifetime.Scoped);
services.AddControllers();
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
}
}
}
Please note that this only happens if I use the date tracker, If I don't user the overridden saveChanges methods then all works fine.
Please try to use this way
services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlServer(connectionString,x => x.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)),ServiceLifetime.Transient);
services.AddTransient<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
I have a problem concerning entities in ASP.NET Core.
I use Entity Framework Core as data access library.
The issue I've come across happens when I'm trying to update an entity. After I modify the properties and call SaveChanges, the entity gets deleted and I don't understand why.
Here's the entity:
public class Contract
{
public int Id { get; set; }
[Required]
public DateTime ExpiryDate { get; set; }
[Required]
[Range(0, float.MaxValue)]
public float MonthlyFee { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public int CarId { get; set; }
public User User { get; set; }
public Car Car { get; set; }
}
Here's the related entities for reference:
public class User : IdentityUser
{
[Required]
[PersonalData]
public string Name { get; set; }
[Required]
[PersonalData]
public string Surname { get; set; }
[Required]
[PersonalData]
public string TaxCode { get; set; }
[Required]
[PersonalData]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
public string ProfilePictureUrl { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
}
public class Car
{
public int Id { get; set; }
[Required]
[RegularExpression("[A-Z][A-Z][0-9][0-9][0-9][A-Z][A-Z]")]
public string LicensePlate { get; set; }
public int CarModelId { get; set; }
public string FittingDescription { get; set; }
public Contract Contract { get; set; }
public ICollection<CarAccident> CarAccidents { get; set; }
public CarModel CarModel { get; set; }
}
Here's my update method in repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.User = entity.User;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Has anyone got any idea how to solve this?
UPDATE:
This is the new Update method:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await GetById(entity.Id);
if (dbContract == null)
return null;
var dbUser = await _userRepository.GetById(entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _carRepository.GetById(entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.Car = dbCar;
dbContract.User = dbUser;
//_context.Contracts.FromSqlInterpolated($"UPDATE dbo.Contracts SET ExpiryDate={entity.ExpiryDate}, MonthlyFee={entity.MonthlyFee} WHERE Id={entity.Id}");
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
Here's the Fluent API configuration:
private void _configureUsers(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOne(u => u.Contract)
.WithOne(c => c.User)
.HasForeignKey<Contract>(c => c.UserId);
}
private void _configureCars(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasAlternateKey(c => c.LicensePlate);
modelBuilder.Entity<Car>()
.HasOne(c => c.Contract)
.WithOne(c => c.Car)
.HasForeignKey<Contract>(c => c.CarId);
}
Both this methods get called in the OnModelCreating method of the context.
I've finally managed to solve my issue.
I was already tracking the entity in my api controller like that:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
var contract = await _contractService.GetContractDTO(id);
if (contract == null)
return NotFound();
var modifiedContract = await _contractService.UpdateContract(viewModel);
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
}
This type of approach works in one to many relationships, but evidently when you have one to one relationship and you have to objects that rapresent the same entity the ChangeTracker cannot track the changes correctly.
I post my new controller and repository code if someone will burst into my same problem.
Controller:
[HttpPut("{id}")]
[Authorize(Roles = "Backoffice")]
public async Task<ActionResult<ContractDTO>> PutContract(int id, [FromBody] PutContractViewModel viewModel)
{
if (viewModel == null || !ModelState.IsValid)
return BadRequest(new { message = "Your model is wrong" });
ContractDTO modifiedContract;
try
{
modifiedContract = await _contractService.UpdateContract(viewModel);
}
catch (EntityNotFoundException)
{
return NotFound();
}
if (modifiedContract == null)
return BadRequest(new { message = "User or car may be busy in another contract" });
return Ok(modifiedContract);
Service:
public async Task<ContractDTO> UpdateContract(PutContractViewModel viewModel)
{
try
{
return await ParseContractToContractDTO(await _contractRepository.Update(ParsePutContractViewModelToContract(viewModel)));
}
catch(EntityNotFoundException)
{
throw;
}
}
Repository:
public async Task<Contract> Update(Contract entity)
{
var dbContract = await _context.Contracts.Include(c => c.User).Include(c => c.Car).FirstOrDefaultAsync(c => c.Id == entity.Id);
if (dbContract == null)
{
throw new EntityNotFoundException();
}
var dbUser = await _context.Users.Include(u => u.Contract).FirstOrDefaultAsync(u => u.Id == entity.UserId);
if (dbUser == null)
return null;
var dbCar = await _context.Cars.Include(c => c.Contract).FirstOrDefaultAsync(c => c.Id == entity.CarId);
if (dbCar == null)
return null;
dbContract.ExpiryDate = entity.ExpiryDate;
dbContract.MonthlyFee = entity.MonthlyFee;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
return null;
}
return await GetById(entity.Id);
}
I want to thank you all, you've been very helpful and patient with me.
I'm sorry for my bad english, but here it goes, I am using Dapper with Dapper Dommel to simplify operations like crud, but in Dommel's Github says that it supports Join operations too, so I'm trying to implement it on my code, so far I managed to return a simple entity with Get.
But when I am trying to use it for more complex operations like join, it raises an exception.
The error message says: SqlException: Need declare the scalar variable "#product_id".
But in single Get< T > it works.
Packages:
Dapper
Dommel
Dapper-FluentMap.Dommel
Dapper-FluentMap
GitHub Dommel
https://github.com/henkmollema/Dommel
Does someone managed to use Dapper Dommel to return multiple entities in join queries with automatic mapping ?
public class DbContext : IDisposable
{
public SqlConnection Connection { get; private set; }
public DbContext()
{
Connection = new SqlConnection("Server=localhost;Database=BikeStores;Trusted_Connection=True;");
OpenConnection();
}
private bool OpenConnection()
{
try
{
if (Connection.State != System.Data.ConnectionState.Open)
Connection.Open();
return true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return false;
}
}
public void Dispose() => Connection.Close();
}
Dommel fluent map:
public class ProductMap : DommelEntityMap<Product>
{
public ProductMap()
{
ToTable("production.products");
Map(m => m.Id)
.ToColumn("product_id")
.IsKey()
.IsIdentity();
Map(m => m.Name)
.ToColumn("product_name");
Map(m => m.BrandId)
.ToColumn("brand_id");
Map(p => p.CategoryId)
.ToColumn("category_id");
Map(p => p.ModelYear)
.ToColumn("model_year");
Map(p => p.ListPrice)
.ToColumn("list_price");
}
}
Category mapping:
public class CategoryMap : DommelEntityMap<Category>
{
public CategoryMap()
{
ToTable("production.categories");
Map(m => m.Id)
.ToColumn("category_id")
.IsKey()
.IsIdentity();
Map(m => m.Name)
.ToColumn("category_name");
}
}
Register mappings:
public RegisterMappings()
{
FluentMapper.Initialize(config =>
{
config.AddMap(new ProductMap());
config.AddMap(new CategoryMap());
config.ForDommel();
});
}
Model:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public int BrandId { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
public int ModelYear { get; set; }
public decimal ListPrice { get; set; }
}
Get simple that works:
using (var connect = new DbContext())
{
var product = connect.Connection.Get<Product>(id);
return product;
}
Get Doesn't work:
using (var connect = new DbContext())
{
var prod = connect.Connection.Get<Product, Category, Product>(1, (product, category) =>
{
product.Category = category;
return product;
});
return prod;
}
I have a problem, when i add a new entity of SessionImages, and then add another one (By uploading it), it gets doubled.
For example :
Adding an image :
Adding another one afterwards :
Here's my controller code :
[HttpPost]
public ActionResult SessionImages(FormCollection collection)
{
int SessionID = Convert.ToInt32(collection[0]);
Sessions SessionModel = sessionsRepo.GetSessionById(SessionID);
bool uploadSucceded = Utility.Utility.UploadImages(this, Request.Files, Server.MapPath(Path.Combine("~/Photos/Sessions", SessionModel.Name)));
for (int i = 0; i < Request.Files.Count; i++)
{
if (Request.Files[i].ContentLength == 0)
{
continue;
}
SessionImages ImageModel = new SessionImages
{
Name = Request.Files[i].FileName,
Path = Path.Combine("~/Photos/Sessions/", SessionModel.Name, "actual", Request.Files[i].FileName),
Session = SessionModel,
SessionID = SessionID
};
sessionImagesRepo.Add(SessionModel, ImageModel);
}
return RedirectToAction("SessionImages",SessionModel.ID);
}
Here's the sessionImagesRepo.Add method :
public SessionImages Add(Sessions SessionModel, SessionImages ImageModel)
{
try
{
context.Entry(ImageModel).State = System.Data.EntityState.Added;
SessionModel.PhotosCount = SessionModel.Images.Count;
context.Entry(SessionModel).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
catch
{
return null;
}
return ImageModel;
}
Here's my entities :
namespace FP.Domain.Entities
{
public class Sessions
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime Date { get; set; }
public int PhotosCount { get; set; }
public string CoverPhoto { get; set; }
public List<SessionImages> Images { get; set; }
}
}
namespace FP.Domain.Entities
{
public class SessionImages
{
public int ImageID { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public int SessionID { get; set; }
public Sessions Session { get; set; }
}
}
Here's the configuration :
namespace FP.Domain.Configurations
{
public class SessionsConfig : EntityTypeConfiguration<Sessions>
{
public SessionsConfig()
{
ToTable("Sessions");
Property(p => p.Name).IsRequired();
Property(p => p.PhotosCount).IsRequired();
Property(p => p.CoverPhoto).IsRequired();
Property(p => p.Date).HasColumnType("date");
}
}
}
namespace FP.Domain.Configurations
{
public class SessionImagesConfig : EntityTypeConfiguration<SessionImages>
{
public SessionImagesConfig()
{
ToTable("SessionImages");
HasKey(e => e.ImageID);
Property(p => p.Path).IsRequired();
HasRequired(i => i.Session)
.WithMany(s => s.Images)
.HasForeignKey(i => i.SessionID);
}
}
}
Try changing the repository method like this:
public SessionImages Add(Sessions SessionModel, SessionImages ImageModel)
{
try
{
SessionModel.Images.Add(ImageModel);
SessionModel.PhotosCount = SessionModel.Images.Count;
context.Entry(SessionModel).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
catch
{
return null;
}
return ImageModel;
}
And in the action remove the line that I've commented:
SessionImages ImageModel = new SessionImages
{
Name = Request.Files[i].FileName,
Path = Path.Combine("~/Photos/Sessions/", SessionModel.Name, "actual", Request.Files[i].FileName),
//Session = SessionModel,
SessionID = SessionID
};
I'd suggest you drop these lines, if you can workaround it (I can see the point though, in the count)...
context.Entry(ImageModel).State = System.Data.EntityState.Added;
SessionModel.PhotosCount = SessionModel.Images.Count;
context.Entry(SessionModel).State = System.Data.EntityState.Modified;
That's probably what's messing with it. I've seen similar problems more than once - and then I usually end up doing quite the opposite:
context.Entry(Parent).State = System.Data.EntityState.Unchanged;
Everything you're doing there is to get the Count working.
I can understand the optimization reasons, but you do have that count in the collection inherently. I know this is faster if you don't want to load etc.
I'm not sure what to suggest - but I'm suspecting that to be the 'culprit'.
Try w/o it, turn off and disregard count while testing. See what
happens. If that's it, then think about how to reorganize that a bit.
How i can save old data in action [post]Edit?
I have these two models:
public class Task
{
public int ID { get; set; }
public int sheet_length { get; set; }
public int sheets_num { get; set; }
[Required]
public int spoolID { get; set; }
[ForeignKey("spoolID")]
public virtual Spool Spool { get; set; }
}
public class Spool
{
public int ID { get; set; }
public string name { get; set; }
public int weight { get; set; }
public int weight_meter { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
When I create Task - Spool.Weight changes
Task.Spool.Weight = Task.Spool.Weight - (Task.sheet_length * Task.sheets_num * Task.Spool.weight_meter)
When I edit a task - I want to do this:
TaskOld.Spool.Weight = TaskOld.Spool.Weight + (TaskOld.sheet_length * TaskOld.sheets_num * TaskOld.Spool.weight_meter)
Task.Spool.Weight = Task.Spool.Weight - (Task.sheet_length * Task.sheets_num * Task.Spool.weight_meter)
But when I try to get old data in edit action
[HttpPost]
public ActionResult Edit(Task task)
{
if (ModelState.IsValid)
{
taskOld = dbContext.Tasks.Single (t => t.ID == task.ID);
////some code
db.Tasks.Attach(task);
db.SaveChanges();
return RedirectToAction("Index", "Task", new { id = task.orderID });
}
///some code }
I get an ObjectStateManager error (it has object with the same key)
The problem occurs when you attach the new task.
if (ModelState.IsValid)
{
taskOld = dbContext.Tasks.Single (t => t.ID == task.ID);
////some code
// Error here! db.Tasks already contains something for the Id
// Can't have two tasks with the same Id. Attach doesn't update the
// existing record, but adds the 'task' to the object graph for tracking.
db.Tasks.Attach(task);
db.SaveChanges();
return RedirectToAction("Index", "Task", new { id = task.orderID });
}
Both have the same Id, and the ObjectStateManager can't track two of the same object with the same Id.
A more normal approach is to map the Task input to taskOld:
if (ModelState.IsValid)
{
taskOld = dbContext.Tasks.Single (t => t.ID == task.ID);
// ... Some code ...
// taskOld is already attached to the DbContext, so just map the updated
// properties.
taskOld.Property1 = task.Property1;
taskOld.Property2 = task.Property2;
...
db.SaveChanges();
return RedirectToAction("Index", "Task", new { id = task.orderID });
}
Or, instead of assigning properties individually, you can call UpdateModel(taskOld) and it will attempt to do the same thing.
You can following Leniency code, or:
[HttpPost]
public ActionResult Edit(Task t)
{
if (ModelState.IsValid)
{
db.Entry(t).State = EntityState.Modified;
db.SaveChanges();
}
return View(t);
}