Collections duplicated when trying to update a detached entity's related collection - c#

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);
}
}

Related

Entity Framework to List without specific Column

I'm actually using EF and very new to it. I have a EDMX-Model from Database. I have to get a List of Entities, but in this Table is Binary-Column. I want my Object-List from that Table without the Binary Data.
My Entity-Objekt looks like:
public partial class bons
{
public int id { get; set; }
public System.DateTime zeitstempel { get; set; }
public byte[] bonBinaer { get; set; }
public Nullable<int> tisch_nr { get; set; }
public bool abgearbeitet { get; set; }
public int kunde_id { get; set; }
public string hash { get; set; }
public string dateiname { get; set; }
public string tisch_name { get; set; }
public int gang { get; set; }
public decimal brutto { get; set; }
public Nullable<System.DateTime> zeitstempelAbgearbeitet { get; set; }
public Nullable<int> positionenAnzahl { get; set; }
public bool manuell { get; set; }
}
And I#m getting the List like:
internal static List<bons> holeBongListeNichtAbgearbeitetRestaurant(int kunde_id)
{
List<bons> rückgabe = new List<bons>();
using (bonsEntities context = new bonsEntities())
{
rückgabe = context.bons.Where(x => x.kunde_id == kunde_id && x.abgearbeitet == false).OrderBy(x => x.zeitstempel).ToList();
}
return rückgabe;
}
Can someone help how to get the List without the 'byte[] bonBinaer'?
Hi you can use autoMapper with EF extension
like: https://docs.automapper.org/en/stable/Queryable-Extensions.html
or if you prefer you can do by yourself with a Select() method like:
using (bonsEntities context = new bonsEntities())
{
rückgabe = context.bons.Where(x => x.kunde_id == kunde_id && x.abgearbeitet == false).OrderBy(x => x.zeitstempel).Select(xx=> new {
id = xx.id
//and all you props
}).ToList().Select(yy=> new bons{
id=yy.id
//and back with other props
}).ToList();
}

EF Core many to many not saving into join table

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();
}

Multiplicity constraint violated error while implementing chat functionality

I am implementing Chat functionality excatly like this one in asp.net.This is the model explaination
My classes are: User, Conversaiton and Message:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public string UserName { get; set; }
//keys
public ICollection<Conversation> Conversations { get; set; }
}
public class Conversation
{
public Guid ID { get; set; }
public ICollection<Message> Messages { get; set; }
public User RecipientUser { get; set; }
public Guid SenderUserId { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
}
and finally,
public class Message
{
public int ID { get; set; }
public string Text { get; set; }
public bool IsDelivered { get; set; }
public bool IsSeen { get; set; }
public Guid SenderUserId { get; set; }
public Conversation Conversation { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
}
I am using EntityTypeConfiguration using fluent Api which are:
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
HasMany(x => x.Conversations).WithRequired(x => x.RecipientUser).HasForeignKey(x => x.SenderUserId);
}
}
public class ConversationConfig : EntityTypeConfiguration<Conversation>
{
public ConversationConfig()
{
HasKey(x => x.ID);
HasRequired(x => x.RecipientUser).WithMany(x => x.Conversations);
HasMany(x => x.Messages).WithRequired(x => x.Conversation).HasForeignKey(x => x.SenderUserId);
}
}
But I'm getting error as follows:
Multiplicity constraint violated. The role 'Conversation_RecipientUser_Target' of the relationship 'DataAcessLayer.Conversation_RecipientUser' has multiplicity 1 or 0..1.
If anyone could help me. Many thanks!!!
I didn't get the exception when I used your model, but that doesn't matter much, because you've got to make some changes anyway.
First, this configuration in UserConfig ...
HasMany(x => x.Conversations).WithRequired(x => x.RecipientUser)
.HasForeignKey(x => x.SenderUserId);
... doesn't make sense to me. This means that the conversations belong to the recipient, not to the sender, which in itself is somewhat unexpected. But the name of the foreign key is SenderUserId. If you save objects into this model, SenderUserId will get the value of the recipient's ID!
Second, assuming the previous point was an error, you make it harder than necessary by defining ...
public User RecipientUser { get; set; }
public Guid SenderUserId { get; set; }
This means that you can only navigate from Conversation to the sender User by an explicit join over SenderUserId. Conversely, you can only set the recipient by setting RecipientUser, not by simply setting a foreign key value.
Third, you shouldn't have this SenderUserId in Message. It should be ConversationId instead. You can find a Message's sender through the Conversation it belongs to.
All in all, I changed your model into this, reducing it to the bare-bone minimum, and using int for ID, just because it's easier reading:
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Conversation> Conversations { get; set; }
}
public class Conversation
{
public int ID { get; set; }
public int SenderUserID { get; set; }
public User SenderUser { get; set;}
public int RecipientUserID { get; set; }
public User RecipientUser { get; set; }
public ICollection<Message> Messages { get; set; }
}
public class Message
{
public int ID { get; set; }
public string Text { get; set; }
public int ConversationID { get; set; }
public Conversation Conversation { get; set; }
}
And the only configuration:
public class ConversationConfig : EntityTypeConfiguration<Conversation>
{
public ConversationConfig()
{
HasKey(c => c.ID);
HasRequired(c => c.SenderUser).WithMany(u => u.Conversations)
.HasForeignKey(c => c.SenderUserID).WillCascadeOnDelete(true);
HasRequired(c => c.RecipientUser).WithMany()
.HasForeignKey(c => c.RecipientUserID).WillCascadeOnDelete(false);
HasMany(c => c.Messages).WithRequired(x => x.Conversation)
.HasForeignKey(x => x.ConversationID);
}
}
(One of the foreign keys doesn't have cascaded delete because that will cause a SQL-Server error: multiple cascade paths).
A simple test:
using (DbContext db = new DbContext(connectionString))
{
var send = new User { Name = "Sender" };
db.Set<User>().Add(send);
var rec = new User { Name = "Recipient" };
var messages = new[] { new Message { Text = "a" }, new Message { Text = "b" } };
var conv = new Conversation { SenderUser = send, RecipientUser = rec, Messages = messages };
db.Set<User>().Add(rec);
db.Set<Conversation>().Add(conv);
db.SaveChanges();
}
Result:
Users:
ID Name
1 Recipient
2 Sender
Converstions:
ID RecipientUserID SenderUserID
11 1 2
Messages:
ID Text ConversationID
21 a 11
22 b 11
Given Conversation has 1 User foreign key-column which called SenderUserId
// Here you set the user key for the old user!
conversation.RecipientUser = user;
// Here you are trying to change Conversation.SenderUserId from other user (currentUser)
currentUser.Conversations.Add(conversation);
As you seen the added user into collection is not the same which you have assigned to the Conversation 1..n can not be working in this way correctly.
EF try to say to you with the exception, that you are trying to add/change more then entity for the releshinship one-to-zero-or-one, the error message is a little bit foggy I would say the error message should be like "One-to-many navigation properties should using the same entity"
solution 1)
// conversation.RecipientUser = user; remove this line why you need it?!
solution 2)
Create a new property in conversation one for CurrentUser and the other one for User
public class User
{
public User()
{
Conversations = new List<Conversation>();
CurrentUserConversations = new List<Conversation>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public string UserName { get; set; }
public ICollection<Conversation> Conversations { get; set; }
public ICollection<Conversation> CurrentUserConversations { get; set; }
}
public class Conversation
{
public Conversation()
{
Messages = new List<Message>();
}
public Guid ID { get; set; }
public User RecipientUser { get; set; }
public Guid SenderUserId { get; set; }
public User CurrentUser { get; set; }
public Guid? CurrentUserId { get; set; }
public ICollection<Message> Messages { get; set; }
}
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
HasMany(x => x.Conversations).WithRequired(x => x.RecipientUser).HasForeignKey(x => x.SenderUserId);
HasMany(x => x.CurrentUserConversations).WithOptional(x => x.CurrentUser).HasForeignKey(x => x.CurrentUserId);
}
}
How to use it:
var user = myDbContext.Users.First();
var currentUser = new User { Active = true, Id = Guid.NewGuid() };
myDbContext.Users.Add(currentUser);
var message = new Message { IsDelivered = true };
var conversation = new Conversation() { ID = Guid.NewGuid() };
conversation.Messages.Add(message);
conversation.CurrentUser = user;
currentUser.Conversations.Add(conversation);
myDbContext.Conversations.Add(conversation);
myDbContext.SaveChanges();

MVC4: Retrieving a related list with Entity and casting it as List<> or IEnum<> for View Model

This a simple project where users can search for job postings by area of expertise. The relationship between Areas and Postings are Many-to-many. I seem to be able to get to the very last part of retrieving the correctly filtered list, but getting back into the view model keeps giving me different errors:
ViewModel:
public class AreaOfertasViewModel
{
public Oferta UnaOferta { get; set; }
public SelectList AreasTrabajo { get; set; }
public IEnumerable<Oferta> Ofertas { get; set; }
public int idArea { get; set; }
public AreaOfertasViewModel()
{
this.UnaOferta = UnaOferta;
this.Ofertas = new List<Oferta>();
cargarAreas();
}
private void cargarAreas()
{
PostulaOfertaContext db = new PostulaOfertaContext();
this.AreasTrabajo = new SelectList(db.Areas, "areaId", "Area");
}
}
}
Controller:
public ActionResult SearchXArea()
{
return View(new AreaOfertasViewModel());
}
[HttpPost]
public ActionResult SearchXArea(AreaOfertasViewModel aovm)
{
int id = aovm.idArea;
PostulaOfertaContext db = new PostulaOfertaContext();
var area = db.Areas.Where(c => c.areaId == id);
var ofertas = from c in db.Ofertas.Where(r => r.AreaTrabajo == area)
select c;
aovm.Ofertas = (IEnumerable<Oferta>)ofertas.ToList();
return View(aovm);
}
The line giving me issues is
aovm.Ofertas = (IEnumerable)ofertas.ToList();
I've tried List<> for Ofertas, and I've tried leaving it as .ToList() without casting, and casting it as different things, but it gives me errors about not being able to cast it, and "Cannot compare elements of type 'System.Collections.Generic.List`1'. Only primitive types, enumeration types and entity types are supported."
What's the solution here?
Model for AreaTrabajo:
public class AreaTrabajo
{
[Key]
public int areaId { get; set; }
public string Area { get; set; }
public virtual List<Oferta> oferta { get; set; }
}
Model for Oferta:
public class Oferta
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Titulo { get; set; }
[Required]
public DateTime Vencimiento { get; set; }
[Required]
public string Cargo { get; set; }
[Required]
public int HorarioComienzo { get; set; }
[Required]
public int HorarioFin { get; set; }
[Required]
public string DescripcionTareas { get; set; }
public int Remuneracion { get; set; }
[Required]
public int RangoEdadMin { get; set; }
[Required]
public int RangoEdadMax { get; set; }
public string TipoFormacion { get; set; }
public string Idiomas { get; set; }
public string Competencias { get; set; }
public string OtrosEstudios { get; set; }
public string Estado { get; set; }
public virtual List<AreaTrabajo> AreaTrabajo { get; set; }
public virtual TipoContrato TipoContrato { get; set; }
public virtual Empresa Empresa { get; set; }
public virtual List<Postulante> Postulantes { get; set; }
}
Answer
[HttpPost]
public ActionResult SearchXArea(AreaOfertasViewModel aovm)
{
int id = aovm.idArea;
PostulaOfertaContext db = new PostulaOfertaContext();
var area = db.Areas.Where(c => c.areaId == id).FirstOrDefault();
var ofertas = db.Ofertas.Where(s => s.AreaTrabajo.All(e => e.areaId == area.areaId)).ToList();
aovm.Ofertas = ofertas;
return View(aovm);
}
Sorry if my question wasn't clear enough. I needed to filter out from the many-to-many relationship, and this solved it.
You are getting an error because the actual sql is executed when you call tolist(). The error is in your sql because you are comparing AreaTrabago to a list.
[HttpPost]
public ActionResult SearchXArea(AreaOfertasViewModel aovm)
{
int id = aovm.idArea;
PostulaOfertaContext db = new PostulaOfertaContext();
var area = db.Areas.Where(c => c.areaId == id).FirstOrDefault();
var ofertas = db.Ofertas.Where(s => s.AreaTrabajo.All(e => e.areaId == area.areaId)).ToList();
aovm.Ofertas = ofertas;
return View(aovm);
}
Sorry if my question wasn't clear enough. I couldn't get the many-to-many relationship, and this solved the filtering problem perfectly.

A better way to retrieve EF data and collections

So I have a model that contains a list of models which contains items, and so on, like this:
public partial class CART
{
public CART()
{
//this.CART_DETAIL = new HashSet<CART_DETAIL>();
this.CART_DETAIL = new List<CART_DETAIL>();
}
public int CART_IDE { get; set; }
public int CART_COUNT { get; set; }
public string SHOPPING_CART_IDE { get; set; }
public virtual IList<CART_DETAIL> CART_DETAIL { get; set; }
}
public partial class CART_DETAIL
{
public int CART_DETAIL_IDE { get; set; }
public int CART_IDE { get; set; }
public int CART_DETAIL_COUNT { get; set; }
public Nullable<int> PACK_IDE { get; set; }
public Nullable<int> BACKSTORE_INVENTORY_IDE { get; set; }
public virtual CART CART { get; set; }
public virtual PACK PACK { get; set; }
public virtual BACKSTORE_INVENTORY BACKSTORE_INVENTORY { get; set; }
}
public partial class BACKSTORE_INVENTORY
{
public BACKSTORE_INVENTORY()
{
this.CART_DETAIL = new HashSet<CART_DETAIL>();
this.ORDER_DETAIL = new HashSet<ORDER_DETAIL>();
}
public int BACKSTORE_INVENTORY_IDE { get; set; }
public int INVENT_IDE { get; set; }
public int STORE_IDE { get; set; }
public decimal BACKSTORE_INVENTORY_PRICE { get; set; }
public int BACKSTORE_STOCK_QTY { get; set; }
public decimal BACKSTORE_DISCOUNT { get; set; }
public decimal BACKSTORE_SELLING_PRICE { get; set; }
public virtual INVENTORY INVENTORY { get; set; }
public virtual STORE STORE { get; set; }
public virtual ICollection<CART_DETAIL> CART_DETAIL { get; set; }
public virtual ICollection<ORDER_DETAIL> ORDER_DETAIL { get; set; }
}
When I open a connection and consult the data, everything's fine, but if I retrive the whole data in a view, for example, unless I modify the Hashset to a List and then proceed like this:
CART cart =
db.CART.FirstOrDefault(_item => _item.SHOPPING_CART_IDE == mShoppingCartID && _item.CART_ACTIVE_INDICATOR);
if (cart != null)
{
cart.CART_EXP_TIME = DateTime.Now.AddMinutes(90);
cart.USER_SESSION_IDE = UserSessionManager.GetUserSession().mUserSessionID;
cart.CART_DETAIL = cart.CART_DETAIL.ToList();
foreach (var cartDetail in cart.CART_DETAIL)
{
if(cartDetail.BACKSTORE_INVENTORY_IDE != null)
{
cartDetail.BACKSTORE_INVENTORY =
db.BACKSTORE_INVENTORY.First(_item => _item.BACKSTORE_INVENTORY_IDE == cartDetail.BACKSTORE_INVENTORY_IDE);
cartDetail.BACKSTORE_INVENTORY.INVENTORY =
db.INVENTORY.Find(cartDetail.BACKSTORE_INVENTORY.INVENT_IDE);
cartDetail.BACKSTORE_INVENTORY.INVENTORY.CARD =
db.CARD.Find(cartDetail.BACKSTORE_INVENTORY.INVENTORY.CARD_IDE);
}
else
{
cartDetail.PACK = db.PACK.First(_item => _item.PACK_IDE == cartDetail.PACK_IDE);
}
}
db.SaveChanges();
}
I get the following error: CS0021: Cannot apply indexing with [] to an expression of type 'System.Collections.Generic.ICollection<MyApp.Models.DAL.Entities.CART_DETAIL>' which I understand is because the ICollection does not afford indexing, and then I get The ObjectContext instance has been disposed and can no longer be used for operations that require a connection. for items that I forgot to retrive.
So my question: what makes this happen? Is there a way to retrieve all the data at once without having to get all specific items separately? A better way to do things?
What are you trying to achieve form the above code?
I am struggling to follow what your end goal is but would something along these lines be what you are looking for:
public List<Cart> GetAllInCart()
{
return db.CART.Where(a => a.Cart_IDE == CartIDE)
.Include(x => x.Cart_Detail)
.Include(x => x.Cart_Detail.Pack)
.Include(x => x.Cart_Detail.Backstore_Inventory)
.ToList()
}
I hope this helps :)

Categories

Resources