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.
Related
I have a treetable structure and this data comes to me from the frontend.
In this treetable structure, there is IssueActivity and IssueActivityDetail for details of this issue.
Now my question is, more than one IssueActivityDetail field can be added to this IssueActivity field. How can I do this on the c# ef core side?
I tried to do it with the logic of ParentId. My Entity structures are as follows. I did not add the parentId in FluenApi because I did not fully understand it.
My IssueActivity table.
public partial class IssueActivitiy
{
public int Id { get; set; }
public int IssueId { get; set; }
public byte Type { get; set; }
public short SubActivityNo { get; set; }
public string SubActivityTitle { get; set; }
public virtual Issue Issue { get; set; }
public virtual List<IssueActivitiyDetail> IssueActivitiyDetails { get; set; }
}
My IssueActivityDetail table.
public partial class IssueActivitiyDetail
{
public int Id { get; set; }
public int IssueActivityId { get; set; }
public short LineNo { get; set; }
public string Definition { get; set; }
public byte RoleId { get; set; }
public byte Medium { get; set; }
public string Explanation { get; set; }
public int? ParentId { get; set; }
public virtual IssueActivitiy IssueActivity { get; set; }
}
FluentApi Configuration.
public void Configure(EntityTypeBuilder<IssueActivitiy> modelBuilder)
{
modelBuilder.ToTable("IssueActivitiy");
modelBuilder.HasKey(a => a.Id);
modelBuilder.Property(e => e.SubActivityNo).HasComment("Sıra No");
modelBuilder.Property(e => e.SubActivityTitle).HasMaxLength(256).IsUnicode(false);
modelBuilder.Property(e => e.Type).HasDefaultValueSql("((1))").HasComment("1) Temel Aktivite\r\n2) Alternatif Aktivite\r\n3) İşlem İptal Aktivite");
modelBuilder.HasOne(d => d.Issue).WithMany(p => p.IssueActivitiys).HasForeignKey(d => d.IssueId).OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_Issue_IssueActivitiy_Id");
}
public void Configure(EntityTypeBuilder<IssueActivitiyDetail> modelBuilder)
{
modelBuilder.ToTable("IssueActivitiyDetail");
modelBuilder.Property(e => e.Definition).IsRequired().HasMaxLength(2048).IsUnicode(false).HasComment("Açıklama");
modelBuilder.Property(e => e.Explanation).HasMaxLength(2048).IsUnicode(false).HasComment("Açıklama");
modelBuilder.Property(e => e.IssueActivityId).HasComment("Konu Id");
modelBuilder.Property(e => e.LineNo).HasComment("Sıra No");
modelBuilder.Property(e => e.Medium).HasComment("Ortam (Excel, Mail vb.)");
modelBuilder.Property(e => e.RoleId).HasComment("Rol");
modelBuilder.Property(e => e.ParentId);
modelBuilder.HasOne(d => d.IssueActivity).WithMany(p => p.IssueActivitiyDetails).HasForeignKey(d => d.IssueActivityId).OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("FK_IssueActivitiy_IssueActivitiyDetail_");
}
Web Api is also the place where I try to receive and process the data, but I played a lot and couldn't do it correctly.
var vIssueActivity = issueInfo.IssueActivitiyInfos
.Select(a => new IssueActivitiy
{
Type = a.Type,
SubActivityNo = a.SubActivityNo,
SubActivityTitle = a.SubActivityTitle,
IssueActivitiyDetails = a.IssueActivitiyDetailInfos
.Select(x => new IssueActivitiyDetail
{
LineNo = x.LineNo,
Definition = x.Definition,
RoleId = vUser.RoleId,
Medium = x.Medium,
Explanation = x.Explanation,
IssueActivityDetail = new List<IssueActivitiyDetail> { }
}).ToList()
});
You don't need to keep ParentId property in IssueActivityDetail.
public partial class IssueActivitiy
{
...
public virtual List<IssueActivitiyDetail> IssueActivitiyDetails { get; set; }
}
public partial class IssueActivitiyDetail
{
...
public virtual IssueActivitiy IssueActivity { get; set; }
}
Your configuration looks not wrong.
Maybe you can use Include when getting the entity from db context.
var issueActivity = context.IssueActivities.Include(x => x.IssueActivityDetails).FirstOrDefault();
You can accomplish this by retrieving all the entries from the database. Then select the Root node and then let EF Core mapping do the rest.
public class TreeNode
{
public bool IsRoot { get; set; }
public int? ParentNodeId {get; set;}
public virtual List<TreeNode> ChildNodes {get; set;}
}
public class TreeNodeRepository
{
public async Task<TreeNode> GetTreeStructure()
{
var allNodes = await _context.TreeNodes.ToListAsync();
return allNodes.FirstOrDefault(t => t.IsRoot);
}
}
You could argue that ParentId == null would also imply that it's a parent node. this just makes the example given more tuitive imo.
You should consider performance, how many nodes will become an issue, is it exposed through a web-api and would iterating over the nodes be more efficient. So you wouldn't have to load the entire Tree into memory each time but let clients handle that instead.
I have an Entity Framework Core 2.2 model with a master and child tables. All tables have a one to one relationship. Both the Master and Child tables have fields that link to the same lookup table. The link for the master table works as expected with MainTable.NameLookup showing all the fields and data. The link to the child table results in a null result even with Include/ThenInclude. So ChildTable.NameLookup exists but is null. I hope I'm presenting this clearly. Here is the pseudo code of what I'm doing.
So is what I'm try to do available within the framework and if so what am I missing?
class MyContext : DbContext
{
public virtual DbSet<MainTable> MainTable { get; set; }
public virtual DbSet<ChildTable> ChildTable { get; set; }
public virtual DbSet<NameLookup> NameLookup{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MainTable>()
.HasOne(w => w.NameLookup)
.WithOne(p => p.MainTable)
.HasPrincipalKey<MainTable>(d => d.NameCode1)
.HasForeignKey<NameLookup>(p => p.NameCode)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ChildTable>()
.HasOne(w => w.NameLookup)
.WithOne(p => p.ChildTable)
.HasPrincipalKey<ChildTable>(d => d.NameCode2)
.HasForeignKey<NameLookup>(p => p.NameCode)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ChildTable>()
.HasOne(d => d.FileNumberNavigation)
.WithOne(p => p.ChildTable)
.HasForeignKey<ChildTable>(d => d.FileNumber);
}
}
public class MainTable
{
public string FileNumber { get; set; }
public string NameCode1 { get; set; }
public virtual ChildTable ChildTable { get; set; }
public virtual NameLookup NameLookup { get; set; }
}
public class ChildTable
{
public string FileNumber { get; set; }
public string NameCode2 { get; set; }
public virtual MainTable FileNumberNavigation { get; set; }
public virtual NameLookup NameLookup { get; set; }
}
public class NameLookup
{
public string NameCode { get; set; }
public string MoreInfo { get; set; }
public virtual ChildTable ChildTable { get; set; }
public virtual MainTable MainTable { get; set; }
}
This is for a winforms application. Within the application I retrieve the MainTable dbSet with the following snippet:
private readonly DbSet<MainTable > dbSetEntity;
public MainTableRepository(DbContext dbContext) : base(dbContext)
{
base.dbContext = dbContext;
dbSetEntity = dbContext.Set<MainTable >();
}
public MainTable GetMainTable(string FileNumber )
{
return dbSetEntity
.Include(t => t.ChildTable)
.ThenInclude(d => d.NameLookup)
.SingleOrDefault(a => a.FileNumber == FileNumber);
}
public string GetMoreInfo (string FileNumber)
{
var q = from e in dbSetEntity
where e.FileNumber == FileNumber
select e.ChildTable.NameLookup.MoreInfo ;
return q.FirstOrDefault();
}
Calling GetMainTable returns the MainTable fields and data and it also contains the MainTable.ChildTable fields and data. However MainTable.ChildTable.NameLookup is null.
If I call GetMoreInfo it does return the field data from the NameLookup.MoreInfo field.
I have a problem with many to many relationship in EF core.
I have the following model classes:
public class Meal
{
public int Id { get; set; }
[Required]
public int Insulin { get; set; }
public MealType Type { get; set; }
public ICollection<MealFood> MealFoods { get; set; }
public Meal()
{
MealFoods = new Collection<MealFood>();
}
}
public class Food
{
public int Id { get; set; }
[StringLength(255)]
public string Name { get; set; }
[Required]
public int Carbohydrates { get; set; }
public ICollection<MealFood> MealFoods { get; set; }
public Food()
{
MealFoods = new Collection<MealFood>();
}
}
public class MealFood
{
public int MealId { get; set; }
public Meal Meal { get; set; }
public int FoodId { get; set; }
public Food Food { get; set; }
}
I have the following API resource class:
public class MealResource
{
public int Id { get; set; }
public int Insulin { get; set; }
public MealType Type { get; set; }
public ICollection<FoodResource> Foods { get; set; }
public MealResource()
{
Foods = new Collection<FoodResource>();
}
}
I have done the mapping in my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MealFood>().HasKey(mf => new { mf.MealId, mf.FoodId });
modelBuilder.Entity<MealFood>().HasOne(mf => mf.Meal).WithMany(m => m.MealFoods).HasForeignKey(mf => mf.MealId);
modelBuilder.Entity<MealFood>().HasOne(mf => mf.Food).WithMany(f => f.MealFoods).HasForeignKey(mf => mf.FoodId);
}
I've got a problem with this call:
var meals = await context.Meals.Include(m => m.MealFoods).ToListAsync();
This returns almost everything I need, except the navigation properties from MealFoods
The reason why I want those properties, because I want to do the following mapping:
CreateMap<Meal, MealResource>().ForMember(mr => mr.Foods, opt => opt.MapFrom(x => x.MealFoods.Select(y => y.Food).ToList()));
I have already found this:
Automapper many to many mapping
but (maybe I don't get something) this doesn't work because the property called Food in MealFood is null.
I hope I didn't explain too complex.
When you include navigation property, EF Core automatically fills the inverse navigation property, e.g. including Meal.MealFoods will automatically fill MealFood.Meal, including Food.MealFoods will automatically populate MealFood.Food etc. In order to populate other navigation properties you need to use additional ThenInclude. E.g.
var meals = await context.Meals
.Include(m => m.MealFoods)
.ThenInclude(mf => mf.Food) // <--
.ToListAsync();
or
var foods = await context.Foods
.Include(f => f.MealFoods)
.ThenInclude(mf => mf.Meal) // <--
.ToListAsync();
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!
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();
}
}