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();
Related
My scenario:
I have a Product that has various properties such a price, size, etc. that are declared in the Product Entity.
Additionally, a Product can have a collection of StockRequirements, i.e. when that Product is used the constituent StockItems can be depleted by the StockRequirement quantity accordingly.
Under one use case I just want the Product so that I can play with the core properties. For another use case I want the Product with its StockRequirements.
This means that when retrieving a Product I may be using it in different contexts. My chosen approach has been to use EF table splitting.
I have one repository for Products and one repository for ProductStockRequirements. They are referring to the same unique Product.
The Product repository will provide a Product Entity with the core details only.
The ProductStockRequirements repository will provide ProductStockRequirements entity which does not have the core details, but does have the list of StockRequirements.
This seemed a reasonable approach so that I am not retrieving 'owned' StockRequirements when I only want to change the price of the product. Similarly, if I'm only interested in playing with the StockRequirements then I don't retrieve the other core details.
Entities
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
}
class ProductStockRequirements
{
public int Id { get; set; }
public List<StockRequirement> StockRequirements { get; set; }
}
Product Mapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.Property(p => p.CoreProperty).IsRequired();
ProductStockRequirementsMapping
b.ToTable("Products");
b.HasKey(p => p.Id);
b.OwnsMany<StockRequirement>(p => StockRequirements, b =>
{
b.ToTable("StockRequirements");
b.WithOwner().HasForeignKey("ProductId");
}
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id");
When running a migration, I get the warning:
The entity type 'ProductStockRequirements' is an optional dependent
using table sharing without any required non shared property that
could be used to identify whether the entity exists. If all nullable
properties contain a null value in database then an object instance
won't be created in the query. Add a required property to create
instances with null values for other properties or mark the incoming
navigation as required to always create an instance.
Focusing on the advice:
mark the incoming navigation as required to always create an instance
I have tried:
b.HasOne<Product>()
.WithOne()
.HasForeignKey<ProductStockRequirements>("Id")
.IsRequired();
and
b.HasOne<Product>()
.WithOne()
.IsRequired()
.HasForeignKey<ProductStockRequirements>("Id");
to no avail.
The warning does not appear to result in any bad behaviour. All my tests are passing. But, it seems that I should be able to create a map that removed this warning, but cannot find the way.
This should really just be
class Product
{
public int Id { get; set; }
public string CoreProperty { get; set; }
public List<StockRequirement> StockRequirements { get; set; } = new List<StockRequirement>();
}
As the StockRequiremens are not part of the Product entity, and related data isn't loaded unless you request it.
And the Entity model is simply not the correct layer to define your aggregates. An Aggregate is defined by selecting a single Entity from your entity model along with 0-few related entities. Typically you include the closely-related and weak entities together in an aggregate.
If your entity model is a graph of 23 related entities, you might organize it into 10 separate and partially-overlapping aggregates or sub-graphs.
I have 2 projetcs PRJ1 and PRJ2 which uses their own databases DB1 and DB2. Each of these databases uses EF Code First Migration.
The PRJ1 is for managing stock of products (already exists since 4 years).
The PRJ2 is for orders (brand new project still in dev)
Now let's talk about the second project only. In my project PRJ2 I need to access data from the other database DB1. So I need to place orders for products.
Here is what I got so far for PRJ2
Note that I defined 2 different contexts.
// Context for accessing entities in DB1
public class DB1Context : DbContext
{
static DB1Context()
{
Database.SetInitializer<DB1Context>(null);
}
public DbSet<Product> Products { get; set; }
}
// Context for accessing entities in DB2
public class DB2Context : DbContext
{
static DB2Context()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DB2Context, DAL.Migrations.Configuration>());
}
public DbSet<Anything> Anythings { get; set; }
public DbSet<Order> Orders { get; set; }
}
What works: I can query data from DB1Context (Products) or DB2Context (Anythings).
What didn't works yet: Creating my Orders entity.
// My Orders entity
public class Orders
{
public int Id { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public virtual Product Product { get; set; }
}
This entity located in DB2Context is referencing the Product entity which is part of DB1Context.
Now the problem is that as soon as I add this Entity inside my context DbSet<Order> I see that there is a migration waiting for Product. This migration is for creating Product in my DB2Context. That's not what I want. This entity already exists in DB1Context. It seems I cannot create this Order entity which is referencing Product from the other context.
Can you confirm this ? Did I missed something ? Otherwise what is the best alternative ?
I think you can't do that using Entity Framework.
That problem looks like a No-SQL database's problem. When you have multiple database like that you have to control all the CRUD for all databases. The ADO can't do it to you because you don't have data integrity.
A possible solution is you put your CRUD in your business logic layer.. or something like that.
Let suppose you have a OrderBll to control:
public class OrderBll
{
private DB1Context _DB1Context = new DB1Context();
private DB2Context _DB2Context = new DB2Context();
public List<Orders> GetOrders()
{
var orders = _DB2Context.Orders.Where(???).ToList();
var productIds = orders.Select(x => x.ProductId).Distinct().ToArray();
var products = _DB1Context.Products.Where(x => productIds.Contains(x.Id)).ToList(); // Optimize the load of all products in orders
// Set the product object in the order list
foreach( var order in orders )
{
order.Product = products.FirstOrDefault(x=>x.Id == order.ProductId);
}
return orders;
}
}
Remember you have to map the Product property in Orders like Ignore.
So, you have to set the Foreign Key by yourself and do all the constraints checks.
But, if someone else have a better solution to do that, I'll be glad to know.
Ideally, this is what you should aim for.
Put orders and products in the same database. You would then be able to create relations between orders and products. You would end up with a single EF context, and this would give you a solid solution.
If for whatever reasons you cannot put orders in the same database as products, then solutions are still available with their own limitations.
You may replicate the products table from DB1 to DB2, replication running every minute, for example. You can write your own replication component or use replication functionality of your database. If products can be deleted in DB2, replication could delete the products and orders in DB1, or just flagged deleted products. It is up to you to decide. If PRJ2 can update the products table, then replication has to be both ways. This gets more complicated. The EF context with this solution would contain relations from orders to products, and products to orders.
Another solution would be to keep in DB1 a "proxy" products table that contains the Id's of the products that are referenced in orders. Everywhere in your business logic, you decide whether you need to access the actual products table from DB1 or not. For example, when creating a new order, you would access products from DB1, and insert their Id's in the proxy table if not there. When displaying the products of an order, you would first retrieve the product Id's of the order from DB1, and then their full description from DB1. When updating the inventory of a product, as part of creating an order, you would access DB1, probably with a transaction that spans DB! and DB2.
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();
EF 4.3.1. I have defined User and Box entities. Each box may or may not be assigned to a user.
What I'd like to achieve is to have a OwnBox property in User class, and an Owner property in Box class.
in Database, I have defined OwnerId foreignkey in Boxes (Boxes.OwnerId has relation with Users.UserId).
To define the relationship with fluent api, I have defined the following classes:
public partial class User
{
public int UserId {get; set;}
public virtual Box OwnBox { get; set; }
}
public partial class Box
{
public int? OwnerId { get; set; }
public virtual User User { get; set; }
}
Then in my Mapping class for Box, I have defined the relations as follows:
this.HasOptional(t => t.User).WithOptionalDependent(d => d.OwnBox).
Map(m => m.MapKey("OwnerId")).WillCascadeOnDelete(true);
But by firing up the project, I got the error:
Schema specified is not valid. Errors: (56,6) : error 0019: Each
property name in a type must be unique. Property name 'OwnerId' was
already defined.
So I had to tell EF to forget about the OwnerId column first:
this.Ignore(t => t.OwnerId);
Now the project works fine. But I'm still doubtful if this is a good approach and will everything work fine on CRUD operations with foreign key associations.
First of all, this is not one-to-one relationship. In one-to-one relationship the foreign key must be a primary key.
I believe in your scenario the situation can happen:
User = { UserID = 2 }
Box1 = { UserID = 2 }
Box2 = { UserID = 2 }
Nothing stops you from doing that, but which box should be returned when you do that:
User.OwnBox, Box1 or Box2?
EF can deal with that using Independent Association. It will create foreign key, hidden from your POCO class. You can specify the name of the column using MapKey as you did. However, because you also created a property called OnwerID, just as the column used with MapKey, the EF has a problem as two properties are mapped to the same column.
When you use ignore, the POCO OwnerID property is ignored by EF so that fixes the problem of two properties, however, the OwnderID value never gets saved or read to the database. Because EF just ignores it.
Thanks for your question, I have learnt a lot thanks to this.
I have run into an interesting problem with Entity Framework and based on the code I had to use to tackle it I suspect my solution is less than ideal. I have a 1-to-Many relationship between Table A and Table B where entities in TableB have a reference to TableA. I have a scenario where I want to simultaneously delete all children of a row in TableA and I thought this could be achieve by simply clearing the collection:
Entity.Children.Clear()
Unfortunately, when I attempted to save changes this produced as a Foreign Key violation.
A relationship is being added or
deleted from an AssociationSet
'FK_EntityB_EntityA'. With cardinality
constraints, a corresponding 'EntityB'
must also be added or deleted.
The solution I came up with was to manually delete object via the entity context's DeleteObject(), but I just know this logic I am using has got to be wrong.
while (collection.Any())
Entities.DeleteObject(collection.First());
For one, the fact that I had to use a Where() loop seems far less than ideal, but I suppose that's purely a semantic assessment on my part. In any case, is there something wrong with how I am doing this, or is there perhaps a better way to clear a child entity collection of an entity such that Entity Framework properly calls a data store delete on all of the removed objects?
Clear() removes the reference to the entity, not the entity itself.
If you intend this to be always the same operation, you could handle AssociationChanged:
Entity.Children.AssociationChanged +=
new CollectionChangeEventHandler(EntityChildrenChanged);
Entity.Children.Clear();
private void EntityChildrenChanged(object sender,
CollectionChangeEventArgs e)
{
// Check for a related reference being removed.
if (e.Action == CollectionChangeAction.Remove)
{
Context.DeleteObject(e.Element);
}
}
You can build this in to your entity using a partial class.
You can create Identifying relationship between parent and child entities and EF will delete child entity when you delete it from parent's collection.
public class Parent
{
public int ParentId {get;set;}
public ICollection<Child> Children {get;set;}
}
public class Child
{
public int ChildId {get;set;}
public int ParentId {get;set;}
}
Mapping configuration:
modelBuilder.Entity<Child>().HasKey(x => new { x.ChildId, x.ParentId });
modelBuilder.Entity<Parent>().HasMany(x => x.Children).WithRequired().HasForeignKey(x => x.ParentId);
Trick: When setting up the relationship between Parent and Child, you'll HAVE TO create a "composite" key on the child. This way, when you tell the Parent to delete 1 or all of its children, the related records will actually be deleted from the database.
To configure composite key using Fluent API:
modelBuilder.Entity<Child>().HasKey(t => new { t.ParentId, t.ChildId });
Then, to delete the related children:
var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId);
var childToRemove = parent.Children.First(); // Change the logic
parent.Children.Remove(childToRemove);
// you can delete all children if you want
// parent.Children.Clear();
_context.SaveChanges();
Done!