I would like to know if it is possible to check FK when using SoftDelete with ASP.NET Boilerplate.
Example
Suppose these tables:
Roles: RoleId (PK) - Description
Users: UserId (PK) - Name - RoleId (FK with Roles)
Data:
Roles
1 - admin
2 - guest
Users
1 - admin - 1
2 - john - 2
So RoleId 1 should not be deleted if it was already assigned to an existing User.
Thanks in advance.
Soft delete just sets a flag to mark the record as deleted.
In ABP, you can write your own checks in ApplyAbpConceptsForDeletedEntity of your DbContext:
public class AbpProjectNameDbContext // : ...
{
// ...
protected override void ApplyAbpConceptsForDeletedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
CheckForeignKeys(entry);
base.ApplyAbpConceptsForDeletedEntity(entry, userId, changeReport);
}
private void CheckForeignKeys(EntityEntry entry)
{
var entity = entry.Entity;
if (!(entity is ISoftDelete))
{
// Foreign key constraints checked by database
return;
}
var role = entity as Role;
if (role != null)
{
if (Users.Any(u => u.Roles.Any(r => r.RoleId == role.Id)))
{
throw new UserFriendlyException("Cannot delete assigned role!");
}
}
}
}
Note that the template's RoleAppService actually removes users from the role before deleting it.
Briefly your goal is non-sense. SoftDelete feature marks the record as deleted and doesn't delete it physically. It's like Recycle Bin in Windows. So that you can undelete it anytime. From the database perspective, it's relationships are consistent. Because there's the data in the table.
Solution; when you prevent users to delete an in-use record you have to validate it yourself against your business rules.
Related
Apparently, EF6 doesn't like objects that have multiple foreign key properties that use the same key value, but do not share the same reference. For example:
var user1 = new AppUser { Id = 1 };
var user2 = new AppUser { Id = 1 };
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
When I attempt to insert this record, EF throws this exception:
Saving or accepting changes failed because more than one entity of type
'AppUser' have the same primary key value. [blah blah blah]
I've discovered that doing this resolves the issue:
var user1 = new AppUser { Id = 1 };
var user2 = user1; //same reference
I could write some helper code to normalize the references, but I'd rather EF just know they're the same object based on the ID alone.
As for why EF does this, one explanation could be that its trying to avoid doing multipe CRUD operations on the same object since separate instances of the same entity could contain different data. I'd like to be able to tell EF not to worry about that.
Update
So it's as I suspected per my last paragraph above. In absense of a means to tell EF not to do CRUD on either instance, I will just do this for now:
if (address.ModifiedBy.Id == address.CreatedBy.Id)
{
address.ModifiedBy = address.CreatedBy;
}
Works well enough so long as I am not trying to do CRUD on either.
Update2
I've previously resorted to doing this to prevent EF from validating otherwise-required null properties when all I need is the child entity's ID. However, it doesn't keep EF from going into a tizzy over separate instances with the same ID. If it's not going to do CRUD on either AppUser object, why does it care if the instances are different?
foreach (var o in new object[] { address.ModifiedBy, address.CreatedBy })
{
db.Entry(o).State = EntityState.Unchanged;
}
If you get AppUser from context, then you will not need to do anything, because Entity Framework will track entities:
var user1 = context.AppUsers.Find(1);
var user2 = context.AppUsers.Find(1);
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
Now, they both will point to same objects and will not cause to conflict.
You can add two extra properties to have the Id for the main objects which is the AppUser, then you can use only one AppUser object and reference it for both the created and modified by properties.
CreatedById = user1.Id,
ModifiedById = user1.Id
Otherwise, your code will end up by saving two instances of AppUser with the same primary key.
Another approach is to set both the foreign key properties to only one AppUserobject
The explanation is that EF's change tracker is an identity map. I.e. a record in the database is mapped to one, and only one, CLR object.
This can be demonstrated easily by trying to attach two objects with the same key:
context.AppUsers.Attach(new AppUser { Id = 1 });
context.AppUsers.Attach(new AppUser { Id = 1 });
The second line will throw an exception:
Attaching an entity of type 'AppUser' failed because another entity of the same type already has the same primary key value.
This also happens if you assign
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
Somewhere in the process, user1 and user2 must be attached to the context, giving rise to the exception you get.
Apparently, you have a function that receives two Id values that can be different or identical. Admittedly, it would be very convenient if you could simply create two AppUser instances from these Ids, not having to worry about identical keys. Unfortunately, your solution ...
if (address.ModifiedBy.Id == address.CreatedBy.Id)
... is necessary. Solid enough, though.
If I have two tables, which have an Id, whish is an autogenerated int (seed), anyway I have a many to many relationship between these two tables which requires another table.
Now, I do a "dry run" to generate the items for the first two table before saving them, this works perfect. The problem is when I try to generate the items for the (many-many relationship) in the third table. Before saving the items all Ids in the first two tables will be set to 0, when adding items to the relation table I have no problems, the problems comes when saving the tables because the relationship table will have the Ids of 0.
Is there a way to overcome this problem? like assigning a temp value which will be automatically changed to the real Id in the relationship table before saving it ?
For the same reason, I've chosen not to use default Seed methods (AddOrUpdate) provided by EF, but I'm rather writing my own seed methods.
Now if I want to set up relationships, I'm not explicitly using ID's, but rather use navigational properties.
Imagine the scenario:
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public virtual IList<Roles> Roles { get;set;}
}
public class Role
}
public int Id {get;set;}
public string Name {get;set;}
public virtual IList<User> Users {get;set;}
}
Doing this will seed both values for user and roles and relationships once you hit the save changes:
Role admin = new Role
{
Name = "Administrator"
};
Role basic = new Role
{
Name = "Basic"
};
User user = new User
{
Name = "John",
Roles = new List<Role>()
{
basic,
admin
}
}
db.Users.Add(user);
db.SaveChanges();
I have 5 tables:
User:
username (PK)
Role:
role_id(PK)
role_name
Permissions:
perm_id(PK)
perm_name
User_role_rels:
ur_id(PK)
username(FK) -> user.username
role_id(FK) -> role.role_id
Role_perm_rels:
rp_id(PK)
role_id(FK) -> role.role_id
perm_id(FK) -> Permissions.perm_id
When I create JPA entities for these five tables I get List of UserRoleRels in User entity, but I would need list of permissions for this user. So, there should be a
List permList in User entity.
I am new to JPA and not sure how I can achieve this using annotations?
Since there is no direct relationship between User and Permission, it's correct that User doesn't have a permList collection property.
Assuming that your IDE has created correctly the entities according to your tables, you don't have ManyToMany annotations in your classes, but only OneToMany and ManyToOne. This makes a little cumbersome to fetch the permissions collection:
String userName = "";
User myUser = entityManager.find(User.class, userName);
for (UserRoleRels urr : myUser.getUserRoleRelsList()) {
Role r = urr.getRole();
for(RolePermRels rpr : r.getRolePermRelsList()) {
Permission p = rpr.getPermission();
}
}
If you don't have an extra property in the ManyToMany relation tables (in your case User_role_rels and Role_perm_rels), you can drop the PK field for both. This will let your IDE: (1) create simpler Entity classes, (2) avoid the creation of the Entity classes for the relation tables, (3) use the ManyToMany annotations. With that change, you'll be able to access the permissions more easily and in a more readable way:
String userName = "";
User myUser = entityManager.find(User.class, userName);
for (Role r : myUser.getRoleList()) {
List<Permission> pl = r.getPermissionList();
}
Useful links: EclipseLink UserGuide, JPA Wikibooks.
I have a table called User, a table called Permission and an association table so Many Users can have many Permissions.
User
ID - PK
Permission
ID - PK
UserPermission
UserID - PK (FK to user)
PermissionID - PK (FK to Permission)
In entity framework how can I add an entry to the association table to link a user to a permission?
I have tried the following with no luck:
var user = _Repository.Users.Single(u => u.ID == someUserID);
var permission = _Repository.Permissions.Single(p => p.ID == somePermissionID);
user.Permissions.Add(permission) //Not working
user.Permission.Attach(permission) //Still not working
_Repository.Save();
Can anyone help?
It should be :
UserPermission obj = new UserPermission { UserID = someUserID, PermissionID = somePermissionID };
_Repository.UserPermissions.AddObject(obj)
_Repository.Save();
Hope this will help.
these are my simplified entities:
public class User : Entity
{
public virtual ICollection<Role> Roles { get; set; }
}
public class Role : Entity
{
public virtual ICollection<User> Users { get; set; }
}
var user = dbContext.Set<User>().Find(id);
dbContext.Set<User>().Remove(user);
dbContext.SaveChanges(); // here i get error (can't delete because it's the
//referenced by join table roleUsers
the problems is that the join table references the user table and ef doesn't remove the records from the join table before deleting the user
I tried writing test cases and I noticed that:
if use the same context to add user with roles, save changes, remove and again save changes it works
but if I use 2 different contexts one for insert and another one for delete I get this error
You must first clear Roles collection (user's roles must be loaded) before you will be able to remove user.
If you want to just get this deleting just do what the error is saying.
as a part of your DELETE method, you should do this, in order.
1) Get User including its related roles you can user User.Include(r=>r.roles)
2) iterate through and delete the roles for the given user (make sure you use a toList() when you do this loop )
3) Delete the user
4) savechanges
user.Roles
.ToList()
.ForEach(role => user.Roles.remove(role));
context.Users.remove(user);
context.SaveChanges();