I lose the foreign key when I update children in object with other foreign key - sqlite-net-extensions

I'am using SQLite.NET-PCL and SQLiteNetExtensions
OBJECTS:
public class Object1
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Object2> ListObject2 { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Object3> ListObject3 { get; set; }
}
public class Object2
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
[ForeignKey(typeof(Object1))]
public int object1_id { get; set; }
[ManyToOne]
public Object1 Object1 { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Object3> ListObject3 { get; set; }
}
public class Object3
{
[PrimaryKey, AutoIncrement]
public int id { get; set }
public string name {get; set;}
[ForeignKey(typeof(Object2))]
public int object2_id { get; set; }
[ManyToOne]
public Object2 Object2 { get; set; }
[ForeignKey(typeof(Object1))]
public int object1_id { get; set; }
[ManyToOne]
public Object1 Object1 { get; set; }
}
"Insert Object1 - this works"
connection.Insert(Object1);
"Insert Object2s and UpdateWithChildren Object1 - this works"
List<Object2> list_object2 = await API_query;
List<Object2> Object2List = new List<Object2>();
foreach (Object2 item in list_object2)
{
connection.Insert(item);
Object2List.Add(item);
}
Object1.ListObject2 = Object2List;
connection.UpdateWithChildren(Object1);
"Insert Object3s and UpdateWithChildren Object2 - this UpdateWithChildren works but too update Object2.object1_id to 0"
List<Object3> list_object3 = await API_query
List<Object3> Object3List = new List<Object3>();
foreach (Object3 item in list_object3)
{
connection.Insert(item);
Object3List.Add(item);
}
Object2.ListObject3 = Object3List;
connection.UpdateWithChildren(Object2);
When I update object2 with children, Object2.object1_id is 0, I lose the Object1_foreign_key in Object2.
Any idea? Whats is my problem? What's the error?

I think that your problem is that these lines:
Object1.ListObject2 = Object2List;
connection.UpdateWithChildren(Object1);
Are setting the foreign keys correctly, but then, you are calling this with an element of Object2List:
connection.UpdateWithChildren(Object2);
At this point, Object2 is null, because the inverse relationship hasn't been set, and therefore the foreign key is set to 0.
To solve it, if you don't plan to update the relationship from Object2, you can set the Object1 -> Object2 relationship to ReadOnly:
[ManyToOne(ReadOnly = true)]
public Object1 Object1 { get; set; }
Alternatively, you can set the inverse relationship manually:
foreach (Object2 item in list_object2)
{
connection.Insert(item);
Object2List.Add(item);
item.Object1 = Object1;
}

Before second UpdateWithChildren you must set Object1 to Object2.
Object2.Object1 = Object1;
and then you can do the sencond UpdateWithChildren.
Object2.ListObject3 = Object3List;
connection.UpdateWithChildren(Object2);
If you didn't set the relationship before update, you lose it.

Related

What is the right syntax for this joined EF Linq query

I am trying to get a query that returns everything properly formatted for my ViewModel so I do not have to manually copy everything over from my entity models. I have this Linq query that is giving me an error. :
var query = from i in context.Invoices
join l in context.LineItems on i.InvoiceID equals l.InvoiceID into il
where i.InvoiceID == id
select new InvoiceViewModel()
{
InvoiceID = i.InvoiceID,
CustomerID = i.CustomerID,
InvoiceNote = i.Note,
InvoiceDate = i.InvoiceDate,
Terms = i.Terms,
LineItems = il.ToList<LineItemViewModel>()
};
This is my ViewModel
public class InvoiceViewModel {
public int InvoiceID { get; set; }
public int CustomerID { get; set; }
public string InvoiceNote { get; set; }
public DateTime InvoiceDate { get; set; }
public string Terms { get; set; }
public virtual ICollection<LineItemViewModel> LineItems { get; set; }
}
public class LineItemViewModel {
public int LineItemID { get; set; }
public int InvoiceID { get; set; }
public int Quantity { get; set; }
public string Item { get; set; }
public decimal Amount { get; set; }
public string LineItemNote { get; set; }
}
The error I am getting is (the red squigly is under the il in LineItems = il.ToList())
'IEnumerable<LineItem>' does not contain a definition for 'ToList' and the best extension method overload 'Enumerable.ToList<LineItemViewModel>(IEnumerable<LineItemViewModel>)' requires a receiver of type 'IEnumerable<LineItemViewModel>'
Which I (sorta, kinda, a little) understand. So what is the proper syntax for this?
You need to explicitly initialize your LineItemViewModel instances from the LineItem entities. You might be better off writing this as a correlated subquery rather than a join:
var query =
from i in context.Invoices
where i.InvoiceID == id
select new InvoiceViewModel()
{
InvoiceID = i.InvoiceID,
CustomerID = i.CustomerID,
InvoiceNote = i.Note,
InvoiceDate = i.InvoiceDate,
Terms = i.Terms,
LineItems =
(
from li in context.LineItems
where li.InvoiceID == i.InvoiceID
select new LineItemViewModel
{
LineItemID = li.LineItemID,
InvoiceID = li.InvoiceID,
Quantity = li.Quantity,
Item = li.Item,
Amount = li.Amount,
LineItemNote = li.LineItemNote,
}
).ToList()
};

C# Creating Entity Object in one line

I have a Entity class
public class SiteMenu
{
public int MenuID { get; set; }
public string MenuName { get; set; }
public string NavURL { get; set; }
public int ParentMenuID { get; set; }
}
I want to create a collection of SiteMenu by the following code:
List<SiteMenu> all = new List<SiteMenu>();
To create and add an SiteMenu object to the List is this the correct format/syntax
all.Add(new SiteMenu(MenuID=1, MenuName="Test1", NavURL="http://test", ParentMenuID=0));
I like to use one line and keep adding to the list.
THanks.
You can use the collection initializer syntax to create the list and add the initial elements in one line:
var all = new List<SiteMenu> { new SiteMenu { MenuID = 1, MenuName = "Test1", NavURL = "http://test", ParentMenuID = 0} };
List<SiteMenu> list = new List<SiteMenu> {
all.Add(new SiteMenu(MenuID=1, MenuName="Test1", NavURL="http://test", ParentMenuID=0));
};
You should use a constructor.
public class SiteMenu
{
public int MenuID { get; set; }
public string MenuName { get; set; }
public string NavURL { get; set; }
public int ParentMenuID { get; set; }
public SiteMenu(int menuID, string menuName, string navURL, int parentMenuID) {
MenuID = menuID;
MenuName = menuName;
NavURL = navURL;
ParentMenuID = parentMenuID;
}
}
Then you can do:
List<SiteMenu> all = new List<SiteMenu>();
all.Add(new SiteMenu(1, "Test1", "http://test", 0));

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

Entity framework Entities in 'xxContext.x' participate in the 'x_y' relationship error

Before me, one developer used Entity Framework code first. I am not good at EF code first, so when I try to insert data, my code gives this error:
Entities in 'TourismContext.HotelOrders' participate in the 'HotelOrder_Order' relationship. 0 related 'HotelOrder_Order_Target' were found. 1 'HotelOrder_Order_Target' is expected.
This is my insert code:
var hotelOrdersInsert = new Data.Entities.HotelOrder
{
OrderId = odr.ID // this gives 7
HotelID = 13,
StartAt = DateTime.Now, // arrivalDate,
EndAt = DateTime.Now, // departureDate,
PaymentTypeID = 1,
PaymentStatusID = 1,
PaymentIdentifier = "a",
TotalRate = Convert.ToDecimal(total),
CurrencyID = 1
};
db.HotelOrders.Add(hotelOrdersInsert);
db.SaveChanges();
And this is my HotelOrder class:
public class HotelOrder
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public int HotelID { get; set; }
public int OrderId { get; set; }
// other properties
public virtual Order Order { get; set; }
public virtual Hotel Hotel { get; set; }
}
This is my Order class:
public class Order
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public virtual HotelOrder HotelOrder { get; set; }
}
Where can I find the relationship between the order and hotel order models?
You must write this relation as follows:
public class HotelOrder
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public int HotelID { get; set; }
[ForeignKey("Order")]
public int OrderId { get; set; }
public Order Order { get; set; }
[ForeignKey("Hotel")]
public int HotelId { get; set; }
public Hotel Hotel { get; set; }
}
public class Order
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public List<HotelOrder> HotelOrders { get; set; }
}
The problem is the relationship between HotelOrder and Order. It's most likely that you have a 1 to 1 relation. Meaning, Every HotelOrder must have an Order.
The relation is probably defined in the so called fluent API. Check the OnModelCreating(DbModelBuilder) function override of your class derived from DbContext. It probably states something like this:
modelBuilder.Entity<HotelOrder>().HasRequired(hotel_order => hotel_order.Order).WithRequiredPrincipal();
This tells EntityFramework that every HotelOrder must have an Order.
Alternatively, the relationship could be defined as DataAnnotations. Like Elvin Mammadov explained in his answer.
At this point the solution becomes obvious. You need to add an instance of Order to your HotelOrder instance before adding it to db.HotelOrders.

Populating a linking table in a many-to-many relationship

I am trying to get to grips with EF4 CTP5. I have two classes that have a many-to-many relationship: Member and MemberGroup. CTP5 Code First generated two tables (Members and MemberGroups) and also a third named MemberGroupMembers that has two columns (MemberGroupId and MemberId) So far everything is as I was expecting it to be. I have seeded the database with some Members and MemberGroups. The problem is that I cannot find how to assign one or more MemberGroups to a Member, which would result in inserting a row into the MemberGroupMembers table for each MemberGroup that the Member is assigned to.
public class Member
{
public int Id { get; set; }
public Guid SecureId { get; set; }
public DateTime JoinedOn { get; set; }
public virtual ICollection<MemberGroup> MemberGroups { get; set; }
}
public class MemberGroup
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedOn { get; set; }
public virtual ICollection<Member> Members { get; set; }
}
public class CTP5testContext : DbContext
{
public CTP5testContext() : base("CTP5test") { }
public DbSet<Member> Members { get; set; }
public DbSet<MemberGroup> MemberGroups { get; set; }
}
public class CTP5testContextInitializer : DropCreateDatabaseIfModelChanges<CTP5testContext>
{
protected override void Seed(CTP5testContext context)
{
new List<Member>
{
new Member
{
Id = 1,
SecureId = Guid.NewGuid(),
JoinedOn = DateTime.Now
}
,
new Member
{
Id = 2,
SecureId = Guid.NewGuid(),
JoinedOn = DateTime.Now
}
}.ForEach(m => context.Members.Add(m));
var memberGroup = new MemberGroup()
{
Id = 1,
Name = "MemberGroup 1",
CreatedOn = DateTime.Now
};
context.MemberGroups.Add(memberGroup);
// How can I assign Member 1 to MemberGroup 1 ?????
context.SaveChanges();
}
}
I hope that it is clear what I am trying to do here and that someone can give me an example of how to achieve this.
Regards,
Erwin
You must use collections defined in your POCO classes. So if you want to assign member1 to memberGroup you will simply call:
memberGroup.Members.Add(member1);

Resources