SQLite-Net Extensions: removing detail records in Master/Detail scenario - sqlite-net

What is the proper way to remove dentail records ?
I have Stock entity (master) in OneToMany relation with Valuation entity (detail).
I retrive one Stock entity with its related Valuations. Then I remove an entity from Valuations list and call UpdateWithChildrenAsync.
The result of this update is that in the removed valuation the foreign key is NULL instead I want this record to be physically removed.
namespace TestSQLitePCL
{
public class Stock
{
[PrimaryKey, AutoIncrement]
public int? Id { get; set; }
public string Symbol { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Valuation> Valuations { get; set; }
}
}
namespace TestSQLitePCL
{
public class Valuation
{
[PrimaryKey, AutoIncrement]
public int? Id { get; set; }
[ForeignKey(typeof(Stock))]
public int StockId { get; set; }
public DateTime Time { get; set; }
public decimal Price { get; set; }
}
}
...
stock.Valuations.RemoveAt(0);
...
await _connection.UpdateWithChildrenAsync(stock);

Removing an element from a relationship is not supposed to delete that object from the database.
You can either delete them manually when removing them from the relationship (making sure nobody else is referencing that record):
// Remove valuation from stock list
stock.Valuations.Remove(valuation);
await valuation.DeleteAsync();
Or delete orphaned objects on a regular basis to keep the database clean.
// Delete orphaned valuations
conn.Execute("DELETE FROM Valuation WHERE stock_id = 0");

Related

I am not able to have one-to-many relationship in Entity Framework

I am following examples from the internet but it's not working. The database is getting created successfully, there is no error.
What I want to have is: one user can have multiple transactions, and a transaction can have references to two users. One of those is the user who did the transaction, the second is the user to whom transaction is done.
But what is happening is I am getting three foreign keys in the Users table, but none in the Transactions table.
See image below:
My classes
public class User
{
public int userId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CardNumber { get; set; }
public string Password { get; set; }
public int Balance { get; set; }
public string UserType { get; set; }
public string ProfileUrl { get; set; }
public IList<Transaction> Transactions { get; set; }
}
public class Transaction
{
public Transaction()
{
this.TranscationDateTime = DateTime.UtcNow;
}
public int TransactionId { get; set; }
public int Amount { get; set; }
public User FromUser { get; set; }
public User ToUser { get; set; }
public DateTime TranscationDateTime { get; set; }
}
public class DB: DbContext
{
public DB() : base("name=DBConnection")
{ }
public DbSet<User> Users { get; set; }
public DbSet<Transaction> Transactions { get; set; }
}
You need to make some modification to your code.
First of all, each navigation property needs to be marked as virtual, in order to allow Entity Framework to lazy loading, unless you want always eager load all your navigations (could be a choice, is up to you).
After that, each of your user has outgoing and incoming transactions, so for the User class:
public class User
{
public int userId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CardNumber { get; set; }
public string Password { get; set; }
public int Balance { get; set; }
public string UserType { get; set; }
public string ProfileUrl { get; set; }
public virtual IList<Transaction> IncomingTransactions { get; set; }
public virtual IList<Transaction> OutgoingTransactions { get; set; }
}
Let's make virtual navigation properties of Transaction class
public class Transaction
{
public Transaction()
{
this.TranscationDateTime = DateTime.UtcNow;
}
public int TransactionId { get; set; }
public int Amount { get; set; }
public virtual User FromUser { get; set; }
public virtual User ToUser { get; set; }
public DateTime TranscationDateTime { get; set; }
}
Last, but not least, let's inform your DbContext of how things are supposed to go:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString) { }
public DbSet<User> Users { get; set; }
public DbSet<Transaction> Transactions { get; set; }
protected override void OnModelCreating(DbModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Transaction>()
.HasRequired<User>(t => t.FromUser)
.WithMany(u => u.OutgoingTransactions).WillCascadeOnDelete(false);
builder.Entity<Transaction>()
.HasRequired<User>(t => t.ToUser)
.WithMany(u => u.IncomingTransactions).WillCascadeOnDelete(false);
}
}
This should be enough for EF autodiscovery to make the right assumptions and create right database structure, that would be two FKs in Transaction table each of them to the primary key of Users table.
And voilà:
This happens because EF doesn't know that one of the FromUser and ToUser fields is supposed to match the collection Transactions - since you are not following the naming conventions. You have several options on how to resolve this situation:
If you only want to match Transactions collection with either FromUser or ToUser but not both, you can use [ForeignKey] and/or [InverseProperty] attributes to setup the database relation explicitly
If you want to use BOTH of them, then you would need to specify two collections in the User class - e.g. TransactionsFromUser and TransactionsToUser. You might still need to setup the relationships explicitly through the attributes though
What i want to have is one user can have multiple transaction but a transaction can have reference to two user.
Your current database model reflects this accuratly. I will explain why in the rest of my answer.
The User table can not hold the foreign keys to the Transactions table because one User can be associated with multiple Transactions. If the FK column was on the User table, it would need to hold more than one TransactionId.
Instead, the references to the Users are stored in the Transaction table. So every Transaction only has to store a single UserId per FK column.
Transaction.User_userId tells us that this Transaction is in the IList<Transaction> Transactions of the User with the stored User_userId.
To get this list of Transactions for a certain user, we query
SELECT *
FROM Transactions t
INNER JOIN Users u on t.User_userId = u.userId
WHERE u.userId = {theUserId}
The additional FKs ToUser_userId and FromUser_userId exists because they might reference different Users.
If the semantics of the IList<Transaction> Transactions is actually "all transactions that originated from this User", you could configure the ModelBuilder to use the FromUser_userId FK for this collection instead of creating the third FK User_userId. See the answer of Sergey.

SQLite-Net Extensions: How to get rid of relationship records?

Let's say I have a many-to-many relationship like in the official sample:
public class Student
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[ManyToMany(typeof(StudentSubject))]
public List<Student> Students { get; set; }
}
public class Subject
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Description { get; set; }
[ManyToMany(typeof(StudentSubject))]
public List<Subject> Subjects { get; set; }
}
public class StudentSubject
{
[ForeignKey(typeof(Student))]
public int StudentId { get; set; }
[ForeignKey(typeof(Subject))]
public int SubjectId { get; set; }
}
If I am now deleting a Student, the relationship record represented by the intermediate object does not get deleted, too. (And if I am enabling cascading deletion, the Subject gets deleted – what should not happen.)
Currently I am cleaning up manually by deleting the record with a custom query, but is there a better way with sqlite-net?
You can set to null or an empty list the Subjects property and call UpdateWithChildren on the student object.
student.Subjects = null;
conn.UpdateWithChildren(student);
It will update the student object and its relationships, deleting all the records for that student, which is equivalent to:
conn.Execute("DELETE FROM StudentSubject WHERE StudentId = ?", studentId);
The drawback is that you have one more 'update' statement executing in the database if you let SQLite-Net Extensions handle the relationship.

Why is cascade delete not on by default for this relationship?

I have a 1 to many relationship between LabelLineItem and DespatchPart.
I can't understand why cascade delete is off for this relationship.
There is no relationship defined in the context using the fluent API.
There is no LabelLineItems navigation collection in DespatchPart, so there is no reference back to LabelLineItem.
public class LabelLineItem
{
public int Id { get; set; }
public int DespatchPartId { get; set; }
public int LabelConfigId { get; set; }
public string Content { get; set; }
// Navigation
public virtual LabelConfig LabelConfig { get; set; }
public virtual DespatchPart DespatchPart { get; set; }
}
public class DespatchPart
{
public int Id { get; set; }
public int DespatchId { get; set; }
// Navigation
public virtual Despatch Despatch { get; set; }
//...
}
It's my understanding that one-to-many relationships default to cascade delete on. As demonstrated in the code sample above.
Whereas zero-or-one-to-many relationships default to cascade delete off as would be the case if either:
- DespatchPartId was declared as int?,
- The fluent API declared the relationship as optional i.e. DespatchPart.HasMany(p => p.LabelLineItems).WithOptional(i => i.DespatchPart).
But neither of these are the case which is why I'm confused.
FYI -
I'm certain the cascade is off, because when I tested the cascade delete by removing a despatch part record (in SQLManagementStudio), I received an attempted FK violation in the LableLineItem table as I tried to remove a referenced DespatchPart record. This wouldn't have occurred if it the delete had cascaded to the LabelLineItem table.

Entity Framework - Add an item to a list more than once

I am creating an Entity Framwork Code First app and am running into a problem when trying to add an entity to a list more than once.
I have the following two classes, which reference each other for a many-to-many relationship.
public class Order
{
public virtual List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public virtual List<Order> Orders{ get; set; }
}
This creates a the following three tables in my database:
Orders
- OrderId (PK, int)
.
OrderItems
- OrderItemId (PK, int)
.
OrderOrderItems
- Order_OrderId (PK,FK,int)
- OrderItem_OrderItemId (PK,FK,int)
In code, I wish to do the following:
private void AddOrderItemsTest
{
OrderItem orderItem = GetOrderItem(); // gets an existing order item from the DB
var order = new Order();
order.OrderItems.Add(orderItem);
order.OrderItems.Add(orderItem); // add the order item to the list a second time
context.Orders.Add(order);
}
When this gets persisted to the database, only a single orderItem entity is added to the list. We see in the table OrderOrderItems that OrderItem_OrderItemId is a PK and therefore must be unique. This means that EF has designed the tables in a way that won't allow more than one orderItem of the same type to be added to the list more than once.
Is there a Data Annotation that I can add to tell EF to allow me to add more than one item of the same type to the list?
I believe that it is not correct way of handling item count (of same item) in your order. Do you really want 10000s duplicated entries loaded into your OrderItems collection? I guess not.
You will need different primary key on OrderOrderItems table, hence suggest to introduce new entity that will contain OrderItem amount per Order:
public class OrderItemDetails
{
public int OrderItemDetailsId { get; set; }
public int OrderId { get; set; }
public int OrderItemId { get; set; }
public int Amount{ get; set; }
public virtual Order Order { get; set; }
public virtual OrderItem OrderItem { get; set; }
}
public class Order
{
public virtual List<OrderItemDetails> OrderItemDetails { get; set; }
}
public class OrderItem
{
public virtual List<OrderItemDetails> OrderItemDetails { get; set; }
}
And if you not happy with introducing Amount and still want to have duplicated entries per each item instance that will be absolutely fine because primary key of your many to many relation will be not combination of OrderId and OrderItemId but OrderItemDetailsId.

Entity Framework asp.net MVC foreign key

I am trying to code the following in code first... since I am just begining I am not able to.. please help.. thanks in advance
1. Student: Student will have student ID, First Name, Last Name
Student should belong to one class and one section(basically one to one relationship with each entity)
2. Classes: Class will have ClassId, Name
Class should have collection of students and collection of sections(basically many to many relationship with each entity)
3. Sections: Section will have SectionID, Name
Section should belong to one class and should have collection of students(basically one to one relation with class and one to many relation with Students)
Below is the code for the same
Students.cs
public class Students
{
public int StudentsId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public decimal Grade { get; set; }
public int ClassesId { get; set; }
public Classes Classes { get; set; }
public int SectionsId { get; set; }
public Sections Sections { get; set; }
}
Classes.cs
public class Classes
{
public int ClassesId { get; set; }
public string Name { get; set; }
public ICollection<Sections> Sections { get; set; }
}
Sections.cs
public class Sections
{
public int SectionsId { get; set; }
public string Name { get; set; }
public int ClassesId { get; set; }
public Classes Classes { get; set; }
public ICollection<Students> Students { get; set; }
}
If I do this I get error saying:
Introducing FOREIGN KEY constraint
'FK_dbo.Sections_dbo.Classes_ClassesId' on table 'Sections' may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I know I can get rid of this error using fluent APIs and telling not to cascade on delete, but I don't want to do that. Is there any other solution to this?? Please help
With your current model, no, there is no other way than disabling casdading delete for some of the relationships.
All your relationships are required, that means that if a class is deleted you delete the sections and the students of that class (Classes has a not exposed collection of students due to the required navigation property Classes in Students). But if the sections are deleted the students of that sections are deleted as well - and that's the second delete path to Students.
I don't know the exact meaning of your model but to me it sounds strange to delete all students of a class if the class gets deleted. Does a student always must have a class or couldn't he temporarily be without class assignment (and section assignment as well)? Maybe the student has a holiday semester for half a year and doesn't participate in any class?
In that case you could make the relationships of Students optional. Just declare the foreign key properties as nullable:
public class Students
{
//...
public int? ClassesId { get; set; }
public Classes Classes { get; set; }
public int? SectionsId { get; set; }
public Sections Sections { get; set; }
}
This would fix your problem of multiple cascading delete paths in the Students class because by default optional relationships don't have cascading delete enabled. The relationship between Classes and Sections is still required, so deleting a class will delete all sections belonging to the class as well, but it won't delete the students anymore.