I am developping an application using EF6 with Fluent API and I have an issue to manage Many-To-Many relationship.
For some internal reasons the Join table has a specific format including 4 fields
- Left Id (FK)
- Right Id (FK)
- StartDate (dateTime)
- EndDate (datetime)
Deleting a link is in fact setting the EndDate as not null but i don't now how to configure it in EF6.
In an other hand when reading links the record with Not NULL EndDate shouldn't be considered.
Can you give me a solution ?
Thank you.
Join tables and EF
EF automates some things for you. For this, it uses convention-over-configuration. If you stick to the convention, you can skip on a whole lot of common configuration.
For example, if your entity has a property named Id, EF will inherently assume that this is the PK.
Similarly, if two entity types have nav props that refer to each other (and only one direct link between the two entities exists), then EF will automatically assume that these nav props are the two sides to a single many-to-many relationship. EF will make a join table in the database, but it will keep this hidden from you, and let you deal with the two entity types themselves.
For some internal reasons the Join table has a specific format including 4 fields - Left Id (FK) - Right Id (FK) - StartDate (dateTime) - EndDate (datetime)
Your join table no longer conforms to what the content of a conventional and automatically generated EF join table is. You are expecting a level of custom configurability that EF cannot provide based on blind convention, which means you have to explicitly configure this.
Secondly, the fact that you have these additional columns implies that you wish to use this data at some point (presumably to show the historical relations between two entities. Therefore, it doesn't make sense to rely on EF's automatic join tables as the join table and it content would be hidden from the application/developer.
It's possible that the second consideration is invalid for you, if you don't need the application to ever fetch the ended entries. But the overall point still stands.
The solution here is to make the join record an explicit entity of its own. In essence, you are not dealing with a many-to-many here, you are dealing with a specific entity (the join element) with two one-to-many relationships (one for each of the two entity types).
This enables you to achieve exactly what you want. Your expectation of what EF can automate for you simply doesn't apply in this case.
Soft delete
Deleting a link is in fact setting the EndDate as not null but i don't now how to configure it in EF6.
In general, this is known as "soft delete" behavior, albeit maybe slightly differently here. In a regular soft delete pattern, when an entry is deleted, the database secretly retains the entry but the application doesn't know that and doesn't see the entry again.
It's unclear if you intend for ended entries to still show up in the application, e.g. the relational history. If this is not the case, then your situation is exactly soft delete behavior.
This isn't something you configure on the model level, but rather something you override in your database's SaveChanges behavior. A simple example of how I implement a soft delete:
public override int SaveChanges()
{
// Get all entries of the change trackes (of a given type)
var entries = ChangeTracker.Entries<IAuditedEntity>().ToList();
// Filter the entries that are being deleted
foreach (var entry in entries.Where(entry.State == EntityState.Deleted))
{
// Change the entry so it instead updates the entry and does not delete it
entry.Entity.DeletedOn = DateTime.Now;
entry.State = EntityState.Modified;
}
return base.SaveChanges();
}
This allows you to prevent deletions to the entities that you want this to apply to, which is the safest way to implement a soft delete as this serves as a catch-all for database deletes coming from whichever consumer uses this db context.
The solution to your question is pretty much the same. Assuming you named your join entity (see previous chapter) JoinEntity:
public override int SaveChanges()
{
var entries = ChangeTracker.Entries<JoinEntity>().ToList();
// Filter the entries that are being deleted
foreach (var entry in entries.Where(entry.State == EntityState.Deleted))
{
// Change the entry so it instead updates the entry and does not delete it
entry.Entity.Ended = DateTime.Now;
entry.State = EntityState.Modified;
}
return base.SaveChanges();
}
Word of warning
Soft deletes tend to be a catch-all for all entities (or at least a significant chuck of your database). Therefore, it makes sense to catch this at the db context level as I did here.
However, if this entity is unique in that it is soft deleted, then this is more of a business logic implementation than it is a DAL-architecture. If you start writing many custom rules for different types of entities, the db context logic is going to get clutterend and it's not going to be nice to work with because you need to account for multiple possible operations happening during the SaveChanges.
Take note to not push what is supposed to be a business logic decision to the DAL. I can't draw this line for you, it depends on your context. But evaluate whether the db context is the best place to implement this behavior.
Can you give me a solution ?
If your linking table has extra columns you have to model it as an Entity, and the EndDate logic for navigation needs to be explicit. EF won't do any of that for you.
Related
EF 6.1.3.
I have a domain which contains many instances of a "Header/ Item" type pattern, where the a Header can have many Items (1 to many), and also has a "current" or "latest" item.
This is represented as follows:
Header
Guid Id
Guid CurrentItemId
Item CurrentItem
ICollection<Item> AllItems
Item
HeaderId
Id
The PK of the Items is always the HeaderID + ItemID. The reason being that, by far, the most common access pattern for items is to list all items related to a given header, and having HeaderID be the first part of the PK/clustered index means we get that data with clustered index seeks.
Our problem is that when we use the CurrentItem navigation property, it only ever uses the ItemID to do the lookup, which results in not so great query plans.
I assume this is because the conventions for EF us to use the CurrentItemId to look up the CurrentItem. My question is, is there a way for my to tell EF to always perform its joins for CurrentItem by mapping the Header.Id,Header.CurrentItemId -> Item.HeaderId,Item.Id?
I believe this is a slight different scenario than the one described here: composite key as foreign key
In my case, I have a one to one mapping not one top many, and there doesn't seem to be a WithforeignKey method available for that scenario.
We ended up not being able to get EF to generate the SQL the way we wanted - so we wrote a db command interceptor to dynamically find instances of this join and re-write the join to match our designed composite key.
We configure this as the DbContext level like so:
this.ModifyJoin<Item, Header>(
(i) => new Header() { CurrentItemId = i.Id }, //What to find
(i) => new Header() { CurerntItemId = i.Id, Id = i.HeaderId }); //What to replace with
This information is attached to the context instance itself, so when the command interceptor sees the overrides, it uses them to re-write the SQL.
This ends up working well for most scenarios, but there are some - such as when additional filtering is doing on the Item table as part of the LINQ statement, that the aliasing rules used by EF become too complex to follow without writing a full SQL parser.
For our use, this results in the ideal join about 90% of the time, which is good enough for us.
The code to do all this isn't difficult, but it's too big to put here. Add a comment if you want a copy and I'll put it up on GitHub.
Rather than deleting an entry from the database, I am planning on using a boolean column like isActive in every table and manage its true/false state.
Normally when you delete a record from the database,
referential integrity is maintained, which means you cannot delete it if before deleting its dependencies.
when you query a deleted record, it returns null
How can I achieve the same results in an automated way using Entity Framework? Because checking isActive field for every entity in every query manually seems too much work which will be error-prone. And the same holds true for marking the dependencies as isActive=false.
EDIT:
My purpose is not limited to point-in-time queries. Let me give an example. UserA posted a photo and UserB wrote a comment on it. Then UserB wanted to delete his account. But the comment has its poster FK pointing at UserB. So, rather than deleting UserB, I want to deactivate its account but keep the record in order not to break dependencies.
And I want to extend this logic to every table in the database. Is that wrong?
As kind of a side answer to this question, instead of querying all of the tables directly why not use Views and then query the views? You can place a filter in the view to only display the "IsActive = true" records, that way you don't have to worry about including it manually in every query (something you mention is error prone).
Because checking isActive field for every entity in every query manually seems too much work which will be error-prone
It is error prone. But you may not always want only the active records (admin page?). You may also not want to soft delete ALL records, as not everything makes sense to keep around (in my experience). You could use an Expression to help you out / wire it up for certain methods / repositories and build dynamic queries.
Expression<Func<MyModel, bool>> IsActive = x => x.IsActive;
And the same holds true for marking the dependencies as isActive=false
A base repository could handle the delete for all your repositories, which would set the status to false (where the BaseModel would have an IsActive property).
public int Delete<TEntity>(long id) where TEntity : BaseModel
{
using (var context = GetContext())
{
var dbEntity = context.Set<TEntity>().Find(id);
dbEntity.IsActive = false;
return context.SaveChanges();
}
}
There is an OSS tool called EF Filters that can achieve what you are looking for: https://github.com/jbogard/EntityFramework.Filters
It let's you set global filters like an IsActive field and would certainly work for queries.
I have a requirement that feels like it probably has a simpler solution with EF than what we're currently using.
Essentially, as an auditing requirement, for any entity that inherits from a given base class, I need to create both the entity's table itself, but also a table that's identical, but with 3 additional columns - a FK back to the original entity's table, a description (e.g. "Modified", "Added", "Deleted") and an XML column that will contain a serialized version of the state of the entity.
At present, we're manually adding the entities to create the audit tables (currently inherit from an AuditableEntity class and developers have to manually ensure that other fields match the original entity) and using migrations to add T-SQL triggers to the entity tables to update the data in the audit tables on any insert, update, delete.
I'd prefer if I could somehow get EF to automatically create/migrate the audit tables based on the entity tables without having to manually sync them, and likewise use an interceptor or something similar to update the audit table on insert/update/delete of an entity rather than using triggers. Does anyone know if this is possible, or done anything similar? In the past, the closest I've come is a single, common audit history table which wasn't too bad.
Disclaimer: I'm the owner of the project Entity Framework Plus
This project may answer to your requirement. You can access to all auditing information like entity name, action name, property name, original and current values, etc.
A lot of options is available like an AutoSave all information in the database.
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntityContext();
// ... ctx changes ...
var audit = new Audit();
audit.CreatedBy = "ZZZ Projects"; // Optional
ctx.SaveChanges(audit);
// Access to all auditing information
var entries = audit.Entries;
foreach(var entry in entries)
{
foreach(var property in entry.Properties)
{
}
}
Documentation: EF+ Audit
You could create one table with the columns:
Id
TableName
Action (Add, update, delete)
IdOfRecord
XmlSerialized
DateChanges (use datetime2)
Then override SaveChanges() to write each change to that one table.
No need to mess around with keeping Audit table schema up to date when running migrations etc
so I am using Entity Framework with a Sqlite database to track a series of notes.
There is a business requirement that states that all edits and deletes are non-destructive. i.e. they are flagged as deleted and just hang out in the database in case they need to be resurrected for auditing purposes.
I found an extremely helpful blog post which accomplishes this. I am wondering if I can go a step further and give admins the ability to view these deleted records in a simple manner, or even mark them as "un-deleted" again.
Example of the Note entity
ID (int)
Note (string)
CreatedBy (string)
EditedBy (string)
Last Modified (DateTime)
DateDeleted (DateTime)
I have conditional mapping that says "if DateDeleted != null, then include this record in a query"
Before I get too far with conditional mapping, I'm wondering whether or not it's possible to explicitly override the conditional mapping and say I don't care whether a record is marked as deleted or not... just get me all the records anyway
Idea #1
Screw conditional formatting. Just make a property that uses LINQ to get the objects I want. I'm leaning toward this idea but would still like answers to the question for the sake of learning.
I have a frustrating situation owing to this little quirk of EF. Here's a simple demo of the behavior. First the DB schema:
As you see, RestrictedProduct is a special case of product, which I'm intending to make a subclass of Product with some special code.
Now I import to an EF data model:
Oops! EF saw that RestrictedProduct had only 2 fields, both FKs, so it mapped it as a one-to-many relationship between Product and Restriction. So I go back to the database and add a Dummy field to RestrictedProduct, and now my EF model looks much better:
But that Dummy field is silly and pointless. Maybe I could delete it? I blow away the field from the DB table and the entity model, then refresh the model from the DB...
Oh, no! The Product-Restriction association is back, under a new name (RestrictedProduct1)! Plus, it won't compile:
Error 3034: Problem in mapping fragments starting at lines (x, y) :Two entities with possibly different keys are mapped to the same row. Ensure these two mapping fragments map both ends of the AssociationSet to the corresponding columns.
Is there any way to prevent this behavior, short of keeping the Dummy field on the RestrictedProduct table?
I just came across the same issue, and as an alternative to putting the dummy field in your RestrictedProduct table to force the creation of an entity you can also make your RestrictedProduct.RestrictionId field nullable and EF will then generate an entity for it. You can then modify it to use inheritance and any subsequent "Update model from database" will not cause undesired nav properties. Not really a nice solution but a work around.
Let's walk slowly into your problem.
1st thing you need to decide is if the restricted product is
really a special case of product or is it a possible extension
to each product.
From your original DB Scheme it seems that any product may have
a relation to a single restriction however a single restriction
can be shared among many products.. so this is a simple 1 to many
situation which means that restricted product is NOT a special case
of product! Restriction is an independent entity which has nothing
to do with product in a specific way.
Therefore EF is correct in the 1st importation of your scheme:
1. a product can have 0 or 1 restrictions.
2. a restriction is another entity which can be related to many products.
I do not see your problem.