I'm trying to implement an Undo / Redo feature based on entity framework with POCO entities. Therefor after each change that I want to track I call the ChangeTracker for any modification to entities an I call the ObjectStateManger for changes to relationships (i.e. navigation properties). I store all the changes with the current and previous values to be able to go back and forth in the history.
My problem now is that for entities with a navigation property and a corresponding foreign key these two values are not properly synced if the referenced entity was newly added to the DbContext.
To clarify: Say I have these classes:
public class EntityFilter
{
[Key]
public Guid ID { get; set; }
public virtual ICollection<EntityConnection> IsSource { get; set; }
public virtual ICollection<EntityConnection> IsSink { get; set; }
//other stuff
}
public class EntityConnection
{
[Key]
public Guid SinkFilterID { get; set; }
[ForeignKey("SinkFilterID")]
public virtual EntityFilter Sink { get; set; }
public Guid SourceFilterID { get; set; }
[ForeignKey("SourceFilterID")]
public virtual EntityFilter Source { get; set; }
//more stuff
}
EntityConnection is basically a many-to-many relationship between filters, but it actually contains more fields which is why I cannot get rid of it. Also I want to be as general as possible and not depend on our actual data model.
The problem arises if I add a new filter and then connect it to an existing filter (could also be a new one). Undoing the connection is still ok, but when I try to redo my program will crash. I can see in the restored connection that the foreign key SinkFilterID has the correct value but Sink is null (the same might happen for source, depending on the direction of the connection). Manually calling DetectChanges makes no difference. Adding a connection between two existing filters (i.e. they are already stored in the db before) is no problem.
The detected changes for a new connection of this type only contain entity changes from the ChangeTracker and no relationship changes from the ObjectStateManger. I guess this is because the relationship is already handled by the foreign key, which is included in the properties from PreviousValues.
I've read that entities in the EntityState.Added state get temporary keys and that change tracking for them is not fully supported. Can I get this to work somehow?
I've tried to check with the MetadataWorkspace if my updated entities have a foreign key and a corresponding navigation property and in that case update it manually via reflection, but I'm not sure what data I actually have to check.
Is there a way to keep foreign keys and navigation properties to added entities in sync? Or do you have any suggestions what I might try?
Thank you very much.
Here is what I ended up with:
I keep a separate list of all the added entities. Then when I have to restore a navigation property that is backed by a foreign key I search that list and manually set the navigation property. The hardest part was to figure out how to check in the data model if this fixup was at all needed and to find the name of the corresponding property.
The overall system still has some flaws for maximum generality but it works quite well for what we need.
Related
I am trying to add a foreign key to my database table using EF code-first migrations, but when I run add-migration, the Up() and Down() methods in the generated migration are empty.
The base table to which the foreign key should link is Reservation and the table to which I am trying to add the key is Batch.
Reservation model class:
public class Reservation
{
[Key]
public int ReservationId { get; set; }
public virtual ICollection<Batch> Batches { get; set; }
...
}
Batch model class:
public class Batch
{
[Key]
public int BatchId { get; set; }
public int ReservationId { get; set; }
[ForeignKey("ReservationId")]
public Reservation Reservation { get; set; }
...
}
The Reservation attribute was previously called TempReservation and did not have a [ForeignKey] annotation which is why the foreign key did not get created in the first place.
I tried fixing it by adding the [ForeignKey] annotation and changing the property name to Reservation like in the above code snippet shows, but to no avail.
The migration always ignores my changes, giving me empty Up() and Down().
I have other model classes that follow the same "structure" and they all have foreign keys without any issues. The only difference is that I am adding this FK after the Batch table was already created.
If you started from scratch, your code model setup should work, so your model looks ok. The problem is that by not using the [ForeignKey] attribute the first time, your database schema now likely has an additional Shadow Property column called Reservation_ReservationId. However as this field isn't an active declaration in your model, it's hard to target directly as your convention configuration can greatly affect this, for instance, if you have configured the appropriate convention, the shadow property
may have already been named ReservationId.
tried fixing it by adding the [ForeignKey] annotation and changing the property name to Reservation like in the above code snippet shows, but to no avail.
As suggested above, the database may have already generated the correct foreign keys in the database, based on public virtual ICollection<Batch> Batches { get; set; }.
Something to remember:
Whenever making changes to the model that rename fields and or modify indexes or relationships on existing fields, you must perform these operations in separate migrations or the migration generation logic can't understand you will have to apply the change manually.
If you do these in one hit, you will almost always have to manually edit the migration file in some way.
after applying your manually modified migration logic to the database, if you now run the add-migration command it should generate an empty migration. If this is the case then you can generally move on.
Instead you could have followed this process:
Rename the navigation property, and any other fields.
Add-Migration... review the generated output
'Update-Database`...
Add the ForeignKey attribute
Add-Migration... review the output, if this is empty, check that the foreign key is not already correctly defined in the database
'Update-Database`... if necessary
Code First Migrations is not perfect out of the box
it is still a very usable tool but you need to review the generated code and add to is as necessary. It is not hard to extend it to support default value declarations, or any SQL DDL management queries when or if you need to.
I have read in many places that one should (usually) have the foreign key id in an entity because it is a database detail, and in fact, EF manages it pretty well by adding an EntityName_Id column to the corresponding table.
public class Order
{
public decimal Total { get; set; }
public int? InvoiceId { get; set; }
public Invoice Invoice { get; set; }
}
public class Invoice
{
public ICollection<Order> Orders { get; set; }
}
In this example, an Order has an optional relationship with Invoice. When showing info about Order, I am interested in knowing whether or not it has an Invoice.
The problem is that if I don't have the ´InvoiceId´ property in my entities, I have to check the whole related entity just to check if it exists, whereas having that property in my entity would allow me to check it for null, which is "free" in the sense that the entity is already loaded.
Is that the only use of having foreign key Id properties in entities? Am I missing something here?
You can check entity framework documentation to see answer for your question:
https://msdn.microsoft.com/en-US/data/jj713564.aspx
It says:
With foreign key associations, you can use either method to change,
create, or modify relationships. With independent associations, you
cannot use the foreign key property.
Use cases provided in article:
Update foreign key relationship -- this can cause issues if you update navigation property and foreign key at the same time and reference different objects.
By assigning a new value to a foreign key property, as in the
following example.
course.DepartmentID = newCourse.DepartmentID;
Remove foreign key relationship. This only works if there is no associated object with FK property, that is in state 'Added'.
The following code removes a relationship by setting the foreign key
to null. Note, that the foreign key property must be nullable.
course.DepartmentID = null;
Apart from these cases you can use it to check if your reference is not null/null without really lazy-loading related entity like you said:
var isNull = course.DepartmentID == null;
I believe it's pretty much it. From my point of view, benefits from using FK property are too small to rely on them.
-----edit-----
I have my code working now. I tried making the relationship one-to-many and it works now by using:
newInspection.Sites.Add(newSite)
The Unique Key constraint is still present in the database, so I'm not completely comfortable with this as being the "answer" since it's more of a work around. I have no idea how to make the one-to-one work, as every time I have tried it (with other tables too for testing purposes) it always gives me this error.
----/edit-----
I am using EF 6 Code First for an application I am developing. I used the EF 6.1 tools to reverse engineer the code first model. I am running into a problem with a 1-to-1 relationship when trying to add new items to the database.
Here is the object that's causing a problem:
[Table("childTable")]
public partial class Site
{
[Key]
public int siteID{ get; set; }
public int inspectionID { get; set; }
...
public virtual Inspection inspection { get; set; }
}
The main "inspection" class has a 1-to-1 relationship with the site, and the class is organized like this:
[Table("someTable")]
public partial class Inspection
{
[Key]
public int inspectionID { get; set; }
...
public virtual Site site { get; set; }
}
The context defines this:
modelBuilder.Entity<Inspection>()
.HasOptional(e => e.site)
.WithRequired(e => e.inspection);
I am creating a new "Site" object and setting everything in it except the "siteID" and "inspectionID" properies -- the primary and foreign key respectively. I am adding it to a new "Inspection" object as a navigation property, then adding the "Inspection" to the context and trying to save:
Inspection newInspection = new Inspection
{
...
site = newSite; // Constructed earlier, no explicit ID. ID = 0 if checked
};
using (var db = new Context())
{
db.Inspections.Add(newInspection);
db.SaveChanges();
}
When I call the SaveChanges() I get the "Cannot insert explicit value for identity column in table '--------' when IDENTITY_INSERT is set to OFF."
I cannot understand why it is doing this, so I used db.Database.Log to check out the SQL being generated, and it is trying to pass an ID for the siteID after the "Inspection" insert. That doesn't make sense to me, because if I check the siteID before calling SaveChanges() the ID is 0, as a "new" one should be. However, it is actually trying to insert a number, like 16. I am unsure where it is getting the number. I thought when adding a new item to the context (i.e. db.Inspections.Add()) that it flagged everything in there as new and treated it as such during insert.
I have no idea why it is trying to insert the ID, but it appears to do this for any navigation property that is 1-to-1. That requires setting the navigation property explicitly, as opposed to using the .Add() method. 1-to-many have always worked fine for me (and do in this Context).
Does anyone know why my DBContext is trying to pass the ID?
Entities that have a 1 to 1 relationship should have the same value in the primary key. That means that the primary key in the dependent should also be a foreign key to the principal, and should not be an identity field.
You should change your tables in line with that requirement so that EF can insert the Inspection object then take its new ID and insert that value into the Sites table as the foreign key/primary key.
EF will add the foreign key constraint when you migrate back to a one-to-one but you will need to add sql to the migration to remove the Identity because EF can't do that (yet)
References:
What does principal end of an association means in 1:1 relationship in Entity framework
Do I define a relationship between two entities on the dependent or the principal?
Configuring a Required-to-Optional Relationship (One-to-Zero-or-One)
After much research it seems Entity Framework 4.4 doesn't support Unique constraints. Yes it can & should be done at the database, but I'd much prefer it happen in model validation so the warning to user is prettier.
It would be ideal for programmers to be able to decorate the property with a [Unique] attribute and it should be possible somehow, eg.:
public class UserGroup
{
public int UserGroupID { get; set; }
[Required]
[Unique]
public string Name { get; set; }
[Required]
public string Description { get; set; }
}
Options I'm considering:
1) Have the repository do some extra work at SaveChanges(), scan for [Unique] attributes on modified entities and hit the database to check uniqueness. Downside: this validation only happens when we call SaveChanges(), ideally it can happen earlier (eg. when the UI control validates).
2) Give the UserGroup model a lazy-loaded navigation property to AllUserGroups:
public virtual ICollection<UserGroup> AllUserGroups { get; set; }
Then program UniqueAttribute{} to scan this property and check the values etc.
QUESTION: how can I configure Entity Framework (code first) to load all records into this "navigation property"? It only seems to want a navigation property with foreign keys etc while I just want them all.
3) Manually code this validation in the UI - terrible & absolute last resort.
QUESTION: are there any better options for enforcing a Unique Constraint via validation at the model level?
Regards,
-Brendan
I found this, seems to be the one you're looking for:
UniqueAttribute that validates a unique field against its fellow rows in the database (inherits DataAnnotations.ValidationAttribute)
I have a Branch table that contains:
company_id, is_deleted, branch_id, branch_name, branch_code
company_id - used in order to determine which company owns the branch.
is_deleted - rows where is_deleted=true are logically deleted and I don't want to return them in my queries.
I have to map thos fields to class Branch. Class Branch have the following members:
BranchId, BranchName, BranchCode
Should I add IsDeleted member in order to map the is_deleted field? Can I filter rows with is_deleted=true if I will not map this field?
Should I add CompanyId member in order to map the company_id field? I have many tables with company_id field since it decide whice company own the row. Can I prevent adding CompanyId member when mapping those tables? When inserting, I need to supply CompanyId - I really prefer to supply it externaly and not from the Branch object.
So now you have a concrete example so we can continue in discussion from your previous question where I described some basic information about mapping to existing objects.
Should I add IsDeleted member in order
to map the is_deleted field? Can I
filter rows with is_deleted=true if I
will not map this field?
It is possible. It's called conditional mapping where your is_delete column will be used as a filter in the mapping. It has pros and cons:
Pros:
The filter is applied every time you query the entity set including a lazy loading and an eager loading. You will never get an entity with is_deleted = 1.
Cons:
You can't map is_deleted as a property in the entity. This is one global disadvantage for all columns used to support conditional mapping, table per hierarchy inheritance and independent associations - they can't be exposed as properties. So how would you soft delete your entity if you don't have the column exposed and you can't set it in the application? The only solution for this is stored procedure mapped to delete operation for your entity - btw. it is probably the best solution if you want to do soft / logical deletes because otherwise accidental call of DeleteObject on the context or a set will do hard delete in the database.
You can't map multiple conditional entities to the same table. It means you can't have conditionally mapped both undeleted and deleted entity. This can be handled by table per hierarchy inheritance.
Btw. as I know this is not available in DbContext API (EF 4.1).
Should I add CompanyId member in order
to map the company_id field? I have
many tables with company_id field
since it decide which company own the
row. Can I prevent adding CompanyId
member when mapping those tables? When
inserting, I need to supply CompanyId
- I really prefer to supply it externaly and not from the Branch
object.
Do you have a relation between the company table and the branch table in your database? In such case your Branch entity must use either independent or foreign key association with the Company entity. Association by default creates navigation property on both related entities so your Company entity will have collection of related Branches and your Branch will have a reference to the Company it belongs to. Navigation properties are the main way how to create relations in the object world. So if you want the Branch to belong to any Company you will either assign the Company to the property in the Branch or add the Branch to the collection of branches in the Company. That is the theory - it is little bit more complex with EF when using detached objects.
To avoid some problems EFv4 introduced foreign key association where dependent entity doesn't have only navigation property but also foreign key property (your country_id). You can create relation simply by assigning this property with the id of related Country.
I have already answered separate question describing differences between Independent and Foreign key associations.
Conclusion: You must use either navigation property or foreign key property to create relation between object - both these artifacts are mapped in the entity.
Now example which will also show some details you asked me yesterday. This example shows following features:
Conditional mapping (When is_deleted = 0 in mapping details)
Independent association (I have also already described how to change Independent association to Foreign key association). If you are creating the model from existing database you can check Include foreign key columns in the model in Update wizard and it will use foreign key associations instead of independent associations in the whole model.
Navigation properties on both sides of the relation
Renaming properties in conceptual model (check mapping details where nice names are mapped to database names)
Changing accessibility of the Id property setter. I already answered similar question where this was required with POCO T4 template but same must be done for custom business objects.
Support for lazy loading - check virtual keyword used in business object's code for navigation properties.
Support for tracking proxies - check virtual keyword used in business object's code for scalar properties.
Related mapped business objects will look like:
public class Branch
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual Company Company { get; set; }
}
public class Company
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
}
And context using these custom business objects can look like:
public class Context : ObjectContext
{
public Context()
:base ("name=ModelContainer")
{
Companies = CreateObjectSet<Company>();
Branches = CreateObjectSet<Branch>();
ContextOptions.LazyLoadingEnabled = true;
ContextOptions.ProxyCreationEnabled = true;
}
public ObjectSet<Company> Companies { get; private set; }
public ObjectSet<Branch> Branches { get; private set; }
}
No, you're going to need the field visible if you want to do something like filter on it, unless you use Stored Procedures.
I don't really understand this one. Why would you NOT want company_id visible if you need to use it when inserting? It's not going to hurt anything if it's there. :)