I have a Product object with a related Category within it.
I have the relationship between product and Category as a One to Many. But the Category can also be null.
Trouble is I cannot seem to handle null Category object.
I tried the following in my Product class:
private Category _category;
public virtual Category Category
{
get { return _category ?? (_category = new Category()); }
set { _category = value; }
}
And on my Database Context OnModelCreating method:
modelBuilder.Entity<Product>()
.HasRequired(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
Unfortunately on accessing the Product.Category in my design layer it always returns the New Category instance, rather than try to pull the Category by the Product.CategoryId (which does have a value).
How can I can setup my model to handle null Category?
If the Category is optional (it is because it can be null) you must use:
modelBuilder.Entity()
.HasOptional(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
And define your product as:
public class Product
{
...
public int? CategoryId { get; set; }
public virtual Category { get; set; }
}
Related
In EF Core, I manually configured navigation properties between a database view and a table. Here are the table and view.
Dealer Table
DealerId
DealerName
1
Dealer One
PhysicalAddress View
DealerId
City
1
City one
They are configured via the fluent api like so:
Dealer
modelBuilder.Entity<Dealer>(entity =>
{
entity.ToTable("Dealer");
entity.Property(e => e.DealerId)
.ValueGeneratedNever()
.HasColumnName("Dealer_Id");
entity.Property(e => e.DealerName)
.IsRequired()
.HasMaxLength(70)
.IsUnicode(false)
.HasColumnName("Dealer_Name");
});
PhysicalAddress
modelBuilder.Entity<PhysicalAddress>(entity =>
{
entity.HasKey("DealerId");
entity.ToView("PhysicalAddress");
entity.Property(e => e.DealerId).HasColumnName("Dealer_Id");
entity.Property(e => e.City)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false)
.HasColumnName("City");
entity.HasOne(e => e.Dealer)
.WithOne(d => d.PhysicalAddress)
.HasForeignKey<PhysicalAddress>(e => e.DealerId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
});
And the PhysicalAddress class:
public partial class PhysicalAddress
{
public int DealerId { get; set; }
public string City { get; set; }
public virtual Dealer Dealer { get; set; }
}
In my application code, I am using the foreign key to check if PhysicalAddress is a required dependent of Dealer. Note, this is a simplified version of my actual app code in which the types aren't hard-coded.
private bool IsPhysicalAddressRequired()
{
IEntityType dealerEntType = GetDealerEntityType();
INavigation nav = baseType.FindNavigation(prop);
IForeignKey fk = nav.ForeignKey;
if (fk.IsRequired)
{
return true;
}
return false;
}
This method returns false.
My question:
How do I configure the relationship between Dealer and PhysicalAddress so PhysicalAddress is a required dependent of Dealer?
I figured this one out. To make PhysicalAddress a required dependent of Dealer, it was set was set as an owned entity. This was done in a partial class of the DbContext:
public partial class MyContext : DbContext
{
partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Dealer>(entity =>
{
entity.OwnsOne(e => e.PhysicalAddress, b =>
{
b.ToTable("PhysicalAddress");
entity.Property(e => e.City)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false)
.HasColumnName("City");
b.Property(pa => pa.DealerId).HasColumnName("Dealer_Id").IsRequired();
});
entity.Navigation(e => e.PhysicalAddress).IsRequired();
}
}
Finally, in the DbContext file generated by Entity Framework, OnModelCreatingPartial must be called at the end of OnModelCreating:
public partial class GMDIContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// EF generated entity configurations here
OnModelCreatingPartial(modelBuilder);
}
}
I receive an exception when trying to replace the owned collection with the one I receive through API (EF Core 6).
System.InvalidOperationException: 'The instance of entity type 'Product' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Here is my model configuration:
public class Order
{
public int Id { get; set; }
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public Product Product { get; set; }
public int Quantity { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
modelBuilder.Entity<Product>(x =>
{
x.HasKey(e => e.Id);
x.Property(e => e.Name);
});
modelBuilder.Entity<Order>(x =>
{
x.HasKey(e => e.Id);
x.OwnsMany(e => e.OrderItems, p =>
{
p.ToTable("OrderItems")
.WithOwner();
p.HasOne(e => e.Product).WithMany();
p.Property(e => e.Quantity);
});
});
Act:
//these are updated items received through API
var newItems = new List<OrderItem>
{
new OrderItem{ Product = new Product { Id = 2}, Quantity = 2},
new OrderItem{ Product = new Product { Id = 3}, Quantity = 3}
};
var existingOrder = _dbContext.Orders
.Include(o => o.OrderItems).ThenInclude(i => i.Product)
.Single(o => o.Id == 1);
// Existing OrderItems are already referencing Product with Id 2
existingOrder.OrderItems = newItems;
// Error occurs because Product with Id = 2 was already tracked
_dbContext.SaveChanges();
I know that the issue might be resolved by replacing Product with ProductId within my OrderItem class but I would rather prefer to keep my domain model as it is.
What is the best practice to perform such update?
it's all about understanding of the modeling. You can create a model response to update also, but just have to update manually by calling it.
you have existingOrder from the query include, then include like this I guess
var existingOrder = ...
existingOrder.OrderItems.Update(yourentity_OrderItem);
await _dbContext.SaveChangesAsync();
So this mean you have to retrieve each Product.id by a for, and update each new items, then savechanges.
I am trying to get a single User, with a list of Items, mapped with a many-to-many entity UserItems. However, I am unable to retrieve the mapped Items due to to an error that I'm unable to solve (error at bottom of question). Here is my code:
public class User
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class Item
{
public int Id { get; set; }
public ICollection<UserItem> UserItems { get; set; }
}
public class UserItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
public int Quantity { get; set; }
}
The UserItem class configuration has the following relationships defined:
builder.HasOne(x => x.User)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.ClientCascade);
builder.HasOne(x => x.Item)
.WithMany(x => x.UserItems)
.HasForeignKey(x => x.ItemId)
.OnDelete(DeleteBehavior.ClientCascade);
I have the following generic repo with this method:
public class GenericRepository<T> : where T : class
{
private readonly DbContext _context;
public GenericRepository(DbContext context) => _context = context;
public T Get(Expression<Func<T, bool>> where, params Expression<Func<T, object>>[] navigationProperties)
{
IQueryable<T> query = _context.Set<T>();
query = navigationProperties.Aggregate(query, (current, property) => current.Include(property));
var entity = query.FirstOrDefault(where);
return entity;
}
}
However, when I try to run the code, I get an error on the Select(x => x.Item):
var user = repo.Get(x => x.Id == 1, x => x.UserItems.Select(y => y.Item));
Error:
System.InvalidOperationException: 'The expression 'x.UserItems.AsQueryable().Select(y => y.Item)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.'
What am I doing wrong, this seems to work for my other projects?
This error Occurs because you are not passing in a navigation property (x.UserItems would be a navigation property) but rather something you want to do with the navigation property. UserItems.Select(y => y.Item) is not a property of x because Select() is a function and therefore it cannot be included.
What you are trying to do (I assume it is including UserItems and also the corresponding Items) is not going to work with your current implementation of the repository. To include navigation properties of navigation properties .ThenInclude() must be used instead of .Include() which works only for navigation properties directly defined on the Entity the DbSet is created for.
But apart from your question I would suggest not to use such an generic implementation of Repository. The main benefit from using reposiories is to separarte code related to loading and storing of entities from the rest of your code. In your case if the consumer of repository knows that navigation properties must be included and that he has to provide them - then what is the point of having a repository at all? Then the consumer again cares about database specific code which makes having a repository unneccessary. I would recommend just making a conrete "UserRepository" which can only be used to retrieve users and explicitly includes the needed properties.
I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.
I have an entity relationship as follows:
public class Incident
{
[Key]
public int Id { get; set; }
public List<Equipment> EquipmentAssignments { get; set; }
}
public class Equipment
{
public int EquipmentId { get; set; }
public Incident Incident { get; set; }
public Designator Designator { get; set; }//I WANT TO INCLUDE THIS
}
I am trying to include "Designator" for every equipment in "EquipmentAssignments" and return this list. I am trying the following:
Incident tmp = _context.Incidents
.Where(x => x.Id == incid)
.Include(x => x.EquipmentAssignments)
.ThenInclude(x => x.Select(s => s.Designator)).First();
But I get the following error:
Additional information: The properties expression 'x => {from
Equipment s in x select [s].Designator}' is not valid. The expression
should represent a property access: 't => t.MyProperty'. When
specifying multiple properties use an anonymous type: 't => new {
t.MyProperty1, t.MyProperty2 }'.
I have tried using an anonymous type .ThenInclude(x => x.Select(s => new { s.Designator}) to no avail and am not sure how to accomplish what I need. Thank you for any help.
You cannot pass a select in ThenInclude. This is the ThenInclude syntax:
public static IIncludableQueryable<TEntity, TProperty> ThenInclude<TEntity, TPreviousProperty, TProperty>(this IIncludableQueryable<TEntity, ICollection<TPreviousProperty>> source, Expression<Func<TPreviousProperty, TProperty>> navigationPropertyPath) where TEntity : class;
Just remove the select and everything will working fine!
var tmp = myConext.Incidents
.Where(x => x.Id == 1)
.Include(x => x.EquipmentAssignments)
.ThenInclude(x => x.Designator).First();