Unable to update model with virtual list - entity-framework

My movie model has a virtual declaration of ICollection which is working fine in all cases except one. When I try to edit the list from a session variable and call _db.SaveChanges, it is not getting updated to db. Let me tell that I also have a ProducerId field (along with virtual Producer) which is getting updated properly. Where is it going wrong?
Movie model - Movie.cs
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MovieId { get; set; }
public string Name { get; set; }
public string YearOfRelease { get; set; }
public string Plot { get; set; }
public string ImgUrl { get; set; }
[ForeignKey("Producer")]
public int ProducerId { get; set; }
public virtual Producer Producer { get; set; }
public virtual ICollection<Actor> Actors { get; set; }
public virtual ICollection<MovieReview> Reviews { get; set; }
}
CreateOrEdit Controller -
public ActionResult CreateOrEdit([Bind(Include = "MovieId, Name, Plot, YearOfRelease")]Movie movie)
{
if (ModelState.IsValid)
{
int session_producerId = (int)Session["ProducerId"];
movie.Producer = _db.Producers.Find(session_producerId);
movie.ProducerId = session_producerId;
List<int> actors = Session["Actors"] as List<int>;
ICollection<Actor> session_actors = _db.Actors.Where(a => actors.Contains(a.ActorId)).ToList<Actor>();
movie.Actors = session_actors;
// add the movie to db before saving if creating new
if (movie.MovieId == 0)
_db.Movies.Add(movie);
else
// else trigger the edits
_db.Entry(movie).State = EntityState.Modified;
// finally save changes
_db.SaveChanges();
// removing session information
Session.Clear();
return RedirectToAction("Index");
}
return View(movie);
}
Can someone help me to understand why does _db.Entry(movie).State = EntityState.Modified not updating the Actors collection? Any kind of help is appreciated.

Try this,
if (ModelState.IsValid)
{
Movie movieDb;
if (movie.MovieId == 0)
{
movieDb = new Movie { Actors = new List<Actor>() };
_db.Movies.Add(movieDb);
}
else
{
movieDb = _db.Movies.Include(m => m.Actors).FirstOrDefault(m => m.MovieId == movie.MovieId);
}
// Sets producer.
int session_producerId = (int)Session["ProducerId"];
movieDb.Producer = _db.Producers.Find(session_producerId);
movieDb.ProducerId = session_producerId;
// Sets actors.
List<int> actors = Session["Actors"] as List<int>;
ICollection<Actor> session_actors = _db.Actors.Where(a => actors.Contains(a.ActorId)).ToList<Actor>();
Array.ForEach(session_actors.ToArray(), actor => movieDb.Actors.Add(actor));
// Sets other movie's properties here..
// finally save changes
_db.SaveChanges();
// removing session information
Session.Clear();
return RedirectToAction("Index");
}

Related

Saving one entity having m-t-m relationship duplicate the other entity in the DB?

I have two entities with many to many relationship, I'm saving one entity that has a list of entities, but they get duplicated
ex:
class GENEntity
{
int Id { get; set; }
string Name { get; set; }
List<GENEntityTab> tabs { get; set; }
}
class GENEntityTab
{
int Id { get; set; }
string Name { get; set; }
List<GENEntity> entities { get; set; }
}
When I save object of GENEntity but as a view model as you'll see next, with a list of two GENEntityTab, those two tabs get inserted (duplicated) in the DB. I'm using Web API and angular
In the Repository, it's only called when I click submit (there are some extra properties):
public static JsonViewData AddOrUpdate(ModelDBContext context, GENEntityViewModel entityVM, string name, string id)
{
try
{
var entity = context.Entities.SingleOrDefault(x => x.Id == entityVM.Id);
if (entity == null)
{
entity = new GENEntity();
entity.InjectFrom(entityVM);
context.Entities.Add(entity);
}
else
{
entity.DateUpdated = DateTime.Now;
entity.InjectFrom(entityVM);
}
entity.CreatedById = new Guid(id);
entity.LastUpdatedById = new Guid(id);
context.SaveChanges();
return new JsonViewData { IsSuccess = true, Message = "Created Successfully" };
}
catch (Exception ex)
{
return new JsonViewData { IsSuccess = false, Message = ex.Message };
}
}
the view models:
public class GENEntityTabViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<GENEntity> Entities { get; set; } = new List<GENEntity>();
public GENEntityTabViewModel()
{
}
public GENEntityTabViewModel(GENEntityTab entityTab)
{
Id = entityTab.Id;
Name = entityTab.Name;
Description = entityTab.Description;
Entities = entityTab.Entities;
}
}

EF7 Read Master Detail Records

I'm trying to get my head around EF7 by writing a simple master-detail relationship to a sqlite database. Saving works fine, reading however gives me headaches:
Here are my Entities:
public class Message
{
public int MessageId { get; set; }
public string Name { get; set; }
public List<MessagePart> MessageParts { get; set; }
}
public class MessagePart
{
public int MessagePartId { get; set; }
public string Text { get; set; }
public int MessageId { get; set; }
public Message Message { get; set; }
}
createMessage() does what it is supposed to:
static void createMessages()
{
using (var db = new TestContext())
{
var m1 = new Message
{
Name = "train_arrives_in_x_minutes",
MessageParts = new List<MessagePart>()
{
new MessagePart {
Text = "Train arrives in 5 minutes"
},
new MessagePart {
Text = "Zug faehrt in 5 Minuten ein",
}
}
};
var m2 = new Message
{
Name = "train_out_of_service",
MessageParts = new List<MessagePart>()
{
new MessagePart {
Text = "train is out of service"
},
new MessagePart {
Text = "Kein Service auf dieser Strecke",
}
}
};
db.Messages.Add(m1);
db.Messages.Add(m2);
var count = db.SaveChanges();
Console.WriteLine("{0} records saved to database", count);
}
}
Reading from an existing database reads the master record fine, but the detail recordset pointer stays null.
static void readMessages()
{
using (var db = new TestContext())
{
foreach (Message m in db.Messages)
{
Console.WriteLine(m.Name);
// exception here: m.MessageParts is always null
foreach(MessagePart mp in m.MessageParts)
{
Console.WriteLine("mp.Text={0}", mp.Text);
}
}
}
}
Is there anything I can do to force those messagesparts to load? I've worked with other (Python) ORMs before and never had this problem before. Is this a problem with Lazy Loading? I tried to fetch those childrecords using a LINQ statement, that didn't help either. Everything looks good in the database though.
If you want to enable LazyLoading you need to enable LazyLoading (should be enabled by default) and make your property virtual:
public TestContext()
: base(Name = "ConntextionName")
{
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
}
And your models shuodl look like:
public class Message
{
public int MessageId { get; set; }
public string Name { get; set; }
public virtual ICollection<MessagePart> MessageParts { get; set; }
}
public class MessagePart
{
public int MessagePartId { get; set; }
public string Text { get; set; }
public int MessageId { get; set; }
public virtual Message Message { get; set; }
}
If you do not want to use LazyLoading, you can load related entities using eager loading:
using System.Data.Entity;
using (var db = new TestContext())
{
int messageId = ....;
Message message = db.Messages
.Where(m => m.MessageId == messageId)
.Include(m => m.MessageParts) // Eagerly load message parts
.FirstOrDefault();
// Your message and all related message parts are now loaded and ready.
}
For more information, please have a look at this site.

Explict Value can't be inserted in Table when IDENTITY_INSERT is OFF

I get an error when I try to insert a value in my Table.
_dltype is an object of type BRIDownloadType.
using (var db = new BRIDatabase())
{
foreach (var client in db.BRIClients)
{
var todo = new BRIToDo
{
BRIClient = client,
BRIDownloadType = _dltype,
};
db.BRIToDos.Add(todo);
}
db.SaveChanges();
}
Now I get the error:
An Explict Value can't be inserted in the Idendity Column in the BRIDownloadTypes-Table when IDENTITY_INSERT is OFF.
My 2 Tables are
BRIDownloadType
public class BRIDownloadType
{
[Key]
public int DlTypeId { get; set; }
[Required]
[StringLength(15)]
public string DlType { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<BRIToDo> BRIToDo { get; set; }
}
BRITodo
public class BRIToDo
{
[Key]
public int ToDoId { get; set; }
[ForeignKey("BRIClient")]
public int ClientId { get; set; }
[ForeignKey("BRITask")]
public int TaskId { get; set; }
public virtual BRIClient BRIClient { get; set; }
public virtual BRITask BRITask { get; set; }
[ForeignKey("BRIDownloadType")]
public int DlTypeId { get; set; }
public virtual BRIDownloadType BRIDownloadType { get; set; }**
}
The interesting thing is, if I do something with my _dltype object, I can use it.
The following code is working and I don't understand why, I'm inserting the exact same object.
using (var db = new BRIDatabase())
{
var dl = db.BRIDownloadTypes.FirstOrDefault(c => c.DlTypeId == _dltype.DlTypeId);
foreach (var client in db.BRIClients)
{
var todo = new BRIToDo
{
BRIClient = client,
BRIDownloadType = _dltype,
};
db.BRIToDos.Add(todo);
}
db.SaveChanges();
}
Can anybody explain to me, why the last approach is working and the first is throwing that error? I just added the line
var dl = db.BRIDownloadTypes.FirstOrDefault(c => c.DlTypeId == _dltype.DlTypeId)
But I'm still inserting the same object. If I insert the Id of the object instead of the object it is also working fine. I have no idea whats going on there.

why the explicit load does not work,and the navigation property always null?

All my code is here,quite simple,and I don't konw where it goes wrong.
Person and Task has an many-to-many relationship.
I want to load someone's task using the explicit way.
I follow the way this post shows,and i can't make it work.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public string Subject { get; set; }
public ICollection<Person> Persons { get; set; }
}
public class Ctx : DbContext
{
public Ctx()
: base("test")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Person> Persons { get; set; }
public DbSet<Task> Task { get; set; }
}
class Program
{
static void Main(string[] args)
{
//add some data as follows
//using (var ctx = new Ctx())
//{
//ctx.Persons.Add(new Person { Name = "haha" });
//ctx.Persons.Add(new Person { Name = "eeee" });
//ctx.Task.Add(new Task { Subject = "t1" });
//ctx.Task.Add(new Task { Subject = "t2" });
//ctx.SaveChanges();
//var p11 = ctx.Persons.FirstOrDefault();
//ctx.Task.Include(p2 => p2.Persons).FirstOrDefault().Persons.Add(p11);
//ctx.SaveChanges();
//}
var context = new Ctx();
var p = context.Persons.FirstOrDefault();
context.Entry(p)
.Collection(p1 => p1.Tasks)
.Query()
//.Where(t => t.Subject.StartsWith("t"))
.Load();
//the tasks should have been loaded,isn't it?but no...
Console.WriteLine(p.Tasks != null);//False
Console.Read();
}
}
Is there anything wrong with my code?I'm really new to EF,so please, someone help me.
The problem is your .Query() call. Instead of loading the collection, you are getting a copy of the IQueryable that would be used to load, then executing the query.
Remove your .Query() line and it will work.
If what you are looking for is getting a filtered list of collection elements, you can do this:
var filteredTasks = context.Entry(p)
.Collection(p1 => p1.Tasks)
.Query()
.Where(t => t.Subject.StartsWith("t"))
.ToList();
This will not set p.Tasks, nor is it a good idea to do so, because you'd be corrupting the domain model.
If you really, really want to do that... this might do the trick (untested):
var collectionEntry = context.Entry(p).Collection(p1 => p1.Tasks);
collectionEntry.CurrentValue =
collectionEntry.Query()
.Where(t => t.Subject.StartsWith("t"))
.ToList();
This solution worked for me :
For some reasons EF requires virtual keyword on navigation property, so the entities should be like this :
public class Person
{
//...
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
//...
public virtual ICollection<Person> Persons { get; set; }
}

Entity Framework linked list

I have an entity (I am using code first) that looks like that:
public class Node
{
public int ID { get; set; }
public string SomeInfo { get; set; }
public virtual Node Previous { get; set; }
public virtual Node Next { get; set; }
}
There is no problem to save the Next Node for example. However if the ID of Previous is 1 and I try to set the Next Node (wich is the one with ID=1) to 2 this exception is thrown.
The object cannot be added to the object context. The object�s
EntityKey has an ObjectStateEntry that indicates that the object is
already participating in a different relationship.
I am saving the node like this:
int nextId;
int previousId;
if (int.TryParse(Request["previous"], out previousId))
node.Previous = this.nodeRepository.GetSingle(previousId);
if (int.TryParse(Request["next"], out nextId))
node.Next = this.nodeRepository.GetSingle(nextId);
this.nodeRepository.Update(node);
Update looks like this:
public virtual void Update(T entity)
{
this.context.Entry(GetSingle(entity.ID)).State = EntityState.Detached;
this.context.Entry(entity).State = EntityState.Added;
this.context.Entry(entity).State = EntityState.Modified;
this.Save();
}
And GetSingle like this:
public virtual T GetSingle(object id)
{
var query = this.entities.Find(id);
return query;
}
UPDATE 1
The line with the exception is in the Update method:
this.context.Entry(entity).State = EntityState.Modified;
Is your context in a Using block and being disposed of at some point? I've had similar 'The object cannot be added to the object context." errors. I would add Id's for Previous and Next to your model and use those to update the foreign keys.
public class Node
{
public int ID { get; set; }
public string SomeInfo { get; set; }
public virtual Node Previous { get; set; }
public int PreviousId { get; set; }
public virtual Node Next { get; set; }
public int NextId { get; set; }
}
To update the foreign keys...
int nodeId; // I'm assuming you know the id of node you want updated.
int nextId;
int previousId;
using (var context = new Context())
{
// Perform data access using the context
var node = context.Nodes.find(nodeId);
node.NextId = nextId;
node.PreviousId = previousId;
context.SaveChanges();
}