EF Core on Npgsql AddRange indices - c#

I have to copy a collection of recodrs and add them to a db with new Ids.
var subEntities= ct.SubEntities.Where(qf => qf.ParentEntityId == oldParentEntityId).ToList();
subEntities.ForEach(qf => { qf.ParentEntityId = newParentEntityId; qf.Id = default(int); });
ct.SubEntities.AddRange(subEntities);
When AddRange has run all entities subEntities has awkward Ids like -2147482647 and they go into db though there is a correct sequence. How to fix it?
My entity classes and mapping:
public class SubEntity
{
public int Id { get; set; }
public Guid ParentEntityId { get; set; }
public virtual ParentEntity ParentEntity { get; set; }
//props
}
public class ParentEntity
{
public Guid Id { get; set; }
public virtual List<SubEntity> SubEntities { get; set; }
//props
}
//OnModelCreating
builder.Entity<ParentEntity>()
.HasMany(q => q.SubEntities)
.WithOne(qf => qf.ParentEntity)
.HasForeignKey(qf => qf.ParentEntityId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade);

The problem was the way the ParentEntity were extracted. It were tracked by EF (so did the SubEntities too I suppose), so I tried to add a collection already being tracked. I don't quite understand how EF works in this case but the solution was:
var subEntities= ct.SubEntities
.Where(qf => qf.ParentEntityId == oldParentEntityId)
.AsNoTracking()
.ToList();

Related

The instance of entity type X cannot be tracked because another instance with the key value '{Id:}' is already being tracked

i am using entity framework core 7 in my .net 7 project. Here is my update method
public async Task UpdateAsync(int id, SiteDto dto)
{
Site site = await _context.Sites.Where(x => x.Id == id)
.Include(x => x.Network)
.Include (x => x.Centre)
.Include(x => x.SiteDayParts).ThenInclude(x => x.SiteFrames)
.Include(x => x.Resolution)
.SingleOrDefaultAsync();
_mapper.Map(dto, site);
await _context.SaveChangesAsync();
}
SiteDto object, context has the same fields
public class SiteDto
{
public string Name { get; set; }
public string Address { get; set; }
public MediaFormat Formats { get; set; }
public int ResolutionId { get; set; }
public List<SiteDayPartDto> SiteDayParts { get; set; }
}
public class SiteDayPartDto
{
public int Id { get; set; }
public TimeSpan StartTime { get; set; }
public TimeSpan EndTime { get; set; }
public int Duration { get; set; }
public List<SiteFrameDto> SiteFrames { get; set; }
}
public class SiteFrameDto
{
public int Id { get; set; }
public int FrameId { get; set; }
}
Mapping config
CreateMap<SiteDto, Site>();
CreateMap<SiteDayPartDto, SiteDayPart>()
.ForMember(dest => dest.AdLength, opt => opt.MapFrom(x => x.Duration));
CreateMap<SiteFrameDto, SiteFrame>()
When I trying to save changes, I get an error "The instance of entity type 'SiteFrame' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
Database - Postgresql.
Context registered as scoped, mapper, as I know, don`t create new object.
It is interesting that, if I use entity framework 6, it works well.
I have solved the problem like this. First, install Automapper.Collections and add:
services.AddAutoMapper((serviceProvider, automapper) =>
{
automapper.AddCollectionMappers();
}, Assembly.GetExecutingAssembly());
Then if you have nested list with a nested list, as in my example, add this mapping:
CreateMap<SiteDayPartDto, SiteDayPart>()
.EqualityComparison((odto, o) => odto.Id == o.Id)
CreateMap<SiteFrameDto, SiteFrame>()
.EqualityComparison((odto, o) => odto.Id == o.Id)
EqualityComparison will check if entity contains dto or not (it may be useful https://github.com/AutoMapper/Automapper.Collection). Good luck!

Nested include Entity Framework Core

I'm using EF Core for my project. And I have a problem with nested query in EF Core.
I have 2 classes:
public class PermissionGroupDefinitionEntity : IEntity
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<PermissionDefinitionEntity> PermissionDefinitions { get; set; }
}
public class PermissionDefinitionEntity : IEntity
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public string DisplayName { get; set; }
public bool IsEnabled { get; set; }
public virtual string GroupName { get; set; }
public virtual PermissionGroupDefinitionEntity Group { get; set; }
public virtual ICollection<PermissionDefinitionEntity> Children { get; set; }
}
and this is the ApplicationDbContext:
builder.Entity<PermissionDefinitionEntity>().HasOne(r => r.Group).WithMany(r => r.PermissionDefinitions).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PermissionDefinitionEntity>().HasOne(r => r.Parent).WithMany(r => r.Children).OnDelete(DeleteBehavior.Cascade);
I want query all PermissionGroupDefinitionEntity included PermissionDefinitionEntity and self referencing of PermissionDefinitionEntity.
Can I do that with EF Core?
You need to recursively load PermissionDefinitions that placed in the PermissionGroupDefinitionEntity.
First, you should load all PermissionGroupDefinitionEntities including its children using the following query :
var query = _dbContext.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(p => p.PermissionDefinitions )
.ThenInclude(p => p.Children)
.ToListAsync();
Since every PermissionGroupDefinitionEntity has a list of PermissionDefinition you need a nested loops like this code :
foreach (var PermissionGroupDefinitionEntity in PermissionGroupDefinitionEntities)
{
foreach (var PermissionDefinitions in PermissionDefinitions)
{
}
}
Then in the inner loop you should call your recursive function.
See following link (sample for get all children recursively in Entity Framework Core)
https://patrickdesjardins.com/blog/how-to-load-hierarchical-structure-with-recursive-with-entity-framework-5
This way has terrible performance and I don't recommend that.
In this case it's seems you must write a stored procedure in SQL for better performance.
You can use .ThenInclude(i => ...) like so
var query = _context.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(i => i.PermissionDefinitions)
.ThenInclude(i => i.Group)
.AsQueryable();
Edit:
var query = _context.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(i => i.PermissionDefinitions)
.ThenInclude(i => i.Children)
.AsQueryable();

Entity Framework Core update column with foreign key reference

This is the simplified version of the table structure I have:
[Table("PolicyMapping")]
public class PolicyMapping
{
[Key]
public Guid Id { get; set; }
public Policy PolicyA { get; set; }
public Policy PolicyB { get; set; }
public Lookup_Bank Bank { get; set; }
}
[Table("Policy")]
public class Policy
{
[Key]
public Guid Id { get; set; }
public string PolicyNm { get; set; }
public string Description { get; set; }
}
[Table("Lookup_Bank")]
public class Lookup_Bank
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
I am working on the edit screen for policy mapping where you can have the same values for PolicyA and PolicyB attributes.
After using automapper for DTO to the entity, here is my entity object looks like:
var policyMapping = new PolicyMapping
{
Id = "b27fb632-330b-46be-a649-2e2463d58626",
PolicyA = new Policy
{
Id = "a4f1cf6f-034d-4727-ab8f-49e95b2c9d23",
PolicyNm = null,
Description = null
},
PolicyB = new Policy
{
Id = "a4f1cf6f-034d-4727-ab8f-49e95b2c9d23",
PolicyNm = null,
Description = null
},
Bank = new Lookup_Bank()
{
Id = "98ed2bae-631b-490c-8ddf-3e02232d4231",
Name = null,
Code = null
}
}
I am mapping only selected id value of dropdown to entity id using automapper. Values are present for Code, Description and other attributes in the database. It's just not getting populated after automapper.
dbContext.PolicyMapping.Attach(policyMapping);
dbContext.SaveChanges();
This is the error, I am getting
The instance of entity type Policy cannot be tracked because another instance with the same key value for {'a4f1cf6f-034d-4727-ab8f-49e95b2c9d23'} is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
The reason for the error maybe because I am attaching two different entities with the same Id. I am still not sure how can I make it work in the most efficient way?
Solution 1: (not efficient)
var fromdatabase = dbContext.PolicyMapping.Include(x => x.PolicyA)
.Include(x => x.Bank)
.Include(x => x.PolicyB)
.FirstOrDefault(x => x.Id == policyMapping.Id);
fromdatabase.PolicyA = dbContext.Policy.FirstOrDefault(x => x.Id == policyMapping.PolicyA.Id);
fromdatabase.PolicyB = dbContext.Policy.FirstOrDefault(x => x.Id == policyMapping.PolicyB.Id);
dbContext.PolicyMapping.Attach(fromdatabase);
dbContext.SaveChanges();
This is working. But I would like to avoid a trip to the database just to fetch the entire entity.
Edit: Based on Xing's answer
#Xing, pointed out to change the Model structure by adding navigational properties and some changes in OnModelCreating method. (This method is currently blank in my code)
However, I went through a couple of articles (This & This) related to EF Core Code First approach, none of them are saying about navigational properties and all.
I am wondering how they are updating the column in this scenario?
If you just would like to update the Id of the navigation properties, you could add foreign key for them and update it.
1.Model:
public class PolicyMapping
{
[Key]
public Guid Id { get; set; }
public Guid PolicyAId { get; set; }
public Policy PolicyA { get; set; }
public Guid PolicyBId { get; set; }
public Policy PolicyB { get; set; }
public Guid BankId { get; set; }
public Lookup_Bank Bank { get; set; }
}
2.DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PolicyMapping>()
.HasOne(x => x.PolicyA)
.WithOne()
.HasForeignKey<PolicyMapping>(p => p.PolicyAId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PolicyMapping>()
.HasOne(x => x.PolicyB)
.WithOne()
.HasForeignKey<PolicyMapping>(p => p.PolicyBId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<PolicyMapping>()
.HasOne(x => x.Bank)
.WithOne()
.HasForeignKey<PolicyMapping>(p => p.BankId)
.OnDelete(DeleteBehavior.Restrict);
}
3.Add migrations.
4.Controller:
var policyMapping = new PolicyMapping
{
Id = new Guid("b27fb632-330b-46be-a649-2e2463d58626"),
PolicyAId = new Guid("a4f1cf6f-034d-4727-ab8f-49e95b2c9d23"),
PolicyBId = new Guid("a4f1cf6f-034d-4727-ab8f-49e95b2c9d23"),
BankId = new Guid("98ed2bae-631b-490c-8ddf-3e02232d4231")
};
dbContext.PolicyMapping.Attach(policyMapping);
dbContext.Entry(policyMapping).Property("PolicyAId").IsModified = true;
dbContext.Entry(policyMapping).Property("PolicyBId").IsModified = true;
dbContext.Entry(policyMapping).Property("BankId").IsModified = true;
dbContext.SaveChanges();
Then you could retrieve PolicyA or PolicyB from their foreign key PolicyAId or PolicyBId
var policyA = dbContext.Policy.FirstOrDefault(x => x.Id == policyMapping.PolicyAId);

Many to many in database but 1-to-1 in entity framework

I started working on an ongoing project and it has a many-to-many relationship in the database and in some parts of the code too, but I realized that even the relationship being many-to-many in the model there is always only one line linking the two entities (confirmed with the author). This is what I mean: The two entities are task and task list and a task only belongs to a task list. Models below:
public class ProjectTask
{
public long Id { get; set; }
// other non related properties
}
public class ProjectTaskList
{
public long Id { get; set; }
public DateTime? DateEnd { get; set; }
// other non related properties
}
// link between task list and task
public class ProjectTaskListTask
{
public long ProjectTaskId { get; set; }
public ProjectTask ProjectTask { get; set; }
public long ProjectTaskListId { get; set; }
public ProjectTaskList ProjectTaskList { get; set; }
public int Order { get; set; }
}
And its configuration in the OnModelCreating method of the context class:
modelBuilder.Entity<ProjectTaskListTask>()
.HasKey(a => new { a.ProjectTaskId, a.ProjectTaskListId });
modelBuilder.Entity<ProjectTaskListTask>()
.HasOne(u => u.ProjectTaskList)
.WithMany(u => u.Tasks)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
My problem is: In some parts of my code I need to know the Task List of a task, I need to use it in Where queries to do some validations, like : Tasks.Where(p => p.TaskList.DateEnd == null).
How can I add a Not Mapped property to the ProjectTask entity so I could do that? I'm using Entity Framework Core 2.
Thanks for any help
Without changing the underlying data structure, could you query ProjectTaskListTask? Something along the lines...?
ProjectTaskListTask
.Include(p => p.ProjectTaskList)
.Include(p => p.ProjectTask)
.Where(p => p.ProjectTaskList.DateEnd == null)
.Select(p => p.ProjectTask);

How to map a related table with no primary key with fluent-NHibernate

Looks a common situation to me: I have two tables:
documents:
dID (pk, int), dName(varchar)
and document_options:
dID (int), oType(int), oValue(varchar)
I would like to have a class Document with a property Options (a List of DocumentOption class)
Since document_options has no PK I cannot use HasMany, and rows from this table don't seem like 'real' entities anyway...
I see a way to generate an auto-number key for document options and map with HasMany, or maybe create a composite ID, but I'd like to know if there is a better option that I don't know about.
In this case, DocumentOptions is a value object, since it has no identity of its own and has no meaning outside of the document it belongs to. So, you would use Component to map the collection properties to the value object.
public class Document : Entity // don't worry about Entity; it's a base type I created that contains the Id property
{
public virtual string Name { get; set; }
public virtual IList<DocumentOptions> Options { get; protected set; }
public Document()
{
Options = new List<DocumentOptions>();
}
}
public class DocumentOptions
{
public virtual int Type { get; set; }
public virtual string Value { get; set; }
}
And the mapping:
public DocumentMap()
{
Table("documents");
Id(c => c.Id)
.Column("dId")
.GeneratedBy.HiLo("10");
Map(c => c.Name)
.Column("dName");
HasMany(c => c.Options)
.Component(c =>
{
c.Map(c2 => c2.Value).Column("oValue");
c.Map(c2 => c2.Type).Column("oType");
})
.Table("document_options")
.KeyColumn("dId")
.Cascade.AllDeleteOrphan();
}
If I understand correctly I had to map options as a list of components:
HasMany(x => x.DocumentOptions)
.Table("document_options")
.KeyColumn("dID")
.Component(c => {
c.Map(x => x.Option, "oID");
c.Map(x => x.Value, "oValue");
})
.Fetch.Subselect(); //This type of join isn't strictly needed, is used for SQL optimization
classes FYI:
public class Options {
public virtual int Option { get; set; }
public virtual int Value { get; set; }
}
public class Document {
public virtual int ID { get; set; }
public virtual String Name { get; set; }
public virtual IList<DocumentOption> DocumentOptions { get; set; }
}

Categories

Resources