Say we have Product
s and Category
s in a many-to-many relationship:
public class Product
{
public Product()
{
Categories = new HashSet<Category>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ICollection<Category> Categories { get; private set; }
}
public class Category
{
public Category()
{
Products = new HashSet<Product>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
If we want to add a Category
to a Product
, we have to load the product and add the category to its Categories
, for example:
Bad Example:
var product = db.Products.Find(1);
var category = db.Categories.Find(2);
product.Categories.Add(category);
db.SaveChanges();
(where db
is a DbContext
subclass).
This creates one record in the junction table between Product
and Category
. However, this table only contains two Id
values. It's a waste of resources to load two full entities in order to create one tiny record.
A more efficient way is to use stub entities, i.e. entity objects, created in memory, containing only the bare minimum of data, usually only an Id
value. This is what it looks like:
Good example:
// Create two stub entities
var product = new Product { ProductId = 1 };
var category = new Category { CategoryId = 2 };
// Attach the stub entities to the context
db.Entry(product).State = System.Data.Entity.EntityState.Unchanged;
db.Entry(category).State = System.Data.Entity.EntityState.Unchanged;
product.Categories.Add(category);
db.SaveChanges();
The end result is the same, but it avoids two roundtrips to the database.
Prevent duplicates
It you want to check if the association already exists, a cheap query suffices. For example:
var exists = db.Categories.Any(c => c.Id == 1 && c.Products.Any(p => p.Id == 14));
Again, this won't load full entities into memory. It effectively queries the junction table and only returns a boolean.