How can I delete an object with navigation property with entity framework 5 code first? - code-first

I'm using EF 5 code first and I have 2 related entities ApplicationPermission and Application:
public class ApplicationPermission
{
public virtual Application Application { get; set; }
public int Id { get; set; }
}
public class Application
{
public string Name { get; set; }
public int Id { get; set; }
}
with the following mapping for ApplicationPermission:
HasKey(x => x.Id);
HasRequired(x => x.Application).WithMany().Map(m => m.MapKey("ApplicationId")).WillCascadeOnDelete(false);
and for Application:
HasKey(x => x.Id);
I use the code below for deleting ApplicationPermission:
ApplicationPermission entity = new ApplicationPermission { Id = id };
DbContext.Set<ApplicationPermission>().Attach(entity);
DbContext.Set<ApplicationPermission>().Remove(entity);
DbContext.SaveChanges();
But I got an error on SaveChanges method:
Entities in 'CodeFirstContainer.ApplicationPermissions' participate in
the 'ApplicationPermission_Application' relationship. 0 related
'ApplicationPermission_Application_Target' were found. 1
'ApplicationPermission_Application_Target' is expected.
How can I delete ApplicationPermission without loading Application to the dbcontext?

I believe it's not possible to delete an entity without having set required navigation properties when you use independent associations. You must load the Application from the database or - at least - know the foreign key value and attach an Application entity with that value, like so:
ApplicationPermission entity = new ApplicationPermission { Id = 1 };
entity.Application = new Application { Id = 5 };
DbContext.Set<ApplicationPermission>().Attach(entity); //attaches Application too
DbContext.Set<ApplicationPermission>().Remove(entity);
DbContext.SaveChanges();
The SQL command generated when you call SaveChanges is then:
exec sp_executesql N'delete [dbo].[ApplicationPermissions]
where (([Id] = #0) and ([ApplicationId] = #1))',N'#0 int,#1 int',#0=1,#1=5
As you can see the query for the delete does not only ask for the Id of the ApplicationPermission to delete but also (and) the foreign key value for ApplicationId. In order to succeed you must know and set this FK value by setting the related entity with the same primary key.
The problem does not occur when using foreign key associations:
public class ApplicationPermission
{
public virtual Application Application { get; set; }
public int ApplicationId { get; set; }
public int Id { get; set; }
}
Mapping:
modelBuilder.Entity<ApplicationPermission>()
.HasRequired(x => x.Application)
.WithMany()
.HasForeignKey(x => x.ApplicationId)
.WillCascadeOnDelete(false);
You can then use your original code without setting the FK property ApplicationId to a correct value (will default to 0 then) and without setting the navigation property and deleting the entity will work. The SQL command doesn't care about the FK and just queries for the Id of the ApplicationPermission to delete:
exec sp_executesql N'delete [dbo].[ApplicationPermissions]
where ([Id] = #0)',N'#0 int',#0=1
I have no idea why the SQL commands are different between the two types of associations.

Related

Many to Many between IdentityUser and other table in a separate context

I am having difficulty creating a join table relationship between my Identity Framework IdentityContext(the IdentityUser) and one of my other tables Let's call it Entry. The problem is, Entry is in an entirely separate context doing it's own thing as well.
What is the proper way to associate these two? Where do I define the Join Table in fluent api?
Right now, I am getting the following error.
The key {'ApplicationUserId'} contains properties in shadow state and is referenced by a relationship from 'ApplicationUser.ApplicationUserEntries' to 'ApplicationUserEntry.ApplicationUser'. Configure a non-shadow principal key for this relationship.
These are how my tables are defined.
public class ApplicationUser : IdentityUser
{
...
public virtual List<ApplicationUserEntry> ApplicationUserEntries { get; set; }
}
public class Entry
{
public int Id { get; set; }
...
public virtual List<ApplicationUserEntry> ApplicationUserEntries { get; set; }
}
And the join table as follows.
public class ApplicationUserEntry
{
public int ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public int EntryId { get; set; }
public Entry Entry { get; set; }
}
For the IdentityContext I have just some generic setup for other properties
var users = modelBuilder.Entity<ApplicationUser>();
users.Property(u => u.Name).IsRequired().HasMaxLength(65);
users.Property(u => u.FirstName).HasMaxLength(32);
users.Property(u => u.LastName).HasMaxLength(32);
And in my GoalsContext I have some general setup for other unrelated stuff, and the join table defined for ApplicationUserEntry
// Entry Configuration
var entries = modelBuilder.Entity<Entry>();
entries.HasKey(e => e.Id);
entries.HasAlternateKey(e => new { e.MilestoneId, e.CategoryId, e.MetricId });
entries.Property(e => e.Value).IsRequired();
entries.Property(e => e.Locked).IsRequired().HasDefaultValue(false);
entries.ToTable("GoalsEntries");
// ApplicationUserEntry Join Table
modelBuilder.Entity<ApplicationUserEntry>()
.ToTable("GoalsApplicationUserEntry")
.HasKey(se => new { se.ApplicationUserId, se.EntryId });
modelBuilder.Entity<ApplicationUserEntry>()
.HasOne(se => se.ApplicationUser)
.WithMany(s => s.ApplicationUserEntries)
.HasForeignKey(se => se.ApplicationUserId);
modelBuilder.Entity<ApplicationUserEntry>()
.HasOne(se => se.Entry)
.WithMany(e => e.ApplicationUserEntries)
.HasForeignKey(se => se.EntryId);
Now I'm sure I'm obviously missing something but I can't figure out what. I've never attempted to create a many to many relationship between two tables that are defined in two different contexts... and not even sure if that's wise or not to do.
My ultimate goal is to be able to associate owners with Entry records, so they can only be modified by the owners, which I verify with Identity Framework.
Ideally I would just prefer a unidirectional relationship, so I can find the owner from the Entry, but I'm not intending to get a list of Entry by looking at the IdentityUser

Issue loading child entity of a parent entity. Unidirectional mapping and 1 to 0..1 relationship with a Shared primary Key?

When I try to load child entity of parent entity it loads with default values. If i try to load explicitly it throws exception
Multiplicity constraint violated. The role 'Association_Customer_Target' of the relationship 'CodeFirstNamespace.Association_Customer' has multiplicity 1 or 0..1. This exception is thrown while retrieving the child entities of a complex graph.
I have a graph Association which has a child entity Customer with a relationship of one to zero or one and has an Independent association.*Primary key* is shared. I'm using EF6. lazy loading is enabled.
public class Association
{
public virtual long Id { get; set; }
public virtual string ExternalId { get; set; }
public virtual int OrganizationId { get; set; }
public virtual AssociationType AssociationType { get; set; }
public virtual Customer Customer {get; set;}
public Association()
{
Customer = new Customer();
}
}
Customer class.
public class Customer
{
public virtual long Id { get; set; } //Shared primary key
public virtual ICollection<Item> Items {get; set;}
public virtual ICollection<Complaint> Complaints {get; set;}
public customer()
{
Items = new List<Item>();
Complaints = new List<Complaint>();
}
}
Mapping are Uni directional:
public class AssociationMapping:EntityTypeConfiguration<Association>
{
public AssociationMapping() : base()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.ExternalId).IsRequired();
Property(x => x.OrganizationId).IsRequired();
Property(x => x.AssociationType);
HasOptional(x => x.Customer).WithRequired().WillCascadeOnDelete();
}
}
public class CustomerMapping : EntityTypeConfiguration<Customer>
{
public CustomerMapping ():base()
{
HasKey(x => x.Id);
Property(x => x.Id);
HasMany(x => x.Items)
.WithOptional()
.HasForeignKey(key => key.CustomerId)
.WillCascadeOnDelete();
HasMany(x => x.Complaints)
.WithOptional()
.HasForeignKey(key => key.CustomerId)
.WillCascadeOnDelete();
}
}
When I Load My association entity it loads perfectly but child entity Customer is loaded with default values when i try to load Customer explicitly it throws the exception.
var dbassociation = Single<Association>(x => x.OrganizationId== asso.organizationId && x.ExternalId == asso.ExternalId && x.AssociationType == asso.AssociationType);
dbassociation.Customer = Single<Customer>(x => x.id == dbassociation.id);
[Update: Single Method]
public TEntity Single<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>>criteria) {
return Context.Set<TEntity>().SingleOrDefault(criteria); }
For testing purpose I have tried to eager load by removing virtual on Customer property in association class and tried following but it throws same excepetion
Context.Configuration.LazyLoadingEnabled = false;
Context.Entry<Association>(dbassociation).Reference<Customer>(pa => pa.Customer).Load();
I have also tried which throws same exception
var dbassociation = Context.Set<Association>().Include("Customer").SingleOrDefault(x => x.OrganizationId== asso.organizationId && x.ExternalId == asso.ExternalId && x.AssociationType == asso.AssociationType);
Now I came to conclusion that though I use different methods for retrieving the exception is same. The problem is with mapping I guess. I appreciate your comments and suggestions.
Try to remove
Customer = new Customer();
from the Association constructor. Instantiating navigation references is a source for known problems (in contrast to instantiating empty navigation collections which is fine). It is the reason why you get a Customer with default values. I'm not sure if it also explains the exception, but I could imagine that when the Association gets loaded and attached to the context together with the uninitialized Customer created by the default constructor EF detects related entities with invalid keys: The Association which has (I assume) a key value !=0 and the related Customer with a key ==0 (because it never has been initialized to another value). However, in a shared primary key association the two key values must match. Because they don't, it might cause the exception (however an exception that doesn't really point very well to the root of the problem).
Just a guess.

EntityFramework: How to configure Cascade-Delete to nullify Foreign Keys

EntityFramework's documentation states that the following behavior is possible:
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null.
(from http://msdn.microsoft.com/en-us/jj591620)
However, I cannot achieve such a behavior.
I have the following Entities defined with code-first:
public class TestMaster
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TestChild> Children { get; set; }
}
public class TestChild
{
public int Id { get; set; }
public string Name { get; set; }
public virtual TestMaster Master { get; set; }
public int? MasterId { get; set; }
}
Here is the Fluent API mapping configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TestMaster>()
.HasMany(e => e.Children)
.WithOptional(p => p.Master).WillCascadeOnDelete(false);
modelBuilder.Entity<TestChild>()
.HasOptional(e => e.Master)
.WithMany(e => e.Children)
.HasForeignKey(e => e.MasterId).WillCascadeOnDelete(false);
}
Foreign Key is nullable, navigation property is mapped as Optional, so I expect the cascade delete to work as described as MSDN - i.e. to nullify MasterID's of all children and then delete the Master object.
But when I actually try to delete, I get the FK violation error:
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Find(1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
On SaveChanges() it throws the following:
System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.UpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.SqlClient.SqlException : The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TestChilds_dbo.TestMasters_MasterId". The conflict occurred in database "SCM_Test", table "dbo.TestChilds", column 'MasterId'.
The statement has been terminated.
Am I doing something wrong or did I misunderstood what the MSDN says?
It works indeed as described but the article on MSDN misses to emphasize that it only works if the children are loaded into the context as well, not only the parent entity. So, instead of using Find (which only loads the parent) you must use eager loading with Include (or any other way to load the children into the context):
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Include(m => m.Children)
.SingleOrDefault(m => m.Id == 1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
This will delete the master from the database, set all foreign keys in the Child entities to null and write UPDATE statements for the children to the database.
After following #Slauma's great answer I was still getting same error as OP.
So don't be as naive as me and think that the examples below will end up with same result.
dbCtx.Entry(principal).State = EntityState.Deleted;
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
// code above will give error and code below will work on dbCtx.SaveChanges()
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
dbCtx.Entry(principal).State = EntityState.Deleted;
First load the children into context before setting entity state to deleted (if you are doing it that way).

How to not assign an id when id is a fk with a custom id generator in ef

I have a project where I'm using EF5, I made a custom Guid Generator and I have an override of the SaveChanges method to assign the ids of my entities.
Everything is working fine except in one case: when the ID of one entity is a FK to another ID of another entity.
A little bit of code to explain the problem:
I have two entities I cannot change:
public class FixedEntityA
{
public Guid Id { get; set;}
public string SomeText { get; set; }
}
public class FixedEntityB
{
public Guid Id { get; set;}
public int OneInt { get; set; }
}
In my project I have an entity defined like this:
public class ComposedEntity
{
public Guid Id { get; set;}
public FixedEntityA FixedA { get; set; }
public FixedEntityB FixedB { get; set; }
public double OneDouble { get; set; }
}
The relationships are:
ComposedEntity may have 0 or 1 FixedEntityA
ComposedEntity may have 0 or 1 FixedEntityB
The constraints on the id are:
The Id of FixedEntityA is a FK pointing to the Id of ComposedEntity
The Id of FixedEntityB is a FK pointing to the Id of ComposedEntity
The mapping class are:
public ComposedEntity(): EntityTypeConfiguration<ComposedEntity>
{
HasOptional(fea => fea.FixedA).WithRequired();
HasOptional(feb => feb.FixedB).WithRequired();
}
Here is my SaveChanges override:
foreach (var entry in ChangeTracker.Entries<IEntity>().Where(e => e.State == EntityState.Added))
{
Type t = entry.Entity.GetType();
List<DatabaseGeneratedAttribute> info = t.GetProperty("Id")
.GetCustomAttributes(typeof (DatabaseGeneratedAttribute), true)
.Cast<DatabaseGeneratedAttribute>().ToList();
if (!info.Any() || info.Single().DatabaseGeneratedOption != DatabaseGeneratedOption.Identity)
{
if (entry.Entity.Id == Guid.Empty)
entry.Entity.Id = (Guid) _idGenerator.Generate();
}
}
return base.SaveChanges();
This code works fine everywhere for all kind of relationships except in this case, I am missing a test to make sure I'am not setting an id on id that are foreign keys, and I have no clue on how to check if an Id is a FK...
Here is a sample object where this code fails:
var fea = new FixedEntityA();
var feb = new FixedEntityB();
var composedEntity = new ComposedEntity();
composedEntity.FixedA = fea;
composedEntity.FixedB = feb;
If you insert the whole graph, all three objects are marked as Added and all Ids are default.
The problem is, with the current SaveChanges method, I will go through all object with the Added state in the change tracker and I will assign an Id to all entity with a default Guid and break my FK constraints.
Thanks in advance guys!
Here is some code that will get the FK properties for a given type (it's horrible I know). Should be simple enough to plug this into your code.
var typeName = "Category";
var fkProperties = ((IObjectContextAdapter)db)
.ObjectContext
.MetadataWorkspace
.GetItems<AssociationType>(DataSpace.CSpace)
.Where(a => a.IsForeignKey)
.Select(a => a.ReferentialConstraints.Single())
.Where(c => c.FromRole.GetEntityType().Name == typeName)
.SelectMany(c => c.FromProperties)
.Select(p => p.Name);

EF4 CTP5 Database First + Many-To-Many Relation (Error)

I'm pretty sure it's something regarding hidden conventions, but I always get an error when trying to map a many-to-many relation to an existing database.
Here is the simplest example:
[Table("ALRole", SchemaName = "AL")]
public class Role
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
[Table("ALUser", SchemaName = "AL")]
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
I got the usual three tables in the db: the first two are obvious, and the third is created with this script:
CREATE TABLE AL.ALUsersRoles
(
RoleID int NOT NULL,
UserID int NOT NULL,
CONSTRAINT PK_ALUserRole PRIMARY KEY(RoleID, UserID),
CONSTRAINT FK_ALUserRole_RoleID FOREIGN KEY(RoleID) REFERENCES AL.ALRole(ID),
CONSTRAINT FK_ALUserRole_UserID FOREIGN KEY(UserID) REFERENCES AL.ALUser(ID)
)
Now I try to map the many-to-many relation, with code like this:
// ...I'm in the EntityTypeConfiguration-derived class (User)
HasMany(u => u.Roles)
.WithMany(r => r.Users)
.Map(m =>
{
m.MapLeftKey(u => u.ID, "UserID");
m.MapRightKey(r => r.ID, "RoleID");
ToTable("ALUsersRoles", "AL");
});
I tried all the possibile combinations and variations in this code, but I always get the error:
{"Invalid column name 'Name'.\r\nInvalid ...and so on...
So I think it must be the table that is not created correctly.
Any ideas?
Thanks in advance
Andrea
P.S.: I stripped down some of my code, so maybe there can be some small typo...
well, this works for me same as OP.
//many-to-many between *Users -> Web_User_Rol <- Web_Rol*
modelBuilder.Entity<Users>()
.HasMany(u => u.Web_Rols).WithMany(r => r.Users)
.Map(t=>t.MapLeftKey("user_id")
.MapRightKey("roleID")
.ToTable("Web_User_Rol"));
There is nothing wrong with your object model or fluent API code. I've used them and they perfectly created the desired schema without any exception. I think your problem comes from another entity (perhaps one with a "Name" property) unrelated to what you've shown here. To find that, drop (or rename) your existing database and let Code First create one for you and then compare the 2 databases and see what is different.