I have two entities as shown in the screenshot:
each DIMPeriodDates can connect to many DIMPeriodComparatives and
each DIMPeriodComparatives can connect to many DIMPeriodDates
In other words, DIMPeriod can connect to themselves with order number.
This is the DIMPeriod class :
public class DIMPeriodDate
{
public enum EnumDIMPeriodPresentStatus
{
Refresh,
Operation
}
public enum EnumDIMPeriodType
{
Decisive,
Predicted
}
public enum EnumDIMPeriodAuditStatus
{
Audited,
NotAudited
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public string Title { get; set; }
public string? Desc { get; set; }
public bool IsClosed { get; set; } = false;
[Column(TypeName = "date")]
public DateTime DateStart { get; set; }
[Column(TypeName = "date")]
public DateTime DateEnd { get; set; }
public List<DIMPeriodComparative> PeriodComparativeList { get; set; } = new();
public List<DIMPeriodComparative> PeriodBaseComparativeList { get; set; } = new();
}
And this is the PeriodComparative class :
public class DIMPeriodComparative
{
public int PeriodComparativeID { get; set; }
public int PeriodBaseID { get; set; }
public int Order { get; set; } = 1;
public DIMPeriodDate PeriodComparative { get; set; }
public DIMPeriodDate PeriodBase { get; set; }
}
Here is my Fluent API config :
modelBuilder.Entity<DIMPeriodComparative>()
.HasKey(q => new { q.PeriodComparativeID, q.PeriodBaseID });
modelBuilder.Entity<DIMPeriodComparative>()
.HasOne(q => q.PeriodComparative)
.WithMany(q => q.PeriodComparativeList)
.HasForeignKey(q=>q.PeriodComparativeID)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<DIMPeriodComparative>()
.HasOne(q => q.PeriodBase)
.WithMany(q => q.PeriodBaseComparativeList)
.HasForeignKey(q=>q.PeriodBaseID)
.OnDelete(DeleteBehavior.NoAction);
Now when I insert a new DIMPeriodComparatives entity to specific DIMPeriodDates like this :
After calling SaveChanges, the value automatically has changed :
PeriodBase and PeriodComparative have different Value with different id 11 and 13 ...
i change fulent api to this :
modelBuilder.Entity<DIMPeriodDate>()
.HasMany(q => q.PeriodComparativeBase)
.WithMany(q => q.PeriodComparative)
.UsingEntity<DIMPeriodComparative>(right =>
right.HasOne(q => q.PeriodComparative)
.WithMany()
.HasForeignKey(q => q.PeriodComparativeID)
, left =>
left.HasOne(q=>q.PeriodBase)
.WithMany()
.HasForeignKey(q=>q.PeriodBaseID)
, joinent =>
joinent.HasKey(q => new {q.PeriodComparativeID,q.PeriodBaseID})
);
and issue is fixed ...
Related
I have the following classes:
[Table("Artikel_Meldung", Schema = "BDB")]
public class Artikel_Meldung
{
[Key]
public int Artikel_MeldungId { get; set; }
public int Meldung_Id { get; set; }
public Meldung Meldung { get; set; }
public Artikel Artikel { get; set; }
public long ArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string Ausfkz { get; set; }
}
[Table("Meldung", Schema = "BDB")]
public class Meldung
{
[Key]
public int Meldung_Id { get; set; }
public string Erfasser { get; set; }
public Artikel ErsatzArtikel { get; set; }
[NotMapped]
public Artikel Parent { get; set; }
public long ErsatzArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string ErsatzAusfKz { get; set; }
public virtual ICollection<Artikel_Meldung> Artikel_Meldungen { get; set; }
public string Kommentar { get; set; }
public DateTime StartZeitpunkt { get; set; }
public DateTime EndeZeitpunkt { get; set; }
}
And retrieving the results with the following command:
query = srv_apps_db.Artikel.Where(x => x.Artikelbezeichnung.Contains(search_string) || x.ModbezKd.Contains(search_string))
.Include(art => art.Supplier)
.Include(art => art.Warengruppe)
.Include(a => a.Inventory)
.Include(art => art.Specials.Where(spe => spe.GueltigAb <= DateTime.Now.Date && spe.GueltigBis >= DateTime.Now.Date && (spe.FilialNr == Filiale || spe.FilialNr == 0)))
.Include(art => art.Orders.Where(ord => ord.PosNr.Substring(0, 1) != "5"))
.Include(art => art.Artikel_Bilder).ThenInclude(img => img.Bild).ThenInclude(s => s.ImgTyp)
.Include(art => art.Artikel_Meldungen).ThenInclude(x=>x.Meldung);
artikel = await query.AsSplitQuery()
.AsNoTracking()
.ToListAsync();
Everything works so far, but as I inspected the retrievd objects I noticed that Meldung.Meldung_Id is always 1 off the actual value in the Database.
Json-Result of my API-Call:
{
"artikel_MeldungId": 608,
"meldung_Id": 609,
"meldung": {
"meldung_Id": 609,
"erfasser": "NZI",
"ersatzArtikel": {
"artNr": 10080405,
"ausfKz": "02",
....
}
}
The values are correct but the Id doesn't match is this a bug or an error in my code or expected behaviour on EF?
Thanks in advance
regards Tekkion
I have the following classes
public class Travel
{
public int Id { get; set; }
public string Location { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Activity> Activities { get; set; }
public List<ActivityTravel> ActivitityTravels { get; set; } = new List<ActivityTravel>();
}
public class Activity
{
public int Id { get; set; }
public string Name { get; set; }
public List<ActivityTravel> ActivitityTravels { get; set; } = new List<ActivityTravel>();
}
public class ActivityTravel
{
public int TravelId { get; set; }
public Travel Travel { get; set; }
public int ActivityId { get; set; }
public Activity Activity { get; set; }
}
My ApplicationDbContext looks like this
modelBuilder.Entity<ActivityTravel>()
.HasKey(at => new { at.ActivityId, at.TravelId });
modelBuilder.Entity<ActivityTravel>()
.HasOne(at => at.Activity)
.WithMany(a => a.ActivitityTravels)
.HasForeignKey(at => at.ActivityId);
modelBuilder.Entity<ActivityTravel>()
.HasOne(at => at.Travel)
.WithMany(t => t.ActivitityTravels)
.HasForeignKey(at => at.TravelId);
//outside of OnModelCreating
public DbSet<Travel> Travels { get; set; }
public DbSet<Activity> Activities { get; set; }
public DbSet<ActivityTravel> ActivityTravels { get; set; }
A user can add a new Travel with a number of already existing Activites. But everytime a new Travel is added to the context, nothing is added to ActivityTravel.
Here is the code used to add a new Travel
public async Task<ActionResult> AddTravel([FromBody] Travel travel)
{
travel.Activities.ForEach(a => travel.ActivitityTravels.Add(new ActivityTravel() { Activity = a, Travel = travel }));
_context.Travels.Add(travel);
//dont add activity again
travel.Activities.ForEach(a => _context.Entry(a).State = EntityState.Detached);
_context.SaveChanges();
return Ok();
}
I've followed the example in this question Saving many-to-many relationship in Entity Framework Core
You should not detach your existing Activities you need to attach them first before handling other stuff, so they will not be added as duplicates. Something like this:
public async Task<ActionResult> AddTravel([FromBody] Travel travel)
{
travel.Activities.ForEach(a => travel.ActivitityTravels.Add(new ActivityTravel() { Activity = a, Travel = travel }));
_context.AttachRange(travel.Activities);
_context.Travels.Add(travel);
_context.SaveChanges();
return Ok();
}
I am following Microsoft's many-to-many ef core example at
https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many
But get a self referencing loop error.
Here are my entities:
public class Card
{
public Guid Id { get; set; }
public string CardNumber { get; set; }
public CardType CardType { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int PassCode { get; set; }
public List<CardSet> CardSets { get; set; }
public Card()
{
CardSets = new List<CardSet>();
}
}
public class Set
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<CardSet> CardSets { get; set; }
public Set()
{
CardSets = new List<CardSet>();
}
}
// join entity
public class CardSet
{
public Guid SetId { get; set; }
public Set Set { get; set; }
public Guid CardId { get; set; }
public Card Card { get; set; }
}
Here is my OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CardSet>().HasKey(cs => new {cs.CardId, cs.SetId});
modelBuilder.Entity<CardSet>()
.HasOne(cs => cs.Card)
.WithMany(c => c.CardSets)
.HasForeignKey(cs => cs.CardId);
modelBuilder.Entity<CardSet>()
.HasOne(cs => cs.Set)
.WithMany(s => s.CardSets)
.HasForeignKey(cs => cs.SetId);
}
Here is the call to get the Set with its Cards:
public Set GetSetWithCards(Guid setId)
{
return context
.Sets
.Include(s => s.CardSets)
.ThenInclude(cs => cs.Card)
.FirstOrDefault(s => s.Id == setId);
}
The error:
Newtonsoft.Json.JsonSerializationException: Self referencing loop
detected for property 'set' with type
'Tools.Entities.Set'. Path 'cardSets[0]'.
All of your entity configurations are correct, and, based on the error message, it appears that the issue is happening when you try to serialize the resulting data to JSON.
Check out this answer for details: JSON.NET Error Self referencing loop detected for type
By some reason EF wont load the included list properly so it ends up being null all the time.
Here is the entities i'm using:
[Table("searchprofilepush")]
public class SearchProfilePush
{
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
public SearchProfilePush SearchProfilePush { get; set; }
}
Then when i'm trying to do this:
var searchProfilePush = _dataContext.SearchProfilePush.Include(w => w.SearchProfileMediaTypePush).FirstOrDefault(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId);
My included list is always null.
I guess it's some obvious reason why this doesn't work but i just can't figure it out.
Thanks!
EDIT:
Here is the sql query:
SELECT \"Extent1\".\"id\", \"Extent1\".\"accountid\", \"Extent1\".\"push\", \"Extent1\".\"userpushid\", \"Extent1\".\"searchprofileid\" FROM \"public\".\"searchprofilepush\" AS \"Extent1\" WHERE \"Extent1\".\"accountid\" = #p__linq__0 AND #p__linq__0 IS NOT NULL AND (\"Extent1\".\"searchprofileid\" = #p__linq__1 AND #p__linq__1 IS NOT NULL) LIMIT 1
EDIT 2:
I have now mapped my entities both way and the list is still always null.
Edit 3:
This is how i created my database tables.
The documentation I read for loading related entities has some differences with the sample code and your code. https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
First, when you define your ICollection, there is no keyword virtual:
public virtual ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
Next, in the example close to yours, where they load related items using a query, the first or default is not using a boolean expression. The selective expression is in a where clause:
// Load one blogs and its related posts
var blog1 = context.Blogs
.Where(b => b.Name == "ADO.NET Blog")
.Include(b => b.Posts)
.FirstOrDefault();
So you can try:
var searchProfilePush = _dataContext.SearchProfilePush
.Where(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId)
.Include(w => w.SearchProfileMediaTypePush)
.FirstOrDefault();
Can you make these two changes and try again?
A few things will be an issue here. You have no keys defined or FKs for the relationship:
[Table("searchprofilepush")]
public class SearchProfilePush
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
[Key]
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
[ForeignKey("SearchProfilePushId")]
public SearchProfilePush SearchProfilePush { get; set; }
}
Personally I prefer to explicitly map out the relationships using EntityTypeConfiguration classes, but alternatively they can be set up in the Context's OnModelCreating. As a starting point have a look at http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx for basic EF relationship configuration.
for a SearchProfilePush configuration:
modelBuilder.Entity<SearchProfilePush>()
.HasMany(x => x.SearchProfileMediaTypePush)
.WithRequired(x => x.SearchProfilePush)
.HasForeignKey(x => x.SearchProfilePushId);
I have two API calls. GetExam and SaveExam. GetExam serializes to JSON which means by the time I go to save, the entity is detached. This isnt a problem, I can go retrieve the entity by its primary key and update its properties manually.
However, when I do so the exam questions get its current collection duplicated. For example, if examToSave.ExamQuestions had a few questions deleted, and a new one added all selectedExam.exam_question are duplicated and the new one is added in. Eg. if 3 questions existed, I deleted 1 and added 4 there will now be 7.
Domain models:
public partial class exam
{
public exam()
{
this.exam_question = new HashSet<exam_question>();
}
public int ID { get; set; }
public string ExamName { get; set; }
public string ExamDesc { get; set; }
public Nullable<decimal> TimeToComplete { get; set; }
public bool AllowBackStep { get; set; }
public bool RandomizeAnswerOrder { get; set; }
public int Attempts { get; set; }
public virtual ICollection<exam_question> exam_question { get; set; }
}
public partial class exam_question
{
public exam_question()
{
this.exam_answer = new HashSet<exam_answer>();
}
public int ID { get; set; }
public int ExamID { get; set; }
public string QuestionText { get; set; }
public bool IsFreeForm { get; set; }
public virtual exam exam { get; set; }
public virtual ICollection<exam_answer> exam_answer { get; set; }
}
public partial class exam_answer
{
public int ID { get; set; }
public string AnswerText { get; set; }
public int QuestionID { get; set; }
public bool IsCorrect { get; set; }
public virtual exam_question exam_question { get; set; }
}
Save method:
[Route("SaveExam")]
[HttpPost]
public IHttpActionResult SaveExam(ExamViewModel examToSave)
{
using (var db = new IntranetEntities())
{
// try to locate the desired exam to update
var selectedExam = db.exams.Where(w => w.ID == examToSave.ID).SingleOrDefault();
if (selectedExam == null)
{
return NotFound();
}
// Redacted business logic
// Map the viewmodel to the domain model
Mapper.CreateMap<ExamAnswerViewModel, exam_answer>();
Mapper.CreateMap<ExamQuestionViewModel, exam_question>().ForMember(dest => dest.exam_answer, opt => opt.MapFrom(src => src.QuestionAnswers));
Mapper.CreateMap<ExamViewModel, exam>().ForMember(dest => dest.exam_question, opt => opt.MapFrom(src => src.ExamQuestions));
var viewmodel = Mapper.Map<exam>(examToSave);
// Update exam properties
selectedExam.ExamName = viewmodel.ExamName;
selectedExam.ExamDesc = viewmodel.ExamDesc;
selectedExam.AllowBackStep = viewmodel.AllowBackStep;
selectedExam.Attempts = viewmodel.Attempts;
selectedExam.RandomizeAnswerOrder = viewmodel.RandomizeAnswerOrder;
selectedExam.exam_question = viewmodel.exam_question; // DUPLICATES PROPS
// Save
db.SaveChanges();
return Ok(examToSave);
}
}