I have two tables written in EF CodeFirst:
public class DayType
{
[Key]
public int DayTypeID { get; set; }
public string NameDayType { get; set; }
public virtual ICollection<SpecialDay> Specialdays { get; set; }
public DayType() { }
}
public class SpecialDay
{
[Key]
public int SpecialDayID { get; set; }
public int Year { get; set; }
public int JanuaryDay { get; set; }
public SpecialDay() { }
public int? DayTypeId { get; set; }
public virtual DayType daytype { get; set; }
}
DBContext relation one-to-many were made by FluentAPI:
modelBuilder.Entity<DayType>().HasMany(p => p.Specialdays).WithOptional(p => p.daytype);
This code throw exception. The purpose of function is to update entity. While debugging sd had all properties. sd - is object which were selected from datagrid and then changed.
internal void Update(SpecialDay sd)
{
using (SalDBContext _db = new SalDBContext())
{
var newsd = _db.SpecialDays.FirstOrDefault(p => p.SpecialDayID==sd.SpecialDayID);
newsd.JanuaryDay = sd.JanuaryDay;
....
newsd.DecemberDay = sd.DecemberDay;
newsd.DayTypeId = sd.DayTypeId;
newsd.daytype = sd.daytype;
try
{
_db.SaveChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
At these point exception is happened _db.SaveChanges(); Exception:
{"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."} System.Exception {System.InvalidOperationException}
Would be thankful for any help with solution for my problems. Thank you
As the exception states,
"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
Looking at the code you have there, you are assigning the daytype from the one passed in to the one you pulled from the database. Because you're creating a new SalDBContext every time you call into the Update method, you're assigning the daytype across ObjectContext objects (per the error message.
To get around that, you just need to eliminate that assignment in your Update method. Because you're assigning the FK ID in the property, you do not also need to assign the object.
One other note on EF, there is also a Find method which will go just by the ID instead of FirstOrDefault. It's a bit more optimized.
internal void Update(SpecialDay sd)
{
using (SalDBContext _db = new SalDBContext())
{
var newsd = _db.SpecialDays.Find(p => p.SpecialDayID==sd.SpecialDayID);
newsd.JanuaryDay = sd.JanuaryDay;
....
newsd.DecemberDay = sd.DecemberDay;
newsd.DayTypeId = sd.DayTypeId;
// newsd.daytype = sd.daytype; Must be eliminated!
try
{
_db.SaveChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
You have loaded sd (from method head) with anonther context as the one in your method body newsd. Therefore you received an exception on calling _db.SaveChanges();. I would say in line newsd.daytype = sd.daytype;, because it's a complex data type and represents one of your entities. Remove the line and set only the ID or load the obejct sd again for referencing.
Modified version of yours:
internal void Update(SpecialDay sd)
{
using (SalDBContext _db = new SalDBContext())
{
var newsd = _db.SpecialDays.FirstOrDefault(p => p.SpecialDayID==sd.SpecialDayID);
newsd.JanuaryDay = sd.JanuaryDay;
// ...
newsd.DecemberDay = sd.DecemberDay;
// set only the id to reference the object
newsd.DayTypeId = sd.DayTypeId;
// newsd.daytype = sd.daytype;
try
{
_db.SaveChanges();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
Well I was some seconds to late. :)
Related
I have a class named Classroom which is something like:
public class Classroom
{
[Key]
public int ClassroomId { get; set; }
public string ClassroomTitle { get; set; }
public string AccessCode { get; set; }
public string ColorPicker { get; set; }
public LevelGroup LevelGroup { get; set; }
}
The LevelGroup class is something like:
public class LevelGroup
{
public int MLevelId { get; set; }
public int MGroupId { get; set; }
public Level Level { get; set; }
public Group Group { get; set; }
}
In my API, I am trying to retrieve the data of type Classroom like:
[HttpPost("AddClassroom")]
public async Task<JsonResult> AddClassroom([FromBody] Classroom classroom)
{
if (!ModelState.IsValid)
{
return Json(new ApiMessage
{
HasError = true,
Message = "InvalidModel",
});
}
try
{
_context.Add(classroom);
await _context.SaveChangesAsync();
return Json(new ApiMessage
{
HasError = false,
Message = "Success"
});
}
catch (Exception e)
{
return Json(new ApiMessage
{
HasError = true,
Message = e.Message
});
}
}
From the POSTMAN, I tried to hit the API at some url and in the BODY, I've passed this object:
{
"classroomTitle": "SWE",
"accessCode": "6s1x4d1",
"colorPicker": "blue",
"levelGroup": {
"mLevelId": 1,
"mGroupId": 2
}
}
But, This is not working. It says:
An exception occurred in the database while saving changes for context type 'mirror_api.Models.ApplicationDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'UNIQUE constraint failed: LevelGroups.MGroupId, LevelGroups.MLevelId'.
How to solve this problem?
Based on Your comments I understand You want to save passed object but You do not want to save inner property of it as it violates some DB constraints.
What I would try is to detach those properties from EF tracking before saving so it won't mark classroom.LevelGroup as Added. You can see this example. You can also control which objects EF is tracking for changes by setting a proper state for each individual property that was added to the EF context (docs).
You also want to read this which nearly describes what You seek:
var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };
using (var context = new BloggingContext())
{
context.Blogs.Attach(existingBlog);
context.Entry(existingBlog).State = EntityState.Unchanged;
// Do some more work...
context.SaveChanges();
}
but instead of attaching the object, You want to add it and then set one of its properties as EntityState.Detached to ignore it completly (or EntityState.Unchanged to keep tracking it but tell EF that there is nothing to save here). Something more like this:
...
_context.Add(classroom);
_context.Entry(classroom.LevelGroup).State = EntityState.Detached;
await _context.SaveChangesAsync();
...
The moment You add the object, EF "gegins tracking the given entity, and any other reachable entities" and You can "Use State to set the state of only a single entity" as shown.
I'm attempting to create an Audit Log for my MVC, Entity Framework website project. I've been able to subscribe to SaveChanges() in my DBContext (and save to my database through another DBContext but same database).
My two questions in the end are:
What does if (!entry.IsRelationship) do exactly? I have a ViewModel that calculates this as True when Saving and another as False. I would expect this to move into the rest of my method to save in the Audit Log.
How can I get the full Namespace of my Object being modified? I was using this: entry.Entity.ToString() but doesn't seem to work when Saving/Editing from a View Model (details below)
Here is a basic setup that I have thus far (Album object/controller works, but AlbumView doesn't):
Ablum class:
public class Album : BaseObject //BaseObject has a few properties, one is Oid (Guid)
{
public string Name { get; set; }
[Column(TypeName = "varchar(MAX)")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name="Genres")]
public virtual ICollection<AlbumsGenres> AlbumGenres { get; set; }
[Display(Name="Artists")]
public virtual ICollection<AlbumsArtists> AlbumArtists { get; set; }
}
AblumView class:
public class AlbumView
{
[ScaffoldColumn(false)]
public Guid Oid { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "varchar(MAX)")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Display(Name = "Genres")]
public virtual List<AlbumsGenres> AlbumGenres { get; set; }
[Display(Name = "Artists")]
public virtual List<AlbumsArtists> AlbumArtists { get; set; }
}
AlbumsController (Audit works with something like this):
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges(); //This is where SaveChanges() takes over (see below)
return RedirectToAction("Index");
}
return View(album);
}
AlbumsViewController:
public ActionResult Edit(Guid id, AlbumView albumViewModel)
{
//Omitting setup...
//Album gets updated
Album album = db.Albums.Find(id);
album.Name = albumViewModel.Name;
album.Description = albumViewModel.Description;
//Other Objects are also updated, just an example:
albumArtists = new AlbumsArtists();
albumArtists.Oid = Guid.NewGuid();
albumArtists.Album = db.Albums.Find(id);
albumArtists.Artist = db.Artists.Find(item.Artist.Oid);
//In the end it calls:
db.SaveChanges();
//Omitting other stuff...
}
On db.SaveChanges() within my DbContext:
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext() : base("name=DefaultConnection") { }
public System.Data.Entity.DbSet<ContentPub.Models.Music.Album> Albums { get; set; }
//Other DBSet objects...
public DbSet Set(string name)
{
return base.Set(Type.GetType(name));
}
public override int SaveChanges()
{
ApplicationLogDBContext logDb = new ApplicationLogDBContext();
ChangeTracker.DetectChanges();
ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;
List<ObjectStateEntry> objectStateEntryList =
ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added
| EntityState.Modified
| EntityState.Deleted)
.ToList();
foreach (ObjectStateEntry entry in objectStateEntryList)
{
Guid oid = Guid.Empty;
try
{
if (!entry.IsRelationship) //I don't understand this (first of my two questions)
{
switch (entry.State)
{
//Removed other cases
case EntityState.Modified:
{
oid = (Guid)entry.EntityKey.EntityKeyValues[0].Value;
//This is the area that I am having issues (second of the two questions)
//Below will work when I call db.SaveChanges() from the AlbumsController,
//'entry.Entity.ToString()' will get 'x.Models.Music.Albums' and begin a query
var query = this.Set(entry.Entity.ToString()).AsNoTracking().Where("Oid == #0", oid);
//The issue with the above is when I have a ViewModel, returns something like
// = System.Data.Entity.DynamicProxies.Album_AF81C390156ACC8283ECEC668AFB22C4AD621EF70F8F64641D56852D19755BF3
//If the proper Namespace is returned, the next line works and Audit continues
var query = this.Set(entry.EntitySet.ElementType.ToString()).AsNoTracking().Where("Oid == #0", oid);
//Does a bunch of AuditLog stuff if the above issue doesn't fail
break;
}
}
}
}
catch (Exception ex)
{
throw new Exception("Log Error (" + entry.Entity.ToString() + ") - " + ex.ToString());
}
}
return base.SaveChanges();
}
}
entry.Entity.ToString() will return something like:
System.Data.Entity.DynamicProxies.Album_AF81C390156ACC8283ECEC668AFB22C4AD621EF70F8F64641D56852D19755BF3
In the AlbumView I am updating Album, and a bunch of other Objects. Not sure why it isn't returning x.Models.Music.Albums, is there a work-around, can someone explain or point me to other resources that I haven't found yet?
While it isn't the most efficient solution, it still is a solution for now.
I was able to do the following inside my db.SaveChanges() method:
//When AlbumView .BaseType was able to return x.Models.Music.Album
string strNamespace = entry.Entity.GetType().BaseType.ToString();
//Needed this if I was updating just an Object (ie: Album),
//would be nice to make something more concret
if (strNamespace == "x.Models.Core.BaseObject")
strNamespace = entry.Entity.ToString();
//Continuing code
var query = this.Set(strNamespace).AsNoTracking().Where("Oid == #0", oid);
Found the answer here from another Question that I had not found before posting this question
i have an issue which is driving me up the wall.
The code below adds a note into the database which is then listed out. The problem is, it adds the first note perfectly fine, then i go to add another note and i get a "Constraints" error! I just can see why.
Modal;
namespace MyApp.Models
{
[Table("notes")]
public class Note
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[Unique]
public long ProgramId { get; set; }
public string source { get; set; }
public int TaskId { get; set; }
public string UserId { get; set; }
public DateTime DateAdded { get; set; }
public string Content { get; set; }
public static void addNote(Note newNote)
{
//SQLiteConnection conn = new SQLiteConnection(AssessNETApp.Data.Globals.DB);
using (var conn = new SQLiteConnection(AssessNETApp.Data.Globals.DB))
{
try
{
conn.Insert(newNote);
}
catch (SQLiteException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
}
Code for button click to add note
async void OnAddNoteButtonClick(object sender, EventArgs e)
{
if (Text.Text.Length > 0)
{
Note NoteData = new Note();
NoteData.DateAdded = DateTime.Now;
NoteData.TaskId = thisTask.TaskId;
NoteData.UserId = MyApp.Data.LoggedInUser.UserID;
NoteData.Content = Text.Text.ToString();
Note.addNote(NoteData);
await Navigation.PopAsync();
}
}
First note i add , fine! Second, error..
[Unique]
public long ProgramId { get; set; }
if you don't explicitly assign a ProgramID, it will default to 0. So the first is Unique, but the second is also 0, which violates the constraint.
I have since fixed it. For some reason i wondered if the actual data table had been altered considering they remain on the device. I have now dropped the tables so they re-create and hence this has actually removed where i did have [Unique].
Lesson learnt is that if you make changes to the Model, drop the tables else its still just running off the original unless the DB or tables are created each time you use the app.
I have extended a entity framework class (SQL server table) and added some extra properties to the child class but when I want to insert into the table which I already extended I get this exception:
Mapping and metadata information could not be found for EntityType 'Student.Models.Add.SubjectToStageModel'.
My controller:
[HttpPost]
public ActionResult SubjectToStage(SubjectToStageModel model)
{
try
{
if (ModelState.IsValid)
{
using (StudentEntities studentEntities = new StudentEntities())
{
int intCount =
studentEntities.SubjectToStageTbls.Count(
x => x.StageId == model.StageId && x.SubjectId == model.SubjectId);
if (intCount == 0)
{
studentEntities.SubjectToStageTbls.Add(model);
studentEntities.SaveChanges();
}
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
TempData["error"] = "An error occured";
}
return RedirectToAction("SubjectToStage");
}
My base class:
public partial class SubjectToStageTbl
{
public SubjectToStageTbl()
{
this.StudentMarkTbls = new HashSet<StudentMarkTbl>();
}
public int SubjectToStageId { get; set; }
public int SubjectId { get; set; }
public int StageId { get; set; }
public int Point { get; set; }
public virtual StageTbl StageTbl { get; set; }
public virtual ICollection<StudentMarkTbl> StudentMarkTbls { get; set; }
public virtual SubjectTbl SubjectTbl { get; set; }
}
My subclass:
public class SubjectToStageModel : SubjectToStageTbl
{
public IEnumerable<SelectListItem> StageListItem
{
get
{
List<SelectListItem> listsSelectListItems = new List<SelectListItem>();
try
{
using (StudentEntities studentEntities = new StudentEntities())
{
IQueryable<StageTbl> queryableStage = studentEntities.StageTbls;
foreach (var stage in queryableStage)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = stage.StageId.ToString();
selectListItem.Text = stage.StageName;
listsSelectListItems.Add(selectListItem);
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return listsSelectListItems;
}
}
public IEnumerable<SelectListItem> SubjectListItem
{
get
{
List<SelectListItem> listsSelectListItems = new List<SelectListItem>();
try
{
using (StudentEntities studentEntities = new StudentEntities())
{
IQueryable<SubjectTbl> queryableSubject = studentEntities.SubjectTbls;
foreach (var stage in queryableSubject)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = stage.SubjectId.ToString();
selectListItem.Text = stage.SubjectName;
listsSelectListItems.Add(selectListItem);
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return listsSelectListItems;
}
}
}
You didn't explicitly map the SubjectToStageModel class. If you want Entity Framework to work with derived classes you should add them to the model as well. But I don't think you intended to do that in the first place.
In fact, SubjectToStageModel is a view model. It may look convenient to derive a view model from an entity class, but I think generally it's not a good idea. View models should be tailored to the view (or use case) they're used in. A couple of reasons:
It's very likely that the entity class contains more properties than you need in the view. In later maintenance it's always a pain to keep checking what you do and don't need.
While view evolves, it may require a differently structured model than the entity.
The view may require different validations.
The view may be allowed to return a state that absolutely shouldn't be stored (you may require some post processing of entered data), so it's good to ensure it can't possibly be stored.
It creates a dependency between the data layer model and the view.
Maybe these consideration don't apply in your case. Still I'd prefer to have an independent view model. However, if you're lazy (we developers prefer the word pragmatic), you may succeed by doing:
studentEntities.SubjectToStageTbls.Add((SubjectToStageTbl)model);
(I never tried though).
I have ONE entity that i want to update without updating its List of MANY entity. Im using Code-First But i cant get it to work...
Im using Ninject and everything is working except my update...
//Entities
public class A
{
public int AId { get; set; }
public string Name { get; set; }
}
public class B
{
public int BId { get; set; }
public string Name { get; set; }
public virtual List<A>ListOfAs { get; set; }
}
//Interface
private EFDbContext context = new EFDbContext();
public IQueryable<B> Bs
{
get { return context.B; }
}
public void SaveBs(B b)
{
if (b.Id== 0)
{
context.B.Add(b);
context.SaveChanges();
}
*//here i wanna call:
context.Entity(b).State = EntityState.Modified;
BUT VS dont let me... I probably missing something out...*
context.SaveChanges();
the Save method is working when i want to just add a new object to my database. But the update wont change anything...
I would appricate if anyone could tell me what im missing out...
/Thx J
Use The Following Criteria, This work for me
public BuyerInformation Update(BuyerInformation objBuyerInformation)
{
context.BuyerInformation.Attach(objBuyerInformation);
context.Entry(objBuyerInformation).State = EntityState.Modified;
context.SaveChanges();
return objBuyerInformation;
}
if (b.Id== 0)
{
context.B.Add(b);
}
else
{
context.B.Attach(b);
}
context.SaveChanges();