Entity Framework many to many relationship Delete - entity-framework

I have a class that is called User. It has a property called Roles. Each user has many roles and each role can be assigned to many users.
The relationship is established by a 3rd table. I'd like to be able to remove a role from a User. However, I am calling the database twice. Once to fully load the "User" role and once to delte the role.
var user = this.Users.Include(f => f.Roles)
.SingleOrDefault(f => f.CustomerID == customerId && f.UserID == userId);
if (user != null)
{
var role = user.Roles.FirstOrDefault(f => f.RoleID == roleId);
if (role != null)
{
user.Roles.Remove(role);
return this.SaveChanges() > 0;
}
}
I tried doing this but it didn't work.
var user = new User { CustomerID = customerId, UserID = userId };
this.Users.Attach(user);
var role = new Role { RoleID = roleId };
this.Roles.Attach(role);
user.Roles.Add(role);
user.Roles.Remove(role);
return this.SaveChanges() > 0;
My Context has a DbSet<User> and DbSet<Role>. I don't have one for UserRole and I don't intend to have it.
Am i doing it right and do I need to always do 2 database calls?
-- User Class
[DataContract(Namespace = "urn:AES/Schemas/2014/09", IsReference = true)]
public class User
{
.....
[DataMember]
public List<Role> Roles { get; set; }
[DataMember]
public bool Active { get; set; }
}
The Mapping
this.HasMany(u => u.Roles)
.WithMany()
.Map(m =>
{
// The "left" key is the one specified in the HasMany method; the "right" key is the one specified in the WithMany method.
m.MapLeftKey(new string[] { "CustomerID", "UserID" });
m.MapRightKey("RoleID");
m.ToTable("UserRoles");
}
);
// Table & Column Mappings
this.ToTable("Users");
}

Curious why you don't model those other tables like UserRoles, are you just lazy? =)
But seriously, what you're doing is fine however for mass deletes EF doesn't have a bulk delete capability, you are better off simply executing the DELETE statement directly, like this:
using (System.Data.Common.DbCommand cmd = Context.Database.Connection.CreateCommand())
{
cmd.CommandText = #"DELETE FROM Table WHERE KeyColumn = #Id";
YourSqlHelper.AddParameter(cmd, "#Id", id);
cmd.ExecuteNonQuery();
}

Related

ef6 set only foreign key property of navigation property while saving

When I save entity which has navigation property which I use as foreign key:
this.HasRequired<Role>(c => c.Role).WithMany().Map(c => c.MapKey("role_id"));
I set only foreign key property of this navigation property (I get it from web page) thereby other properties of this navigation property are empty, but they have required restrictions:
this.Property(c => c.RoleName).IsRequired();
It's the reason why I get "dbentityvalidationexception" exception with error "field is required".
Is it possible to solve this problem by somehow?
Or I must get full entity for that navigation property from DB, set navigation property of entity which I save and then save my initial entity (It works now, but it doesn't look like good solution)?
Thanks in advance.
This is MS MVC action where I handle the model:
[HttpPost]
public async Task<ActionResult> AddAsync(Staff staff)
{
await staffService.InsertAsync(staff);
return RedirectToAction("Index");
}
and that part of view where I set the property:
<dt>
#Html.Label("Role")
</dt>
<dd>
#Html.DropDownListFor(_=>_.Role.Id, new SelectList(ViewBag.Roles, "Id", "RoleName"), "- Please select a Role -")
</dd>
this is the type of model "Staff"
public class Staff
{
public Staff()
{
Name = new Name();
}
public int Id { get; set; }
public Name Name { get; set; }
...
public virtual Role Role { get; set; }
...
}
I have found more or less good solution for that problem.
I have set all navigation property for which I have only foreign key property as
EntityState.Unchanged.
Now I have this method for saving only specific entity (Staff)
public virtual Staff InsertStaff(Staff entity)
{
context.Entry(entity.Role).State = EntityState.Unchanged;
context.Entry(entity.Maneger).State = EntityState.Unchanged;
SetStaffNavigationPropertiesUnchanged(entity);
return dbSet.Add(entity);
}
and this for saving full graph (in base generic class):
public virtual TEntity InsertGraph(TEntity entity)
{
return dbSet.Add(entity);
}
using "EntityState.Unchanged" (I will show in simplest way) lets me saving Staff when Role has only foreign key property filled (Role has RoleName required property)
using (ClinchContext context = new ClinchContext())
{
var role = new Role { Id = 1 };
Staff staff = context.Staffs.Add(new Staff
{
Name = new Name { Firstname = "FN", Surname = "S", Patronymic = "P" },
Email = "E",
Login = "L",
IsDeleted = false,
Role = role,
Maneger = null//nullable for now
});
context.Entry(staff.Role).State = EntityState.Unchanged;
context.SaveChanges();
}
If someone has more proper solution I would be happy to know, Thank you.

EF code first Seed many to many relationship not working when fluent API is used

I have an issue with seeding the many to many tables, when I add the Fluent API.
First I have created the two entities, and the many to many relationship between them like this:
[Table("meta.DataCategory")]
public partial class DataCategory : ResolvableEntityBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column("DataCategoryId")]
public int Id { get; set; }
[Required]
[StringLength(50)]
[Column("DataCategory")]
[DisplayName("DataCategory")]
public string Name { get; set; }
public virtual ICollection<DestinationTable> DestinationTables { get; set; }
}
[Table("meta.DestinationTable")]
public partial class DestinationTable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column("DestinationTableId")]
public int Id { get; set; }
[Required]
[StringLength(200)]
[Column("TableName")]
public string Name { get; set; }
[Required]
[StringLength(200)]
[Column("SchemaName")]
public string Schema { get; set; }
public virtual ICollection<DataCategory> DataCategories { get; set; }
}
Adding the migration, and updating the database create the 3 tables, including the many to many table between the DataCategory and DestinationTable. The seed code showen here also worked as it should, and I was able to populate test data to all 3 tables:
context.DataCategories.AddOrUpdate(s => s.Id,
new DAL.DataCategory() { Id = 0, Name = "Unknown", RegexPattern = #"" },
new DAL.DataCategory() { Id = 1, Name = "ProductsMaster", RegexPattern = #"(?:\b|[_]{1})(product|products|produkter|articles)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 2, Name = "CustomersTraffic", RegexPattern = #"(?:\b|[_]{1})(trafik|antal)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 3, Name = "ProductSales", RegexPattern = #"(?:\b|[_]{1})(salg|sales|ugedata|uge data|uge_data)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 4, Name = "CategorySales", RegexPattern = #"(?:\b|[_]{1})(kategory|kategori|category|kat)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 5, Name = "StoresMaster", RegexPattern = #"(?:\b|[_]{1})(site\bmaster|store|stores|butik)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 6, Name = "MultipleCategories", RegexPattern = #"" },
new DAL.DataCategory() { Id = 7, Name = "ConsultantsMaster", RegexPattern = #"" }
);
DAL.DestinationTable dt = new DAL.DestinationTable();
dt.Id = 1;
dt.Name = "Product";
dt.Schema = "DW";
dt.Type = "Reference";
dt.DataCategories = new List<DAL.DataCategory>();
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "ProductsMaster").First());
context.DestinationTables.AddOrUpdate(x => x.Name, dt);
dt = new DAL.DestinationTable();
dt.Id = 2;
dt.Name = "Store";
dt.Schema = "DW";
dt.Type = "Reference";
dt.DataCategories = new List<DAL.DataCategory>();
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "StoresMaster").First());
context.DestinationTables.AddOrUpdate(x => x.Name, dt);
dt = new DAL.DestinationTable();
dt.Id = 3;
dt.Name = "ProductSales";
dt.Schema = "DW";
dt.Type = "Transactions";
dt.DataCategories = new List<DAL.DataCategory>();
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "ProductSales").First());
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "ProductsMaster").First());
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "StoresMaster").First());
context.DestinationTables.AddOrUpdate(x => x.Name, dt);
So far so good, however, the many to many table was created under the dbo schema, and the column names used the _Id suffix, which I do not use in the rest of the model, so I had to change that, so I used the following Fluent API in the OnModelCreating() method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DAL.DestinationTable>()
.HasMany<DAL.DataCategory>(dest => dest.DataCategories)
.WithMany(dcat => dcat.DestinationTables)
.Map(m2m =>
{
m2m.MapLeftKey("DestinationTableId");
m2m.MapRightKey("DataCategoryId");
m2m.ToTable("DestinationTableToDataCategory", "meta");
});
}
This made the expected result in terms of naming the table, and naming the columns the way I want it to be, but now the seeding does not work as before.
The seed code populate the DataCatetory records, and the DestinationTable records, but the many to many table is empty. I tried to reveres the direction, so that the DataCategory was on the left side, but the result was the same, that is the many to many table was not populated.
Note that the seed method run completely without errors.
Why does this happens?
What is the way to seed many to many relationship, when using the Fluent API to bypass EF defaults?
Is there something wrong/missing in the Fluent API part?
OK,
I rolled back the entire database to the initial state, and then I applied all the migrations again, the result was that now the seed is working, and the data is populated in all 3 tables.
Cannot say what was wrong.

Need examples for Entityframework.HierarchyId

I found EF.HierarchyId on NuGet. Looks like EF6.1.1 supports it now?
Wondering if there are any documentation I can see about how to use it.
For example:
-does it work with edmx? may it import models with hierarchyId column?
-some linq examples? what commands are supported?
Thank you.
From version 6.1.3-alpha1 you can add multiple entities, no more ArgumentException "At least one object must implement IComparable." (This is the same as version 6.1.2, only contains the fix for this problem)
But I can't reproduce the "And finding by key is not working." problem.
If you want to create the model from database you need to install a custom version of EntityFramework Tools. The source code is on CodePlex: https://entityframework.codeplex.com/SourceControl/network/forks/zgabi/EfHierarchyId
Edmx should work, if you find any problem, please report it to zavarkog(X)gmail.com
I just installed it, I think it's still under development.
When creating from database first.
The data type 'hierarchyid' is currently not supported for the target
.NET Framework version; the column 'Id' in table 'dbo.Users' was
excluded.
The column 'Id' on the table/view 'dbo.Users' was excluded, and is a
key column. The table/view has been excluded. Please fix the entity
in the schema file, and uncomment.
When creating from code first.
public class AppContext : DbContext
{
public DbSet<User> Users { get; set; }
}
public class User
{
public HierarchyId Id { get; set; }
public string Name { get; set; }
}
It worked, the database and the table were successfully created, but adding multiple objects at once throws error.
using (var db = new AppContext())
{
db.Users.Add(new User { Id = HierarchyId.Parse("/"), Name = "President" });
// Working.
db.SaveChanges();
db.Users.Add(new User { Id = HierarchyId.Parse("/1/"), Name = "VP 1" });
db.Users.Add(new User { Id = HierarchyId.Parse("/2/"), Name = "VP 2" });
db.Users.Add(new User { Id = HierarchyId.Parse("/3/"), Name = "VP 3" });
// ArgumentException "At least one object must implement IComparable."
db.SaveChanges();
}
And finding by key is not working.
using (var db = new AppContext())
{
var id = HierarchyId.Parse("/");
var user1 = db.Users.Find(id); // null
var user2 = db.Users.FirstOrDefault(u => u.Id == id); // null
var user3 = db.Users.FirstOrDefault(u => HierarchyId.Compare(u.Id, id) == 0); // null
var user4 = db.Users.AsNoTracking().ToArray()
.FirstOrDefault(u => u.Id == id); //not null
var user5 = db.Users.AsNoTracking().ToArray()
.FirstOrDefault(u => HierarchyId.Compare(u.Id, id) == 0); //not null
}

how can I update children and insert new child in one to many relationship in EF

I have one to many relationship between ItemPrice and ItemPriceHistory classes my mapping follows below:
public class ItemPrice
{
public long ID {get; set;}
public List<ItemPriceHistory> ItemPriceHistories { get; set; }
public ItemPrice()
{
ItemPriceHistories = new List<ItemPriceHistory>();
}
}
public class ItemPriceHistory
{
public ItemPrice ItemPrice { get; set; }
public long ID {get; set;}
public bool IsCurrent { get; set; }
}
modelBuilder.Entity<ItemPriceHistory>()
.HasRequired(h => h.ItemPrice)
.WithMany(p => p.ItemPriceHistories)
.Map(h => h.MapKey("ItemPrice_ID"));
I am trying to update previous ItemPriceHistory Entries and try to add a new ItemPriceHistory Entry.
var dbItemPrice = repo.Std.Get<ItemPrice>()
.SingleOrDefault(c => c.ID == id);
if (dbItemPrice == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
//query for matching ItemPriceHistory
var dbPriceHistories = repo.Std.Get<ItemPriceHistory>()
.Include(h=>h.ItemPrice, repo.Std)
.Where(h => h.ItemPrice.ID == ItemPrice.ID)
.OrderByDescending(h => h.ModifiedDate);
#region new history entry
var newHistoryEntry = new ItemPriceHistory();
newHistoryEntry.IsCurrent = true;
newHistoryEntry.ItemPrice = dbItemPrice;
//copy the current pirce list and update it with new history entry
var currentPriceHistoryList = dbPriceHistories.ToList();
currentPriceHistoryList.ForEach(h => { h.IsCurrent = false; });
currentPriceHistoryList.Add(newHistoryEntry);
//new price list
var newHistoryList = new List<ItemPriceHistory>();
currentPriceHistoryList.ForEach(h => newHistoryList.Add(new ItemPriceHistory
{
ItemPrice = h.ItemPrice,
IsCurrent = h.IsCurrent,
}
));
#endregion
//delete current price histories
dbItemPrice.ItemPriceHistories.Clear();
// add histories to database
newHistoryList.ForEach(h => dbItemPrice.ItemPriceHistories.Add(h));
Context.SaveChanges();
When it calls SaveChanges(), I get the following error:
{"An error occurred while saving entities that do not expose foreign
key properties for their relationships. The EntityEntries property
will return null because a single entity cannot be identified as the
source of the exception. Handling of exceptions while saving can be
made easier by exposing foreign key properties in your entity types.
See the InnerException for details."}
InnerException: {"A relationship from the 'ItemPriceHistory_ItemPrice'
AssociationSet is in the 'Deleted' state. Given multiplicity
constraints, a corresponding 'ItemPriceHistory_ItemPrice_Source' must
also in the 'Deleted' state."}
I do not want to delete my ItemPrice_Source. I just want to delete current ItemPriceHistories and update previous ItemPriceHistories and add new ItemPriceHistory entry. How can I safely update ItemPriceHistory entries along with new ItemPriceHistory entry?
Thanks!
I use an example that shoudl fit into your STD generic repository class.
Looks like you are using this pattern.
Did you try something like
public virtual IQueryable<T> GetList(Expression<Func<T, bool>> predicate, bool withTracking = false)
{
if (withTracking)
return QuerySet.Where(predicate);
return QuerySet.Where(predicate).AsNoTracking();
}
// then
var iph_list = RepItemPriceHist.GetList(h=>h.ItemPrice.Id == VarID)
// psuedo code... foreach history entity entitity.Iscurrent = false;
so now these entities should be changed and managed by the statemanager.
add teh new entries....
// add histories to database
newHistoryList.ForEach(h => dbItemPrice.ItemPriceHistories.Add(h));
Context.SaveChanges();
I didnt understand why you need to CLEAR the collection
Arent you updating existing records and adding new ones ?
Good luck
Can't you just load the ItemPrice including all current ItemPriceHistories, then set the IsCurrent flag of those histories to false and add a new history with IsCurrent = true? Change tracking should update the current histories and insert a new one to the database:
var dbItemPrice = repo.Std.Get<ItemPrice>()
.Include(i => i.ItemPriceHistories, repo.Std)
.SingleOrDefault(i => i.ID == id);
if (dbItemPrice == null)
return Request.CreateResponse(HttpStatusCode.NotFound);
foreach (var history in dbItemPrice.ItemPriceHistories)
history.IsCurrent = false;
dbItemPrice.ItemPriceHistories.Add(new ItemPriceHistory { IsCurrent = true });
Context.SaveChanges();

Can anyone tell my how I can fix my model (Using FluentAPI) so that I can assign a different User to my User.FriendCollection?

I am new to CodeFirst and I am trying to create a Friends Collection so that two Users can have each other in their Friends Collection.
Here is my User Class:
public class User
{
public User()
{
if (FriendCollection == null) FriendCollection = new Collection<Friend>();
}
public long ID { get; set; }
public virtual ICollection<Friend> FriendCollection { get; set; }
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(x => x.ID);
}
}
Here is my Friend Class:
public class Friend
{
public Friend()
{
DateAdded = DateTime.UtcNow;
}
public long ID { get; set; }
public DateTime DateAdded { get; set; }
public long UserID { get; set; }
public virtual User User { get; set; }
}
public class FriendConfiguration : EntityTypeConfiguration<Friend>
{
public FriendConfiguration()
{
HasKey(x => x.ID);
HasRequired(x => x.User).WithMany(x => x.FriendCollection).HasForeignKey(x => x.UserID);
}
}
Here is my Entity Class:
public Entities()
{
Database.SetInitializer<Entities>(new DropCreateDatabaseAlways<Entities>());
}
public DbSet<User> Users { get; set; }
public DbSet<Friend> Friends { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserConfiguration());
modelBuilder.Configurations.Add(new FriendConfiguration());
base.OnModelCreating(modelBuilder);
}
}
And here is my Console Code and ideally what I would like to achieve:
using (Entities db = new Entities ())
{
var u1 = new User();
db.Users.Add(u1);
var u2 = new User();
db.Users.Add(u2);
u1.FriendCollection.Add(new Friend { User = u2 });
u2.FriendCollection.Add(new Friend { User = u1 });
try
{
var cnt = db.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
SaveChanges throws an error whenever I tried to populate the Friend class with a User object saying:
Multiplicity constraint violated. The role 'Friend_User_Target' of the relationship 'Entities.Friend_User' has multiplicity 1 or 0..1.
If I assign to the UserID like this:
u1.FriendCollection.Add(new Friend { UserID = u2.ID });
This allows me to save the entities, but not correctly!
When I step through the code I can isolate the problem and here's what I find:
b.FriendCollection.Add(new Friend { UserID = a.ID });
//Friend.UserID = 1
db.SaveChanges();
//Friend.UserID = 2 matching the b.ID
So now I can go back to my previous example and SaveChanges() without throwing the error if I assign the Friend.User to the calling User. For example:
b.FriendCollection.Add(new Friend { User = b });
db.SaveChanges()
But when I try to assign a different user I get the error. For example:
b.FriendCollection.Add(new Friend { User = a });
db.SaveChanges()
I get:
Multiplicity constraint violated. The role 'Friend_User_Target' of the relationship 'Entities.Friend_User' has multiplicity 1 or 0..1.
Can anyone tell my how I can fix my model (Using FluentAPI) so that I can assign a different User to my User.FriendCollection?
The mapping is not correct, at least not correct for the meaning of the relationships in your model.
Your mapping creates one single relationship and the two navigation properties User.FriendCollection and Friend.User are the two ends of this single relationship. You have defined them as inverse properties.
But this would mean that if you have a user UserA who has a FriendB in the FriendCollection the User in FriendB must be UserA and not any other user. Your model can only express that a user is a friend of him/herself, so to speak. Because you are adding another user to the collection you get exceptions.
In my opinion the entity Friend does not represent a friend = another user, but it actually expresses a relationship. It's kind of an intermediate entity for a many-to-many relationship between users and this relationship is a Friendship (I would prefer to call it this way.)
In any case your model needs two relationships. You can leave the entities as they are but use this mapping:
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(x => x.ID);
HasMany(x => x.FriendCollection)
.WithRequired();
}
}
public class FriendConfiguration : EntityTypeConfiguration<Friend>
{
public FriendConfiguration()
{
HasKey(x => x.ID);
HasRequired(x => x.User)
.WithMany()
.HasForeignKey(x => x.UserID)
.WillCascadeOnDelete(false);
}
}
(Disabling cascading delete on one of the relationships is necessary to avoid the infamous "multiple cascading delete path exception".)
Note that you have now two foreign keys in the Friend table, UserID for the second and User_ID for the first mapping. You can rename the default column name by adding Map(m => m.MapKey("SomeUserID")) to the first mapping.
With this mapping your console code should work.