Saving changes to EF with children - c#

I have build reporting system where user can select data and display as report. Report is saved in three tables (split entity). But when I try to edit report and save again I get following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
My Entity:
public class Report
{
[Key]
public int ReportId { get; set; }
public string Title { get; set; }
public int? DateRange { get; set; }
public int Layout { get; set; }
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
public int OwnerId { get; set; }
public DateTime DateCreated { get; set; }
public bool Active { get; set; }
public virtual List<ReportCharts> ReportCharts { get; set; }
public virtual List<ReportElements> ReportElements { get; set; }
}
My EF repository:
//Save Report to Database
public void Save(Report report)
{
assignSettingsToEntity(report);
assignElementsToEntity(report);
assignChartsToEntity(report);
int found = Reports
.Select(r => r.ReportId)
.Where(id => id == report.ReportId)
.SingleOrDefault();
if (found == 0)
{
context.Reports.Add(report);
}
else
{
context.Entry(report).State = EntityState.Modified; // Here I get error
}
context.SaveChanges();
}
My DBContext
class EFDbContext : DbContext
{
//Get Lines data from Lines table
public DbSet<Line> Lines { get; set; }
//Get Shifts data from Shifts table
public DbSet<Shift> Shifts { get; set; }
//Get list of Charts from Charts table
public DbSet<Graph> Graphs { get; set; }
//Get Reports data from Reports table
public DbSet<Report> Reports { get; set; }
// Report entity mapping
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Report>().Property(t => t.ReportId).HasColumnName("ReportId");
modelBuilder.Entity<Report>().Property(t => t.Title).HasColumnName("Title");
modelBuilder.Entity<Report>().Property(t => t.DateRange).HasColumnName("DateRange");
modelBuilder.Entity<Report>().Property(t => t.Layout).HasColumnName("Layout");
modelBuilder.Entity<Report>().Property(t => t.DateFrom).HasColumnName("DateFrom");
modelBuilder.Entity<Report>().Property(t => t.DateTo).HasColumnName("DateTo");
modelBuilder.Entity<Report>().Property(t => t.OwnerId).HasColumnName("OwnerId");
modelBuilder.Entity<Report>().Property(t => t.DateCreated).HasColumnName("DateCreated");
modelBuilder.Entity<Report>().Property(t => t.Active).HasColumnName("Active");
modelBuilder.Entity<Report>().HasMany(t => t.ReportElements).WithRequired().HasForeignKey(c => c.ReportId).WillCascadeOnDelete(true);
modelBuilder.Entity<Report>().HasMany(t => t.ReportCharts).WithRequired().HasForeignKey(p => p.ReportId).WillCascadeOnDelete(true);
modelBuilder.Entity<ReportElements>().Property(c => c.ElementName).HasColumnName("ElementName");
modelBuilder.Entity<ReportElements>().HasKey(c => new { c.ReportId, c.ElementName, c.Active });
modelBuilder.Entity<ReportCharts>().Property(p => p.ChartId).HasColumnName("ChartId");
modelBuilder.Entity<ReportCharts>().HasKey(c => new { c.ReportId, c.ChartId, c.Active });
}
}
Thanks for any help.

Entity Framework attaches every entity to one context and all changes can only be performed on the context it is attached to it, you are probably using multiple context. You should detach and attach entity obtained from different context.
Otherwise, the best practice is not to use multiple instances of context, only keep one context throughout your changes.

Sounds like the object was attached to another context and not detached.
//Save Report to Database
public void Save(Report report)
{
using(EFDbContext context=new EFDbContext ())
{
assignSettingsToEntity(report);
assignElementsToEntity(report);
assignChartsToEntity(report);
int found = context.Reports
.Select(r => r.ReportId)
.Where(id => id == report.ReportId)
.SingleOrDefault();
// Insert Flow
if (found == 0)
{
context.Reports.Add(report);
}
// Update flow
else
{
context.Reports.Attach(report);
context.Entry(report).State = EntityState.Modified;
}
context.SaveChanges();
}
}

Related

How to use Linq in C# to select a specific string from multiple nested columns?

I have a small problem here. I have the following tables with their relations :
Building
Batteries
Columns
Elevators
A building can have many batteries, batteries can have many columns, columns can have many elevators.
A battery has one building, a column has one battery, an elevator has one column.
If I were to do this var myintervention = _context.buildings.Where(b => b.batteries.Any(ba => ba.status == "Intervention")).ToList(); it would work perfectly fine in my query to return a list of the buildings that have batteries with the intervention status (status is a column).
The problem is that I can't do something like _context.buildings.Where(c => c.columns.Any...) because the building model doesn't have access to the column class but the battery does... Same goes for elevators, buildings don't have access to elevators, columns do.
Here's how I defined my modelBuilder :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Battery>()
.HasOne(p => p.buildings)
.WithMany(b => b.batteries)
.HasForeignKey(p => p.building_id);
modelBuilder.Entity<Column>()
.HasOne(p => p.batteries)
.WithMany(b => b.columns)
.HasForeignKey(p => p.battery_id);
modelBuilder.Entity<Elevator>()
.HasOne(p => p.columns)
.WithMany(b => b.elevators)
.HasForeignKey(p => p.column_id);
}
Here's how my relations look in my models :
building.cs
public List<Battery> batteries { get; set; }
battery.cs
public long building_id { get; set; }
public Building buildings { get; set; }
public List<Column> columns { get; set; }
column.cs
public long battery_id { get; set; }
public Battery batteries { get; set; }
public List<Elevator> elevators { get; set; }
elevator.cs
public long column_id { get; set; }
public Column columns { get; set; }
tl;dr; I want to do a linq query to list all the buildings that have either a battery, column or elevator whose's status column is at "Intervention".
UPDATE: apparently this somehow works, but not efficiently :
var myintervention = _context.buildings.Where(a => a.batteries.SelectMany(b => b.columns.SelectMany(c => c.elevators)).Any(c => c.status == "Intervention")).ToList();
It seems as though it's not accurate? Sometimes there are batteries in intervention and the building associated with those batteries doesn't appear in the building list. Same goes for elevators or columns.. I'm a bit lost!
UPDATE 2 : here's my request :
[HttpGet("intervention")]
public List<Building> Getintervention(string status)
{
var myintervention = _context.buildings.Where(c => c.batteries.SelectMany(z => z.columns).Any(z => z.status == "Intervention")).ToList();
return myintervention;
}
First up, please fix the model so collections have plural names and objects have single, otherwise your code will become very confused:
building.cs
public List<Battery> Batteries { get; set; }
battery.cs
public long BuildingId { get; set; }
public Building Building { get; set; }
public List<Column> Columns { get; set; }
column.cs
public long BatteryId { get; set; }
public Battery Battery { get; set; }
public List<Elevator> Elevators { get; set; }
elevator.cs
public long ColumnId { get; set; }
public Column Columns { get; set; }
Now let's add some more properties to the model so it can tell us about interventions:
building.cs
public List<Battery> Batteries { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention" || Batteries.Any(b => b.IsInIntervention);
battery.cs
public long BuildingId { get; set; }
public Building Building { get; set; }
public List<Column> Columns { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention" || Columns.Any(c => c.IsInIntervention);
column.cs
public long BatteryId { get; set; }
public Battery Battery { get; set; }
public List<Elevator> Elevators { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention" || Elevators.Any(e => e.IsInIntervention);
elevator.cs
public long ColumnId { get; set; }
public Column Column { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention";
Now you can just ask a building if it IsInIntervention and it will say yes if it is or if anything it owns is
Note: if the model hasn't been loaded with entities then you might need to employ a trick like this: EF Core linq and conditional include and theninclude problem to conditionally load them
var vara = _context.maintable.Where(a => a.tablerelatedtomain.SelectMany(b => b.tablerelatedtoprevious).SelectMany(c => c.tablerelatedtoprevious).Any(c => c.status == "mystring")).ToList();
var varb = _context.maintable.Where(a => a.tablerelatedtomain.SelectMany(b => b.tablerelatedtoprevious).Any(b => b.status == "mystring")).ToList();
var varc = _context.maintable.Where(a => a.tablerelatedtomain.Any(a => a.status == "mystring")).ToList();
var result = vara.Union(varb).Union(varc).OrderBy(z => z.id).ToList();
return result;

EF Core FindAsync returns only root object

I have a many to many relation and I am using ASP.Net Core2.2 MVC with EF.
From what I read I understand that at the current time I have to create a CLR class for join table.
My code:
Models:
public class Post
{
public Post()
{
Categories= new List<PostCategory>();
}
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
public List<PostCategory> Categories { get; set; }
}
public class Category
{
public Category()
{
Posts= new List<PostCategory>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public ICollection<PostCategory> Posts { get; set; }
}
public class PostCategory
{
public int PostId { get; set; }
public Post Post { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
DBContext:
public class MyDBContext:DbContext
{
public MyDBContext()
{
}
public MyDBContext(DbContextOptions<MyDBContext> options) : base(options)
{
}
public DbSet<Post> Post { get; set; }
public DbSet<Category> Category { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostCategory>()
.HasKey(x => new { x.PostId, x.CategoryId });
modelBuilder.Entity<PostCategory>()
.HasOne(x => x.Post)
.WithMany(m => m.Categories)
.HasForeignKey(x => x.PostId);
modelBuilder.Entity<PostCategory>()
.HasOne(x => x.Category)
.WithMany(e => e.Posts)
.HasForeignKey(x => x.CategoryId);
}
}
Create Post Code:
var categories= new List<PostCategory>();
foreach (var category in model.SelectedCategories)
{
categories.Add(new PostCategory()
{
CategoryId= category
});
}
model.Post.Categories.AddRange(categories);
_context.Post.Add(model.Post);
await _context.SaveChangesAsync();
When I create a post I can see in database that I have Post data, and in PostCategory table I can see PostId CategoryId as they should be.
My issue is when I try to get a post data using:
var post = await _context.Post.FindAsync(id);
post.Categories count is always 0, what am I missing?
You need to write your query as follows to eager load the related Categories with Post:
var post = await _context.Post.Include(p => p.Categories).FirstOrDefaultAsync(p => p.Id == id);
Here is the more details about Loading Related Data in EF core
The accepted answer will work but may not be as performant - when using FindAsync if the entity is already being tracked you'll get that copy rather than going to the database
In order to have that and load related data you need to do it manually
for example:
context.Entry(post)
.Reference(p => p.Categories)
.Load();
references:
https://learn.microsoft.com/en-us/ef/core/querying/related-data/explicit#explicit-loading
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.findasync?view=efcore-7.0#overloads

Mapping exception in Entity Framework

I have a big database for a multi lingual application that gets it's texts from the server , inserts into the database, then based on user preferred language, finds appropriate text.
Let me first describe the database then I'll say my problem:
Illustration: for example I have a table Product, which has a foreign key (Description column) to the Translation table which in turn connects to TranslationEntry table that has all the translations of products's description in all languages.
The languages are in a separate table called Language which has a foreign key to TranslationEntry table.
public class Product : BaseModel
{
public int description { get; set; }
public virtual Translation Description { get; set; }
}
public class Translation : BaseModel
{
public Translation()
{
Products = new List<Product>();
}
public virtual ICollection<Product> Products { get; set; }
public virtual ICollection<MainCategory> MainCategories { get; set; }
public virtual ICollection<Caption> Captions { get; set; }
}
public class TranslationEntry : BaseModel
{
public string text { get; set; }
public int language { get; set; }
public virtual Language Language { get; set; }
public int translation { get; set; }
public virtual Translation Translation { get; set; }
}
public class Language : BaseModel
{
public Language()
{
TranslationEntries = new List<TranslationEntry>();
}
public string title { get; set; }
public string language_code { get; set; }
public virtual ICollection<TranslationEntry> TranslationEntries { get; set; }
}
public class BaseModel
{
public int id { get; set; }
public int MembershipId { get; set; }
public SyncStatus SyncState { get; set; }
....
}
Translation Entry Mapping:
HasRequired(translationEntry => translationEntry.Translation)
.WithMany(translation => translation.TranslationEntries)
.HasForeignKey(translationEntry =>
new {translationEntry.translation, translationEntry.MembershipId, translationEntry.SyncState})
.WillCascadeOnDelete(false);
HasRequired(translationEntry => translationEntry.Language)
.WithMany(language => language.TranslationEntries)
.HasForeignKey(translationEntry =>
new {translationEntry.language, translationEntry.MembershipId, translationEntry.SyncState})
.WillCascadeOnDelete(false);
Property(t => t.translation)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_translatinlanguageOd", 1) { IsUnique = true }));
Property(t => t.language)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_translatinlanguageOd", 2) { IsUnique = true }));
Product Mapping:
HasRequired(product => product.Description)
.WithMany(translation => translation.Products)
.HasForeignKey(product => new { product.description, product.MembershipId, product.SyncState })
.WillCascadeOnDelete(false);
Sample set of data here:
Now the problem: I want to get description of a product, I use the following command
var o = databaseContext.Products.ToList().First(p=>p.id==1)?.Description.TranslationEntries.First(te=>te.language==1);
but I get an error:
A 'Mapping' exception occurred while processing the query. See the inner exception.
Inner exception:
More than one property map found for property 'translation' when using case-insensitive search.
Note that there are many entities which have the same relationships for translation as Product table which I showed.
UPDATE:
my temporary Solution is this:
var Language = context.Languages.Include(l => l.TranslationEntries)
.Where(l => l.id == languageId)
.ToList()
.FirstOrDefault();
TranslationEntries = Language?.TranslationEntries;
var translatedText = (from t in TranslationEntries where t.translation == 2 select t.text).FirstOrDefault();
Finally fixed this stupid problem!
As the error message says, the problem is by case-insensitive search there are 2 property named translation in TranslationEntry Class, I renamed one of them and now everything works without any problem!

Keep multiple objects with the same key in DbContext

I am trying to create ASP.NET MVC web application using Entity Framework Code First.
I have two simple models
public class Assignment
{
public int Id { get; set; }
[ForeignKey("Initiator")]
public int InititatorId { get; set; }
public virtual User Initiator { get; set; }
public virtual List<User> Debtors { get; set; }
public virtual List<User> Responsibles { get; set; }
public Assignment()
{
Debtors = new List<User>();
Responsibles = new List<User>();
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
and my DbContext looks like
public class AssignmentContext : DbContext
{
public DbSet<Assignment> Assignments { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Assignment>()
.HasRequired(r => r.Initiator)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Assignment>()
.HasMany(m => m.Responsibles)
.WithMany()
.Map(m =>
{
m.MapLeftKey("AssignmentId");
m.MapRightKey("UserId(Responsible)");
m.ToTable("AssignmentResponsibles");
});
modelBuilder.Entity<Assignment>()
.HasMany(m => m.Debtors)
.WithMany()
.Map(m =>
{
m.MapLeftKey("AssignmentId");
m.MapRightKey("UserId(Debtor)");
m.ToTable("AssignmentDebtors");
});
}
Is there a way to keep the same User instance in the Lists Debtors and Responsibles?
Because I'm getting the error when I'm trying to save changes.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I need to do this because it is required by the application logic.
Thanks in advance.
Update:
Here on HttpPost the error appears (The same error in Console app sample):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Text,CreationDate,InititatorId,Debtors,Responsibles")] Assignment assignment)
{
if (ModelState.IsValid)
{
assignment.Debtors.RemoveAll(r => r.Name == "Roman");
db.Entry(assignment).State = EntityState.Modified; // HERE!
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.InititatorId = new SelectList(db.Users, "Id", "Name", assignment.InititatorId);
return View(assignment);
}
Debtors and Responsbiles elements from the view are passed by hidden input.
Why don't you just get the data first instead of directly attach it to the dbcontext, the same assignment with the same id might have been attached locally within the same context.
instead of these
assignment.Debtors.RemoveAll(r => r.Name == "Roman");
db.Entry(assignment).State = EntityState.Modified; // HERE!
db.SaveChanges();
try these
var assignmentDb = db.Assignments.FirstOrDefault(asg => asg.Id == assignment.Id);
assignmentDb.Debtors.RemoveAll(r => r.Name == "Roman");
db.SaveChanges();

duplicate rows in many to many relation EF 5.0

I have this two tables
[Table("Designs")]
public class Design
{
[Key]
public Int64 ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
and
[Table("Tags")]
public class Tag
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Design> Designs { get; set; }
}
this is my context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
this.Configuration.ValidateOnSaveEnabled = false;
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Design>().HasMany(m => m.Tags).WithMany(m => m.Designs).Map(m => { m.MapLeftKey("DesignID"); m.MapRightKey("TagID"); m.ToTable("DesignsToTags"); });
}
there are some tags
and this is the cross table
I try to update tags for design
public Design EditDesign(Design design, int[] newTags)
{
var entry = ctx.Entry(design);
entry.Collection(m => m.Tags).Load();
design.Tags = newTags.Select(m => ctx.Tags.Find(m)).ToList();
entry.State = System.Data.EntityState.Modified;
ctx.SaveChanges();
return design;
}
and this code adds duplicate values in Tags table (ID == 3 and 16 are same), but in DesignsToTags table everything is OK
How can I prevent adding duplicate values??
Finally found the problem
I need to include Tags when get the design
var design = ctx.Designs.Include(m => m.Tags).Single(m => m.ID == model.Id);
instead of
var design = ctx.Designs.Find(model.Id);
and everything works perfectly.

Categories