I have stores with products, I want to get all stores even if the store doesn't have active products
here are the entity classes
class Store
{
[Key]
public Guid UId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public bool Inactive { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
class Product
{
[Key]
public Guid Id { get; set; }
public Guid StoreUid { get; set; }
public virtual Store Store{ get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public DateTime Updated { get; set; }
public bool Inactive { get; set; }
}
the statement I'm using now is:
using (var context = new DbContext())
{
context.Stores.
.AsNoTracking()
.Where(store => !store.Inactive)
.Include(x => x.Products)
.ToList();
}
I tired
using (var context = new DbContext())
{
context.Stores.
.AsNoTracking()
.Where(store => !store.Inactive && store.Products.Any(p=>!p.Inactive))
.Include(x => x.Products)
.ToList();
}
but I didn't get the store without any linked product to it.
the think is that I want to be able to get the Store.Products as Null or empty collection
I want to avoid as much for each statement and try to do it SQLish.. because in SQL is much easier to do this but I need the Include for nesting
EF Core support filtered in include.
To retrieve all active stores and include active products, you can :
context.Stores.AsNoTracking()
.Where(store => !store.Inactive)
.Include(x => x.Products.Where(p=>!p.Inactive))
.ToList();
Also, EF Core support Global Query Filters. Very useful to manage soft delete.
Related
As you can see here I have two entities that are linked with each other in two ways.
First way is classic many to many by two ICollection and second is by entity GroupRole which specifies role of user for particular group.
The problem is that User stores his roles for every group and Group stores roles of each user for itself.
I'd like to retrieve all groups including it's users but only with particular role, unfortunately it seems like when I use Include statement and then nest inside of it Where I cannot use Group entity representation within nested statement and it throws an error
LINQ statement couldn't be translated because of 'g' within Include statement
If anyone could drop some hint how to work around this problem I would be very glad.
Thanks
var result = await groupsRepository.Query(tracking: false)
.Include(g => g.Users.Where(u => u.GroupRoles.Any(r => r.Role == GroupRolesEnum.ExampleRole && r.GroupId == g.GroupId))).
.ToListAsync();
public class Group
{
public string Name { get; set; }
public Guid GroupId { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<GroupRole> UsersRoles { get; set; }
}
public class User
{
public Guid UserId { get; set; }
public string Email { get; set; }
public ICollection<Group> Groups{ get; set; }
public ICollection<GroupRole> GroupRoles { get; set; }
}
public class GroupRole
{
public Guid UserId { get; set; }
public Guid GroupId { get; set; }
public GroupRolesEnum Role { get; set; }
public virtual Group Group{ get; set; }
public virtual User User { get; set; }
}
I get an exception with DetachedLazyLoadingWarning:
Error generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: An attempt was made to lazy-load navigation property 'Product' on detached entity of type 'DeliveryProxy'. Lazy-loading is not supported for detached entities or entities that are loaded with 'AsNoTracking()'.'. This exception can be suppressed or logged by passing event ID 'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
while trying to query SQL database with Entity Framework Code 2.1
This is my query:
var orders =
_context
.Set<Order>()
.Where(v => v.CompanyId == companyId)
.Include(v => v.Details)
.ThenInclude(d => d.Delivery)
.ThenInclude(v => v.Product)
.OrderByDescending(v=> v.Details.FirstOrDefault().Delivery.Product.ProductId)
.ThenByDescending(v=> v.Details.FirstOrDefault().Delivery.Value)
.ThenByDescending(v => v.CreatedAt)
.Page(request.Page, request.RowsPerPage);
Here are entities and relationships:
public class Order : IEntity<int>
{
public int CompanyId { get; set; }
public virtual ICollection<OrderDetails> Details { get; set; }
[Required]
public DateTimeOffset CreatedAt { get; set; }
[Key]
public int Id { get; set; }
}
public class OrderDetails : IEntity<int>
{
public int OrderId { get; set; }
public int DeliveryId { get; set; }
[ForeignKey(nameof(OrderId))]
public virtual Order Order { get; set; }
[ForeignKey(nameof(DeliveryId))]
public virtual Delivery Delivery { get; set; }
[Key]
public int Id { get; set; }
}
public class Delivery : IEntity<int>
{
[Required]
public int ProductId { get; set; }
public int Value { get; set; }
[ForeignKey(nameof(ProductId))]
public virtual Product Product { get; set; }
[Key]
public int Id { get; set; }
}
[Table("Products")]
public class Product : IEntity<int>
{
[Required]
public byte ProductCategoryId { get; set; }
public virtual ICollection<Delivery> Deliveries { get; set; }
[Key]
public int Id { get; set; }
}
Looks like Details.FirstOrDefault() detaches the entity Delivery. The same solution worked with Entity Framework 6. How can I improve my query to get the date from database using only one query (suppressing the warining didn't help)?
You should also be seeing a lot of Client evaluation logging
warnings. And currently client evaluation doesn't play well with explicit/lazy loading.
The cause of the client evaluation in this case is the v.Details.FirstOrDefault() expression inside the ordering methods. And the challenge with the current stage of EF Core is to find the supported translatable equivalent LINQ construct.
In this particular scenario the solution (workaround) is to use intermediate SelectMany projection with Take(1). Replace the part starting from .OrderByDescending(..) up to Page(...) with the following:
.SelectMany(
o => o.Details.Select(d => d.Delivery).Take(1).DefaultIfEmpty(),
(o, d) => new { Order = o, Delivery = d })
.OrderByDescending(v => v.Delivery.ProductId)
.ThenByDescending(v => v.Delivery.Value)
.ThenByDescending(v => v.Order.CreatedAt)
.Select(v => v.Order) // restore the original projection
I'm using EntityFramework for my Microsoft Sql Data Base.
First entity is Product:
public class Product
{
public Product()
{
ProductStories = new HashSet<ProductStory>();
}
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Deleted { get; set; }
public HashSet<ProductStory> ProductStories { get; set; }
}
And another entity is ProductStory, which stores story about income or outcome of Products.
public class ProductStory
{
public int ProductStoryId { get; set; }
public virtual Product.Product Product { get; set; }
public int Count { get; set; }
public DateTime DateTime { get; set; }
}
So one Product could be in mane ProductStories, or in none.
I will not show all code(too big), so when I firstly create a single Product instance and save it in DB. Then I create a single ProductStory and reference to property Product to that instance of Product.
Then I save this ProductStory, there becomes 2 instances of ProductStory.
As I read, and I made this as virtual property:
public virtual Product.Product Product { get; set; }
How this problem could be solved?
I'm using EntityTypeConfiguration for tables configuration.
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
ToTable("Products").HasKey(x => x.ProductId);
Property(x => x.ProductId).IsRequired();
Property(x => x.Name).IsRequired().HasMaxLength(255).HasColumnName("Name");
//.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Name") { IsUnique = true }));
Property(x => x.Description).IsOptional().HasColumnName("Description");
Property(x => x.Deleted).HasColumnName("Deleted");
}
}
And for ProductStory:
class ProductStoryMap: EntityTypeConfiguration<ProductStory>
{
public ProductStoryMap()
{
ToTable("ProductStories").HasKey(ps => ps.ProductStoryId);
Property(ps => ps.ProductStoryId).IsRequired();
//Property(ps => ps.ProductId).IsRequired().HasColumnName("ProductId");
Property(ps => ps.Count).HasColumnName("Count");
Property(ps => ps.DateTime).HasColumnName("DateTime");
}
}
You have some errors in your code:
//Change this:
public HashSet<ProductStory> ProductStories { get; set; }
//For this (virtual is needed here, also use ICollection rather than any specific implementation)
public virtual ICollection<ProductStory> ProductStories { get; set; }
//Change this:
public virtual Product.Product Product { get; set; }
//For this (virtual makes no sense here)
public Product.Product Product { get; set; }
And lastly, ProductStory needs a way to keep the reference to its parent Product. This is what creates the Foreign Key relationship in your database and allows Entity Framework to link the tables. So add this to ProductStory:
public int ProductId { get; set; }
If you are still getting a duplicated object (which may happen), ensure you are setting the ProductId to the ProductStory you are saving.
The solution was about Entity Framework "bug/feature".
As I add new ProductStory into DataBase, it attaches the whole graph(including all other entities references and recreates them).
So before commiting new ProductStory, I have to set to null all it's navigation properties to avoid recreating.
I have an auto generated class (Orders class) which references other objects. What i'm trying to accomplish is get
an order using the order id and the user related to that order.
However when loading the object it is taking 2 seconds to load it. I looked at the object and EF is populating the list
of all the dependent objects (Language, country, customer user).
How do i get only specific object like only the order properties and the user object ? I tried to accomplish that using
the below code but it is loading all the objects.
//Code for getting order id and user related to it
using (var _storeEntities = new StoreEntities())
{
var test = _storeEntities.Orders.Where(r => r.Id.ToString() == orderId).Include(x => x.User).ToList();
}
// Parent entity
public partial class StoreEntities: DbContext
{
public virtual DbSet<Orders> Orders { get; set; }
}
// Order object
public partial class Orders
{
public System.Guid Id { get; set; }
public bool IsActive { get; set; }
public System.DateTime OrderDate{ get; set; }
public Nullable<int> CustomerId { get; set; }
public string LanguageId { get; set; }
public Nullable<System.Guid> UserId { get; set; }
public string CountryId { get; set; }
public virtual Customer Customer{ get; set; }
public virtual User User{ get; set; }
public virtual Country Country { get; set; }
public virtual Language Language { get; set; }
}
You should disable Lazy loading in configuration
public class YourContext : DbContext
{
public YourContext ()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
Or only during your call
using (var _storeEntities = new StoreEntities())
{
_storeEntities .Configuration.LazyLoadingEnabled = false;
var test = _storeEntities.Orders.Where(r => r.Id.ToString() == orderId)
.Include(x => x.User).ToList();
}
Here is the MSDN article for every type of loading
https://msdn.microsoft.com/en-nz/data/jj574232.aspx
Disable lazy loading:
_storeEntities.Configuration.LazyLoadingEnabled = false;
In constructor or before the query you want to run.
Or just remove virtual keyword.
Also you can disable proxy creation which is used for lazy loading:
_storeEntities.Configuration.ProxyCreationEnabled = false;
I have a Question entity in my web application. A user can like and bookmark a question. Similar to the way Likes function in Facebook, if a question is liked or bookmarked, the question should have undo-like or undo-bookmark links instead of like and bookmark links, when the existing questions are listed.
To achieve this, first I think I will need to define List<Question> LikedQuestions property for User entity, and List<User> LikedBy property for Question entity, and define Many-to-Many relationship between User and Question entity.
Then, it will be easy to retrieve the liked questions per user. However, my goal is the retrieve the all questions posted in the system, and include undo-like (or undo-bookmark) links if the question is liked by the current user. I guess I can achieve this by using LinQ to check if each question is in the List<Question> LikedQuestions. But, this seems to be a poor design and may lead to very low performances.
I also thought about adding properties such as IsLikedByCurrentUser IsBookmarkedByCurrentUser to Question entity. Is this doable?
What would be the best approach to achieve this?
UPDATE
Here is my current model:
public abstract class Like
{
[Key]
public int LikeItemId { get; set; }
[Required]
public DateTime WhenLiked { get; set; }
[ForeignKey("WhoLiked")]
public string UserId { get; set; }
public Person WhoLiked { get; set; }
}
public class PostLike : Like
{
[Required]
public Post Post { get; set; }
[ForeignKey("Post")]
public int PostId { get; set; }
}
public class CommentLike : Like
{
[ForeignKey("Comment")]
public int CommentId { get; set; }
[Required]
public Comment Comment { get; set; }
}
public class PostBookmark
{
public int PostBookmarkId { get; set; }
[Required]
public string BookmarkedBy { get; set; }
[ForeignKey("BookmarkedBy")]
public Person Person { get; set; }
[Required]
public int BookmarkedPostId { get; set; }
[ForeignKey("BookmarkedPostId")]
public Post BookmarkedPost { get; set; }
[Required]
public DateTime WhenBookmared { get; set; }
}
public class Post
{
public int PostId { get; set; }
[Required]
public string Title { get; set; }
public List<Comment> CommentsAssociated { get; set; }
public List<PostLike> PostLikes { get; set; }
public List<PostBookmark> PostBookmarks { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
[Required]
public string CommentText { get; set; }
[Required]
public Post PostAssociated { get; set; }
public List<CommentLike> CommentLikes { get; set; }
}
Here is the fluent API to define the relationships:
modelBuilder.Entity<Like>()
.HasRequired(l => l.WhoLiked)
.WithMany(p => p.Likes)
.HasForeignKey(l => l.UserId);
modelBuilder.Entity<CommentLike>()
.HasRequired(cl => cl.Comment)
.WithMany(cm => cm.CommentLikes)
.HasForeignKey(cl => cl.CommentId);
modelBuilder.Entity<PostLike>()
.HasRequired(pl => pl.Post)
.WithMany(p => p.PostLikes)
.HasForeignKey(pl => pl.PostId);
modelBuilder.Entity<PostBookmark>()
.HasRequired(pb => pb.Person)
.WithMany(p => p.PostBookmarks)
.HasForeignKey(pb => pb.BookmarkedBy);
modelBuilder.Entity<PostBookmark>()
.HasRequired(pb => pb.BookmarkedPost)
.WithMany(p => p.PostBookmarks)
.HasForeignKey(pb => pb.BookmarkedPostId);
how about:
//Question Model:
[NotMapped]
public bool IsLikedByCurrentUser {get;set;}
The NotMapped attribute will keep this field out of the DB but still allow you to use it as part of your model.
Then when doing your binding or selecting:
Select(x => new {
//other properties
x.IsLikedByCurrentUser = x.LikedBy.Contains(User.Id)
})
This is pseudo code obviously, but I believe the Contains gets converted to a SQL in () statement. As long as LikedBy is a foreign property of a collection of user ids.
If you provide more of your model I'll update my answer.
Update
This is how I would do it:
public class Post
{
public int PostId { get; set; }
[Required]
public string Title { get; set; }
[NotMapped]
public bool IsLikedByCurrentUser {get;set;}
public virtual ICollection<Comment> CommentsAssociated { get; set; }
public virtual ICollection<PostLike> PostLikes { get; set; }
public virtual ICollection<PostBookmark> PostBookmarks { get; set; }
}
Controller
public ActionResults Index() {
var posts = _db.Posts.Select(x => new Post {
Title = x.Title,
IsLikedByCurrentUser = x.PostLike.Select(y => y.UserId).Contains(User.Id)
});
return View();
}
I'm still doing this from memory so you might have to make some adjustments.
Update 2
Something else you could do that might be a better practice is to create a "ViewModel" type of class that has the IsLikedByCurrentUser property instead of using a NotMapped column. The select statement would then be
Select(x => new PostVidewModel {
Title = x.Title,
IsLikedByCurrentUser = x.PostLike.Select(y => y.UserId).Contains(User.Id)
});