Entity framework creates new entity with relation to existing entity - c#

I have Student entity which has Class (Class can have many students)
This is my Student.cs and StudentDto.cs
public partial class Students
{
public int Id { get; set; }
public string FullName { get; set; }
public int? FkClass { get; set; }
}
public class StudentsDTO
{
public int Id { get; set; }
public string FullName { get; set; }
public int FkClass { get; set; }
public int FkClassNavigationId { get; set; }
public string FkClassNavigationTitle { get; set; }
}
When I am creating new student and select class id from the dropdown, instead of using that class, new class is created (with null values and random ID).
I am not sure where is the best way to fix this mistake? Is it the problem in Automapper, context.cs class or my add method is wrong itself?
In my service I do this:
public Students PostStudent(StudentsDTO item)
{
var sub = _mapper.Map<Students>(item);
_repository.PostStudent(sub);
return sub;
}
and In the repository I do the following:
public void PostStudent(Students c)
{
this.museumContext.Add(c);
this.museumContext.SaveChanges();
}
Automapper.cs:
CreateMap<Students, StudentsDTO>()
.ReverseMap();
and context.s
modelBuilder.Entity<Students>(entity =>
{
entity.ToTable("students");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.FkClass).HasColumnName("fkClass");
entity.Property(e => e.FullName)
.HasColumnName("fullName")
.HasMaxLength(255);
entity.HasOne(d => d.FkClassNavigation)
.WithMany(p => p.Students)
.HasForeignKey(d => d.FkClass)
.HasConstraintName("Include");
});
Can someone help me understand how to fix this?
The weird thing is if I add var a = _classesService.GetClassById(s.FkClass); to my controller it works fine. It's not that I'm assigning what I retrieve or anything.
[HttpPost]
[AllowAnonymous]
public IActionResult PostStudent([FromBody] StudentsDTO s)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(ModelState);
}
try
{
var a = _classesService.GetClassById(s.FkClass);
var sub = this._service.PostStudent(s);
return Ok();
}
catch (Exception err)
{
return BadRequest(err);
}
}

Related

EF Core 3.x: copying record does not work as expected

I have the following model:
public partial class Device
{
public string Id { get; set; }
public string IMEI { get; set; }
public virtual DeviceVerizon VerizonData { get; set; }
}
public class DeviceVerizon
{
public int DeviceId { get; set; }
public virtual Device Device { get; set; }
public string ServicePlan { get; set; }
public string State { get; set; }
}
public class DeviceVerizonMap : IEntityTypeConfiguration<DeviceVerizon>
{
public void Configure(EntityTypeBuilder<DeviceVerizon> builder)
{
builder.ToTable(nameof(DeviceVerizon));
builder.HasKey(d => d.DeviceId);
builder.HasOne(o => o.Device)
.WithOne(o => o.VerizonData)
.HasForeignKey<DeviceVerizon>(a => a.DeviceId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
;
}
}
public partial class DeviceMap : IEntityTypeConfiguration<Device>
{
public override void Configure(EntityTypeBuilder<Device> builder)
{
builder.ToTable(nameof(Device));
builder.HasKey(d => d.Id);
builder.HasIndex(d => d.IMEI).IsUnique();
}
}
so, DeviceId is PR and FK for DeviceVerizon.
I want to reassign DeviceVerizon from one to another record of Device.
I tried to it just change DeviceId from source Device to target device and no success: EF 3.x: Change Primary key value
Ok, I try to create copy of DeviceVerizon data by the following way:
CreateMap<DeviceVerizon, DeviceVerizon>()
.ForMember(d => d.DeviceId, o => o.Ignore())
;
and then:
var newVerizonData = _mapper.Map<DeviceVerizon, DeviceVerizon>(verizonData);
newVerizonData.DeviceId = targetDeviceId;
(also, I tried targetDevice.VerizonData = newVerizonData; instead of newVerizonData.DeviceId = targetDeviceId;), in debugger I see, that targetDevice.VerizonData has expected data and that object has expected DeviceId (targetDeviceId), but after await _context.SaveChangesAsync(); I don't have this verizon data at all in DB!
What is wrong?
Problem was because Automapper maps also Device entity and no matter, set you DeviceId or not... This solution works:
CreateMap<DeviceVerizon, DeviceVerizon>()
.ForMember(d => d.DeviceId, o => o.Ignore())
.ForMember(d => d.Device, o => o.Ignore())
;

ef core: mapping value object using OwnsMany requires primary key to be defined

I have a class
public class Document
{
public string TranId { get; set; }
public Record Record { get; set; }
public List<Error> Errors { get; set; }
}
public class Record
{
public string TranId { get; set; }
public List<DataResult> DataResults { get; set; }
}
public class DataResult
{
public string DataSourceName { get; set; }
public List<DataField> DataFields { get; set; }
public List<CustomField> CustomFields { get; set; }
}
I want to map Record and DataResult classes as Value objects so I tried to map as
public void Configure(EntityTypeBuilder<Document> builder)
{
builder.ToTable("Document");
builder.HasKey(x => x.TranId);
builder.OwnsOne(a => a.Record, a =>
{
a.ToTable("Doc_Record");
a.Property(p => p.TranId).HasMaxLength(100)
.HasColumnName("TranID")
.HasDefaultValue("");
a.OwnsMany(x => x.DataResults, x =>
{
x.ToTable("Doc_Rec_DataResults");
x.Property(p => p.DataSourceName).HasMaxLength(150)
.HasColumnName("DataSourceName")
.HasDefaultValue("");
});
}
}
When I try to add this migration it errors with a message:
The entity type 'DataResult' requires a primary key to be defined.
And why it requires primary key cause I'm trying to map as a value
object?
Someone suggested using this link and I try to add
a.OwnsMany(x => x.DataResults, x =>
{
x.WithOwner().HasForeignKey("RecordId");
x.ToTable("Doc_Rec_DataResults");
x.Property(p => p.DataSourceName).HasMaxLength(150)
.HasColumnName("DataSourceName")
.HasDefaultValue("");
});
but this approach is not working cause WithOwner is available from .net core 3 where I'm using .net core 2 (and do I really need to add RecordId property into Record class (it's a value object).
It would be great if someone can provide an example of how to map collection of value objects with OwnsMany in EF Core 2.
In this ef core 2.2 example, we have a Company that owns a collection of Addresses, here's the implementation. note that i omitted some useful code to stick to the point, refer to the full example for further explanation. Also note that this feature OwnsMany() is not available in pre ef core 2.2
public class CompanyAddress
{
public string City { get; }
public string AddressLine1 { get; }
}
public class Company
{
private List<CompanyAddress> addresses = new List<CompanyAddress>();
public Guid Id { get; }
public string Name { get; }
public IEnumerable<CompanyAddress> Addresses { get => this.addresses; }
public void AssignAddress(CompanyAddress address)
{
var exists = this.addresses.Contains(address);
if (!exists)
{
this.addresses.Add(address);
}
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>().OwnsMany<CompanyAddress>("Addresses", a =>
{
a.HasForeignKey("CompanyId");
a.Property(ca => ca.City);
a.Property(ca => ca.AddressLine1);
a.HasKey("CompanyId", "City", "AddressLine1");
});
}
here's a repo of full solution for the article's owner

InvalidOperationException: The instance of entity cannot be tracked - EF Core

I am attempting to save date in multiple tables with a one-to-many relationship in using EF Core. When I do, I get this error:
InvalidOperationException: The instance of entity type 'OrganizationGroupEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Here is my code:
Request model:
public class Organization
{
public Organization()
{ }
public Organization(OrganizationEntity organizationEntity, List<OrganizationGroupEntity> organizationGroupEntities)
{
Id = organizationEntity.Id;
Name = organizationEntity.Name;
Groups = ToList(organizationGroupEntities);
}
public int Id { get; set; }
public string Name { get; set; }}
public List<OrganizationGroup> Groups { get; set; }
private List<OrganizationGroup> ToList(List<OrganizationGroupEntity> organizationGroupEntities)
{
return organizationGroupEntities.Select(
entity => new OrganizationGroup(entity)
).ToList();
}
}
public class OrganizationGroup
{
public OrganizationGroup()
{ }
public OrganizationGroup (OrganizationGroupEntity entity)
{
Id = entity.Id;
Group = entity.Group;
}
public int Id { get; set; }
public string Group { get; set; }
}
Entity models:
public class OrganizationEntity
{
public OrganizationEntity()
{ }
public OrganizationEntity(Organization model)
{
Id = model.Id;
Name = model.Name;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationGroupEntity
{
public OrganizationGroupEntity()
{ }
public OrganizationGroupEntity(int organizationId, OrganizationGroup model)
{
Id = model.Id;
OrganizationId = organizationId;
Group = model.Group;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int OrganizationId { get; set; }
public string Group { get; set; }
}
dbContext:
public DbSet<OrganizationEntity> Organizations { get; set; }
public DbSet<OrganizationGroupEntity> OrganizationGroups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<OrganizationEntity>()
.ToTable("Organizations", "dbo");
modelBuilder.Entity<OrganizationGroupEntity>()
.ToTable("OrganizationGroups", "dbo");
}
repository:
public async Task<Organization> UpdateOrganization(Organization request)
{
// Get the org entity
var organizationEntity = new OrganizationEntity(request);
// get the org groups entities
var groupEntities = request.Groups
.Select(
group => new OrganizationGroupEntity(request.Id, group)
).ToList();
// Get the group entities to remove
var oldEntities = GetOrganizationGroups(request.Id);
var entitiesToRemove = new List<OrganizationGroupEntity>();
foreach (var oldEntity in oldEntities.Result)
{
if (!groupEntities.Any(e => e.Id == oldEntity.Id))
{
entitiesToRemove.Add(oldEntity);
}
}
using (var transaction = _context.Database.BeginTransaction())
{
_context.Organizations.Update(organizationEntity);
_context.OrganizationGroups.UpdateRange(groupEntities); // <-- Fails here
_context.OrganizationGroups.RemoveRange(entitiesToRemove);
await _context.SaveChangesAsync();
transaction.Commit();
}
return request;
}
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}
It turns out when I was getting the current groupEntities in order to fins out what to remove I was initiating tracking on that table. Adding AsNoTracking() to GetOrganizationGroups solved my issue. Like so:
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.AsNoTracking()
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}

linq query regarding many to many relation

I have a many to many relation set up this way
public class Produto
{
public int ProdutoID { get; set; }
public string Nome { get; set; }
public double Preco { get; set; }
public double Altura {get ; set; }
public double Largura {get ; set; }
public double Profundidade {get ; set; }
public virtual ICollection<ProdutoParte> ProdutoPartes { get; set; }
}
public class Parte
{
public int ParteID { get; set; }
public string Nome { get; set; }
public double Preco { get; set; }
public double Altura {get ; set; }
public double Largura {get ; set; }
public double Profundidade {get ; set; }
public virtual ICollection<ProdutoParte> ProdutoPartes { get; set; }
}
public class ProdutoParte
{
public int ProdutoID { get; set; }
public Produto Produto { get; set; }
public int ParteID { get; set; }
public Parte Parte { get; set; }
}
This is my dbcontext
public DbSet<Produto> Produtos { get; set; }
public DbSet<Parte> Partes {get;set;}
public DbSet<ProdutoParte> ProdutoPartes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProdutoParte>()
.HasKey(pp => new { pp.ProdutoID, pp.ParteID });
modelBuilder.Entity<ProdutoParte>()
.HasOne(pp => pp.Produto)
.WithMany(pr => pr.ProdutoPartes)
.HasForeignKey(bc => bc.ProdutoID);
modelBuilder.Entity<ProdutoParte>()
.HasOne(pp => pp.Parte)
.WithMany(pa => pa.ProdutoPartes)
.HasForeignKey(pp => pp.ParteID);
}
One Produto(product) can be composed of many Parte (parts) and
one Parte (part) can be used to create many Produto(products).
I'm trying to write the following query:
produto/{id}/partes
That returns all the parts being used in the product with {id}
This is what I have now
public IActionResult GetPartes([FromRoute] int id)
{
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Produto produto = _context.Produtos.SingleOrDefault(p => p.ProdutoID == id);
if(produto == null)
{
return NotFound();
}
var parts = _context.ProdutoPartes.Where(p => p.ProdutoID == id);
return Ok(parts);
}
With the following test data (still using inmemorydatabse)
if (_context.Produtos.Count() == 0)
{
_context.Produtos.Add(new Produto() {ProdutoID=1, Nome="product1" });
_context.Partes.Add(new Parte() {ParteID=1, Nome="part1" });
_context.Partes.Add(new Parte() {ParteID=2, Nome="part2" });
_context.Partes.Add(new Parte() {ParteID=3, Nome="part3" });
_context.ProdutoPartes.Add(new ProdutoParte() {ProdutoID=1, ParteID=1 });
_context.ProdutoPartes.Add(new ProdutoParte() {ProdutoID=1, ParteID=2 });
_context.ProdutoPartes.Add(new ProdutoParte() {ProdutoID=1, ParteID=3 });
_context.SaveChanges();
}
However using postman over json all I get is this when calling
get api/produto/1/partes
[{"produtoID":1,"produto":{"produtoID":1,"nome":"product1","preco":0.0,"altura":0.0,"largura":0.0,"profundidade":0.0,"produtoPartes":[
When it should be a list of parts.
Would appreciate any help.
When it should be a list of parts.
You aren't asking for a list of parts: you're asking for a list of ProdutoPartes. There are a few ways you can change your query to get what you want. One is:
var parts = _context.Partes
.Where(p => p.ProdutoPartes.Any(pp => pp.ProdutoID == id))
.ToList();
A few other suggestions:
Create a DTO class that represents just the data you really want to send back as part of your API. Use a .Select() statement to map from your Entities to this DTO. That way you don't get tons more information sent back than you really intended to.
It looks like ProdutoPartes is really just an intermediate mapping table. Remove that from your model, in favor of a many-to-many mapping between Produto and Parte. This way, your query becomes:
var parts = _context.Partes
.Where(p => p.Produtos.Any(pp => pp.ProdutoID == id))
.ToList();
This query will give you all the parts for the specified ProdutoID.
List<Parte> parts = _context.ProdutoPartes.Where(x => x.ProdutoID == id)
.Select(x = x.Parte).ToList();
Is the data in your Parte class sufficient enough? Does it have all the info the client needs? If yes, then you are good.
If not, create a ParteDto:
public class ParteDto
{
public Parte Parte { get; set; }
// Put other properties here
}
Modify your query:
List<ParteDto> parts = _context.ProdutoPartes.Where(x => x.ProdutoID == id)
.Select(x = new ParteDto { Parte = x.Parte /* , other properties */ })
.ToList();
To be honest, if you are speaking to the DbContext directly from your controller, you do not even need a DTO and you can just return an anonymous type like below. Please note there is no type after the new keyword:
var parts = _context.ProdutoPartes.Where(x => x.ProdutoID == id)
.Select(x = new { Parte = x.Parte /* , other properties */ })
.ToList();

Entity Framework 4.3: Multiplicity constraint violated

I have 2 classes:
public class FlightCostInfo : BaseEntity<Guid>
{
public Flight FlightInfo { get; set; }
public virtual ICollection<Cost> Costs { get; set; }
public virtual ICollection<PriceCalculationNotification> Notifications { get; set; }
public Guid CalculationResultId { get; set; }
}
public class Cost : BaseEntity<Guid>
{
public BasePrice Price { get; set; }
public decimal Amount { get; set; }
public Vendor Vendor { get; set; }
public Guid FlightCostInfoId { get; set; }
}
And mapping for them:
internal class FlightCostInfoMapping : EntityTypeConfiguration<FlightCostInfo>
{
public FlightCostInfoMapping()
{
HasKey(i => i.Id);
Property(i => i.CalculationResultId).HasColumnName("CalculationResult_Id");
HasOptional(i => i.FlightInfo);
HasMany(i => i.Costs).WithRequired().HasForeignKey(c => c.FlightCostInfoId);
HasMany(i => i.Notifications).WithRequired().HasForeignKey(n => n.FlightCostInfoId);
}
}
internal class CostMapping : EntityTypeConfiguration<Cost>
{
public CostMapping()
{
HasKey(c => c.Id);
Property(c => c.FlightCostInfoId).HasColumnName("FlightCostInfo_Id");
HasRequired(c => c.Price);
HasRequired(c => c.Vendor);
}
}
When I'm saving List of FlightCostInfo where each contains one or more Cost objects I recieve following error:
Multiplicity constraint violated. The role FlightCostInfo_Costs_Source of the relationship Charges.Infrastructure.DataAccess.FlightCostInfo_Costs has multiplicity 1 or 0..1
I don't have any idea why this happens. Could anyone help?
Update:
Code to save list of FlightCostInfo:
public virtual void Save(IEnumerable<TObject> entities)
{
Context.Configuration.AutoDetectChangesEnabled = false;
entities.ToList().ForEach(entity =>
{
if (Equals(entity.Id, default(TKey)) || !Context.ChangeTracker.Entries<TObject>().ToList().Any(dbEntry => dbEntry.Entity.Id.Equals(entity.Id)))
{
Set.Add(entity);
}
});
Context.ChangeTracker.DetectChanges();
SaveChanges();
Context.Configuration.AutoDetectChangesEnabled = true;
}
protected void SaveChanges()
{
var entriesWithGuidKey = Context.ChangeTracker.Entries<BaseEntity<Guid>>().Where(e => e.Entity.Id == Guid.Empty).ToList();
entriesWithGuidKey.ForEach(e => e.Entity.Id = Guid.NewGuid());
var entriesWithPeriodicValidity = Context.ChangeTracker.Entries<IPeriodicValidityObject>().ToList();
entriesWithPeriodicValidity.ForEach(e =>
{
if (e.State != System.Data.EntityState.Unchanged)
{
e.Entity.ChangedDate = DateTime.UtcNow;
}
});
Context.SaveChanges();
}
The problem appeared to be in Equals overload for BaseEntity. So EF thought that all Cost objects in FlightCostInfo collection are equal.
Closing the question

Categories

Resources