UpdateWithChildren sqlite-net-extensions - sqlite-net-extensions

I have confused with UpdateWithChildren and InsertOrReplaceWithChildren.
I can't get it work with UpdateWithChildren, but It can work with InsertOrReplaceWithChildren.
so I have deleted the db, then apply InsertOrReplaceWithChildren, but the problem is that the Id is AutoIncrement, the Id keeps adding on.
Would you give me some advice?
Thanks.
public class WeekHistory {
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public DayOfWeek DayOfWeek { get; set; }
public int NoOfDays { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<DayHistory> DayHistories { get; set; } }
public class DayHistory {
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Hour { get; set; }
public Action Action { get; set; }
public TimeSpan Duration { get; set; }
[ForeignKey(typeof(WeekHistory))]
public int WeekId { get; set; }
[ManyToOne] // Many to one relationship with WeekHistory
public WeekHistory WeekHistory { get; set; }}
List<DayHistory> newMonday = new List<DayHistory>()
{
new DayHistory() {Hour = 1, Action = Action.Known, Duration = new TimeSpan(0,20,0)},
new DayHistory() {Hour = 1, Action = Action.Unknown, Duration = new TimeSpan(0,40,0)},
new DayHistory() {Hour = 2, Action = Action.Known, Duration = new TimeSpan(0,40,0)},
new DayHistory() {Hour = 2, Action = Action.Unknown, Duration = new TimeSpan(0,20,0)},
new DayHistory() {Hour = 3, Action = Action.Known, Duration = new TimeSpan(0,50,0)},
new DayHistory() {Hour = 3, Action = Action.Unknown, Duration = new TimeSpan(0,10,0)}
};
var mondayHistory = dbConn.GetWithChildren<WeekHistory>(1, true);
//delete the db, instead of the UpdateWithChildren
dbConn.DeleteAll(mondayHistory.DayHistories);
mondayHistory.DayHistories = newMonday;
mondayHistory.NoOfDays += 1;
//it won't update the child
//dbConn.UpdateWithChildren(mondayHistory);
//apply new db with children
dbConn.InsertOrReplaceWithChildren(mondayHistory);

I don't see the issue with your second sample. You are deleting the previous DayHistorys and inserting new ones, so the Ids will be different, but nothing to worry about there.
About your first question, UpdateWithChildren updates registers that already exists in the database. If you call UpdateWithChildren on the parent object, the children (DayHistory in this case) won't be inserted into database. Updating the children won't work because you are not assigning any primary key, so the database can't update them either.
Easier solution here is just insert the elements in the database first and then call UpdateWithChildren to update the foreign keys:
// Delete previous DayHistories to avoid orphaned objects
dbConn.DeleteAll(mondayHistory.DayHistories);
// Assign new elements
mondayHistory.DayHistories = newMonday;
// Insert new elements into database
dbConn.InsertAll(mondayHistory.DayHistories);
// Update parent object to correctly assign foreign keys for the relationships
dbConn.UpdateWithChildren(mondayHistory);

Related

Getting Error : A circular reference was detected while serializing an object of type 'Abc.Models.ProductItem'

I'm going for the edit data by ProuctId.i have 2 table like Product and productitems.so i m click on edit go for the id by getting data but that time i m fetch data by id in the product table is getting propare but after going for the list productitems data getting like this error.
this is my class ProductItmes:
[Table("ProductItems ")]
public class ProductItems
{
[Key]
public int id { get; set; }
[ForeignKey("Products")]
public int pid {get; set;}
public int Qty { get; set; }
public Decimal Rate { get; set; }
public virtual Products Products { get; set; }
}
this is my api method:
public ActionResult GetProductByid(int id)
{
var Pro = db.Product.Find(id);
var ProItemList = db.Promotion_ctc.Where(x => x.pid == Pro.id).ToList();//here i am getting list of items by productid
var Details = new
{
Pro.id,
Pro.name,
Pro.image_url,
ProItemList
};
return Json(new { data = Details });
}
idk where is my problem any one know please let me know.
When working with MVC and Entity Framework, there are cases where we make our child entities reference the parent, like you did, by declaring this property here:
public virtual Products Products { get; set; }
it's ok for entity framework, but it's not when you try to serialize this.
What's going on:
The serializer will try to serialize the parent, which has a collection of ProductItem.
The serializer tries to serialize each child.
The child has a reference to parent, so the serializer tries to serialize the parent again.
Infinite loop.
That's why people use ViewModels. Instead of just returning your entities from your action, project them into a view model, and return it. Actually, you're returning an anonymous object containing a ProItemList, which I'd guess it's a List of ProductItems. Just create a view model for it:
public class ProductItemViewModel
{
public int ItemId { get; set; }
public int ProductId {get; set;}
public int Qty { get; set; }
public Decimal Rate { get; set; }
// public virtual Products Products { get; set; } NO PRODUCT HERE
}
...then fix your action to return a List of ProductItemViewModel, instead of returning directly ProductItems, like this:
var ProItemList = db.Promotion_ctc.Where(x => x.pid == Pro.id)
.Select(i => new ProductItemViewModel
{
ItemId = i.ItemId,
ProductId = i.ProductId,
Qty = i.Qty,
Rate = i.Rate
})
.ToList();
var Details = new
{
Pro.id,
Pro.name,
Pro.image_url,
ProItemList
};
return Json(new { data = Details });
}
send only required columns to ui
like this
var passToUi = from s in listResult
select new { s.Id, s.Name };
return Json(passToUi, JsonRequestBehavior.AllowGet);

Entity framework will replace a newly added record , with a record created inside the action method

I want to implement this simple scenario ,which I though EF will support out of the box.
I have a parent record named (Skill) and I am adding child records named (LinktoKB) to it. Now after adding a new LinktoKB, I want to return a view containing the up-to-date list of LinkToKBs (inclusing the newly added one).
Now my Post action method to add new LinktoKB is :-
[HttpPost]
[ValidateAntiForgeryToken]
[CheckUserPermissions(Action = "Edit", Model = "Skill")]
public async Task<ActionResult> AddKBLink(AssignKBLinksToSkill assignkblinkToSkill)
{
try
{
if (assignkblinkToSkill.LinkToKB == null)
{
return HttpNotFound();
}
if (ModelState.IsValid)
{
unitofwork.SkillRepository.AddKBLinkToSkill(assignkblinkToSkill, unitofwork.StaffRepository.GetLoginUserName(User.Identity.Name));
await unitofwork.Save();
//i have removed the values from the model state to prevent showing validation error "that the URL and name is required after succfully adding a new link"
// also to show the modified values and not the binded values
string oldlinkURL = assignkblinkToSkill.LinkToKB.URL;
ModelState.Clear();
var skillAfterAddingKBLink = await unitofwork.SkillRepository.FindSkill(assignkblinkToSkill.Skillid, r => r.LinkToKBs);
assignkblinkToSkill.LinktoKBList = skillAfterAddingKBLink.LinkToKBs.ToList(); //get the new lsit from DB after addign the new link
assignkblinkToSkill.LinkToKB.URL = "http://";//reset the values , so that user will not get old vlues
assignkblinkToSkill.LinkToKB.Name = String.Empty;
if (Request.IsAjaxRequest())
{
TempData["Partialmessage"] = string.Format("{0} URL have been Added", oldlinkURL);
return PartialView("AddKBLink", assignkblinkToSkill);
}
TempData["message"] = string.Format("{0} URL have been Added", oldlinkURL);
return View("AddKBLink", assignkblinkToSkill);
}
}
And my repository methods are:-
public async Task<Skill> FindSkill(int id, params Expression<Func<Skill, object>>[] includeProperties)
{
var query = context.Skills.AsQueryable();
if (includeProperties != null || includeProperties.Count() != 0 || includeProperties[0].Name == "0")
query = includeProperties.Aggregate(query, (current, include) => current.Include(include));
return await query.SingleOrDefaultAsync(a => a.SkillID == id);
}
&
public void AddKBLinkToSkill(AssignKBLinksToSkill assignKBLinkToSkill,string username)
{
var skill = context.Skills.SingleOrDefault(a=>a.SkillID == assignKBLinkToSkill.Skillid);
skill.LinkToKBs.Add(assignKBLinkToSkill.LinkToKB);
skill.Modified = System.DateTime.Now;
skill.ModifiedBy = staffrepo.GetUserIdByUserName(username);
context.Entry(skill).State = EntityState.Modified;
}
Currently I am getting a very strange behavior is that , the list that is returned to the view will not contain the newly added LinkToKB value and it will be replaced by the following value:-
assignkblinkToSkill.LinkToKB.URL = "http://"
so can anyone advice on this please, although I am explicitly retrieving the LinkToKB list from database?
visual studio will how the following at two different stages:-
First this is the newly added LinkToKB:-
Second EF have replace it with the value inside the action method:-
I spend the whole day trying to understand what is going on ... and if i removed these lines:-
assignkblinkToSkill.LinkToKB.URL = "http://";//reset the values , so that user will not get old vlues
assignkblinkToSkill.LinkToKB.Name = String.Empty;
i will get the new up-to-date list correctly (but i need them)..
I have two model classes (Skill & LinktoKB):-
public partial class Skill
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Skill()
{
this.SkillLevels = new HashSet<SkillLevel>();
this.SkillLevelStaffs = new HashSet<SkillLevelStaff>();
this.Customers = new HashSet<Customer>();
this.LinkToKBs = new HashSet<LinkToKB>();
this.SkillVersionHistories = new HashSet<SkillVersionHistory>();
this.Skill1 = new HashSet<Skill>();
this.Skills = new HashSet<Skill>();
}
public int SkillID { get; set; }
public string Name { get; set; }
//code goes here
public virtual SkillStatu SkillStatu { get; set; }
public virtual SkillType SkillType { get; set; }
public virtual ICollection<LinkToKB> LinkToKBs { get; set; }
}
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public LinkToKB()
{
this.Skills = new HashSet<Skill>();
}
public int LinkToKBID { get; set; }
public string URL { get; set; }
public string Name { get; set; }
public virtual ICollection<Skill> Skills { get; set; }
}
and the following viewModel class:-
public class AssignKBLinksToSkill
{
public ICollection<LinkToKB> LinktoKBList { set; get; }
public LinkToKB LinkToKB { set; get; }
public int Skillid { set; get; }
}
In your code there's always one assignkblinkToSkill.LinkToKB instance. When it enters the method it's got some value that you store in the database. Later you re-assign its value to be "http://".
But this is still the instance that you added to the list skillAfterAddingKBLink.LinkToKBs!
You only have to create a new instance in the view model:
assignkblinkToSkill.LinkToKB = new LinkToKB();
assignkblinkToSkill.LinkToKB.URL = "http://";

Loop Adding Records Throws Exception

Does anyone know what the proper way of adding records using loops?
I have a system that handles Inventory, Currently I need to be able to Mass Create inventory as creating 50-100 identical items with different ID's would be tedious, What I did was create a MassCreate viewmodel that would essentially take a StartID and an EndID and a base Inventory Class and in the controller loop through the difference between those two ID's and create a record
The ViewModel isn't an issue and passes the data just fine:
public class MassCreateInventoryViewModel
{
public Inventory InventoryBase { get; set; }
public int StartID { get; set; }
public int EndID { get; set; }
public IEnumerable<SelectListItem> Products { get; set; }
}
I read somewhere that the db.SaveChanges() should be outside of the loop as it should only be called once:
for (int inventoryID = viewModel.StartID; inventoryID <= viewModel.EndID; inventoryID++)
{
Inventory newInventory = new Inventory
{
InventoryID = inventoryID,
ProductID = viewModel.InventoryBase.ProductID,
DateEdited = DateTime.Now,
EditedByUserID = WebSecurity.CurrentUserId,
CustomProperties = viewModel.InventoryBase.CustomProperties
};
Database.Inventories.Add(newInventory);
if (newInventory.CustomProperties != null && newInventory.CustomProperties.Any())
{
foreach (CustomDataType dt in newInventory.CustomProperties.Select(x => x.DataType).ToList())
{
Database.Entry(dt).State = EntityState.Unchanged;
}
}
}
Database.SaveChanges();
}
But when I try looping, it stores the first record just fine then throws a Collection was modified; enumeration operation may not execute. Exception. When I include the Database.SaveChanges() after the Add method, it throws A The property 'InventoryID' is part of the object's key information and cannot be modified. error.
The InventoryID is the Key in this table but has been set so that I can input my own ID.
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Inventory ID")]
public new int InventoryID { get; set; }
The Custom Property is split into two models, the first being the base class.
public class CustomProperty
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomPropertyID { get; set; }
public int CustomDataTypeID { get; set; }
[ForeignKey("CustomDataTypeID")]
public CustomDataType DataType { get; set; }
public string PropertyValue { get; set; }
}
and the second being the model thats mapped to the database:
[Table("CustomInventoryProperty")]
public class CustomInventoryProperty : CustomProperty
{
public int InventoryID { get; set; }
[ForeignKey("InventoryID")]
public virtual Inventory Inventory { get; set; }
}
Replace your for loop with this:
var dateEdited = DateTime.Now;
for (int inventoryID = viewModel.StartID; inventoryID <= viewModel.EndID; inventoryID++)
{
Inventory newInventory = new Inventory
{
InventoryID = inventoryID,
ProductID = viewModel.InventoryBase.ProductID,
DateEdited = dateEdited,
EditedByUserID = WebSecurity.CurrentUserId
};
if(viewModel.InventoryBase.CustomProperties != null)
{
newInventory.CustomProperties = new List<CustomProperties>();
foreach(var customProperty in viewModel.InventoryBase.CustomProperties)
{
newInventory.CustomProperties.Add(customProperty);
}
}
Database.Inventories.Add(newInventory);
Database.SaveChanges();
}

Adding a child object to parent without explicit foreign key id fields with EF Code-First in ASP.Net MVC

I have two model classes - Parent and Child which are only linked via typed navigational properties.
public class Parent {
[Key]
[Required]
public int Id { get; set; }
[Required]
public string ParentName { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child {
[Key]
[Required]
public int Id { get; set; }
[Required]
public string ChildName { get; set; }
[Required]
public virtual Parent Parent { get; set; }
}
Now I want to create a new Child for a parent using ASP.Net MVC. First, I need to show a view to the user. I need to somehow pass the parent object key to the view. I also want to show the ParentName. I just fetch the Parent object from the database, create a new Child object, set its Parent property to the fetched parent object.
public ActionResult Create(int parentId) {
var parent = db.Parents.Find(parentId);
if (parent == null) {
return HttpNotFound();
}
var child = new Child() { Parent = parent};
return View(child);
}
After the user fills the form, the data is sent to the Create action using HTTP POST.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Child child)
{
if (ModelState.IsValid)
{
//db.Parents.Attach(child.Parent); //Added later
db.Children.Add(child);
db.SaveChanges();
return RedirectToAction("Index", new { parentId = child.Parent.Id });
}
}
Here I've hit my first problem. The child.Parent was not null and child.Parent.Id was correct, but EF trashed it and created a new empty parent (with a different key) in the database and linked the child to it. I've fixed this problem by attaching the child.Parent to the data context before adding the child (db.Parents.Attach(child.Parent)).
But then I was hit with another problem. At first, my model classes were wrong and didn't have the [Required] attributes thus creating nullable database table columns. I've added the attribute and the code stopped working. The code doesn't work because ModelState.IsValid is false which happens because child.Parent.Name of the passed child is null.
How can the problem of adding the child to the parent be solved? I'm interested in solution which:
Uses EF Code-First and ASP.Net MVC
Doesn't involve fetching the child.Parent from the database just to make the model validator happy.
Doesn't involve adding explicit foreign key (ParentId) to the model.
Is this possible?
I think attempting to attach a parent to the child is a little backwards. Typically you would attach a child to a parent. A new parent is being created most likely because you are not including an input element with the parent id in your child model. So when Child child is ModelBound coming into the POST, parent id is probably null. EF sees this and thinks you want to create a new parent too.
Also, since your parentId is part of your route, you don't need to specify it in your view model unless you are doing special things to your Html.BeginForm() in your view. Meaning, if you just use Html.BeginForm, it will post with the same URL values that you sent to the GET request.
Create Method
public ActionResult Create(int parentId) {
var parent = db.Parents.Find(parentId);
if (parent == null) {
return HttpNotFound();
}
return View(new Child());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(int parentId, Child child)
{
if (ModelState.IsValid)
{
//Probably not a bad idea to check again...just to be sure.
//Especially since we are attaching a child to the parent object anyways.
var parent = db.Parents.Find(parentId);
if (parent == null) {
return HttpNotFound();
}
parent.Childern.Add(child);
db.SaveChanges();
return RedirectToAction("Index", new { parentId = parentid });
}
}
Here is a link to a full answer to your question. The short answer is, that when you work with disconnected entities, EF will not respect already set entity Ids, and will mark the whole entity graph as new(e.g Added).
I personally don't like it, and simply overrride the SaveChanges(), though it works as below when you have an EnityBase base class with an int(or long) Id property (which I find extremely convenient)
public override int SaveChanges()
{
this.ChangeTracker.Entries()
.Where(x => x.Entity is EntityBase && x.State == EntityState.Added && ((EntityBase) x.Entity).Id > 0)
.ForEach(x => x.State = EntityState.Unchanged);
return base.SaveChanges();
}
Think this should work for Create method:
public ActionResult Create([Bind(Exclude="Parent")]Child child)
public class Menu
{
//public Menu()
//{
// {
// this.Templates = new HashSet<MenuTemplate>();
// }
//}
[Key]
public int MenuId { get; set; }
[Column("MenuCaption")]
[Display(Name = "Menu Caption")]
[StringLength(100)]
public string MenuCaption { get; set; }
[Display(Name = "Parent Menu")]
public int? ParentMenuId { get; set; }
public virtual Menu ParentMenu { get; set; }
[Display(Name = "Is Group")]
public bool IsGroup { get; set; }
[Display(Name = "Menu Order")]
public int MenuOrder { get; set; }
[Display(Name = "Visibility")]
public bool Visibility { get; set; }
[Display(Name = "Visibility Main Menu")]
public bool VisibilityMM { get; set; }
[Column("Controller")]
[Display(Name = "Controller")]
[StringLength(100)]
public string Controller { get; set; }
[Column("Action")]
[Display(Name = "Action")]
[StringLength(150)]
public string Action { get; set; }
[Display(Name = "Icon")]
public int? IconID { get; set; }
[ForeignKey("IconID")]
public virtual Icon Icon { get; set; }
public virtual ICollection<MenuTemplate> Templates { get; set; }
}
var dataList = db.Menus.Include(X => X.Icon).ToList();
var ViewModellist = dataList.Join(dataList,
a => a.ParentMenuId,
b => b.MenuId,
(_menu, _parent) => new MenuView
{
MenuId = _menu.MenuId,
Action = _menu.Action,
Controller = _menu.Controller,
IsGroup = _menu.IsGroup,
MenuCaption = _menu.MenuCaption,
MenuOrder = _menu.MenuOrder,
ParentMenuId = _menu.ParentMenuId,
Visibility = _menu.Visibility,
VisibilityMM = _menu.VisibilityMM,
PMenuName = _parent.MenuCaption
}).ToList();
if (PId == 0)
{
var hierarchyList = ViewModellist.Where(x => x.ParentMenuId == null).OrderBy(x => x.MenuOrder).
Select(x => new MenuView
{
MenuId = x.MenuId,
Action = x.Action,
Controller = x.Controller,
IsGroup = x.IsGroup,
MenuCaption = x.MenuCaption,
MenuOrder = x.MenuOrder,
ParentMenuId = x.ParentMenuId,
PMenuName = x.PMenuName,
Visibility = x.Visibility,
VisibilityMM = x.VisibilityMM,
ChildList = GetChildMenulist(x.MenuId, ViewModellist)
}).FirstOrDefault();
return View(hierarchyList);
}
else
{
var hierarchyList = ViewModellist.Where(x => x.MenuId == PId).OrderBy(x => x.MenuOrder).
Select(x => new MenuView
{
MenuId = x.MenuId,
Action = x.Action,
Controller = x.Controller,
IsGroup = x.IsGroup,
MenuCaption = x.MenuCaption,
MenuOrder = x.MenuOrder,
ParentMenuId = x.ParentMenuId,
PMenuName = x.PMenuName,
Visibility = x.Visibility,
VisibilityMM = x.VisibilityMM,
ChildList = GetChildMenulist(x.MenuId, ViewModellist)
}).FirstOrDefault();
return PartialView("_Index", hierarchyList);
}

CTP5 POCO Code-Only, how to add master-detail records to DB

I am trying EF CTP5 POCO, and converting from EF4 EntityModel to POCO Code Only Approach.
internal class InvoiceContext : DbContext
{
public DbSet<Invoice> Invoices {get; set;}
public DbSet<InvoiceLine> InvoiceLines {get; set;}
}
public class Invoice
{
public Guid InvoiceId { get; set; }
public virtual ICollection<InvoiceLine> Lines { get; set; }
}
public class InvoiceLine
{
public Guid InvoiceLineId { get; set; }
public Guid InvoiceId { get; set; }
public virtual Invoice Header { get; set; }
}
so converting my old code from EF4 Model to POCO, I am supposed to add new Invoice and Lines. I used to create the master class and add the details to the instance of the master class itself.
var inv = new Invoice();
inv.InvoiceId = Guid.NewGuid();
var db = new InvoiceContext();
db.Invoices.Add(inv);
var invLine = new InvoiceLine();
invLine = InvoiceLineId = Guid.NewGuid();
inv.Lines.Add(invLine);
inv.Lines <== is null at this point, so I cannot add a line to the instance of the invoice itself
am I supposed to create a collection of InvoiceLines and set it, or is there some other way to do this.
So in short, how I can add master/detail rows to my database using POCO Code Only (CTP5).
Here is one way to do this:
using (var db = new Ctp5Context())
{
var inv = new Invoice()
{
InvoiceId = Guid.NewGuid()
};
var invLine = new InvoiceLine()
{
InvoiceLineId = Guid.NewGuid(),
Header = inv
};
db.InvoiceLines.Add(invLine);
db.SaveChanges();
}
If you prefer to add an invoice object then:
using (var db = new Ctp5Context())
{
var invLine = new InvoiceLine()
{
InvoiceLineId = Guid.NewGuid()
};
var inv = new Invoice()
{
InvoiceId = Guid.NewGuid(),
Lines = new List<InvoiceLine>() { invLine}
};
db.Invoices.Add(inv);
db.SaveChanges();
}
While all these works, I recommend always initialize the collection of navigation property in the class constructors so that you don't need to do it in the client code every single time as well as there is no chance of hitting NullReferenceException in the runtime:
public class Invoice
{
public Invoice()
{
Lines = new List<InvoiceLine>();
}
public Guid InvoiceId { get; set; }
public virtual ICollection<InvoiceLine> Lines { get; set; }
}
...
using (var db = new Ctp5Context())
{
var invLine = new InvoiceLine()
{
InvoiceLineId = Guid.NewGuid()
};
var inv = new Invoice()
{
InvoiceId = Guid.NewGuid(),
};
inv.Lines.Add(invLine);
db.Invoices.Add(inv);
db.SaveChanges();
}

Resources