This is my first time using GroupJoin. From the examples I have seen it seems pretty straightforward to use in its basic form but I always get a NavigationExpandingExpressionVisitor exception when I use it. Here, an example:
[Table("Users")]
public class WAUser
{
public int Id { get; set; }
//TODO: Unique key
[NotNull]
public string UserUuid { get; set; }
[DefaultValue(true)]
public bool NotifyOnlineState { get; set; }
[DefaultValue(true)]
public bool NotifyOfflineState { get; set; }
}
public class WASubscription
{
public int Id { get; set; }
public string PackageIdentifier { get; set; } //Product package indentifier
public DateTime? ExpiresAt { get; set; } //When the subscription or trial expires
public bool Expired { get; set; }
public bool IsTrial { get; set; }
public int PhoneCount { get; set; } //Number of phones this subscriptions supplies
public int UserId { get; set; }
public WAUser User { get; set; }
}
var userSubscriptions = await dbContext.Users
.GroupJoin(dbContext.Subscriptions,
u => u.Id,
s => s.UserId,
(u, subscriptions) => new
{
User = u,
Subscriptions = subscriptions
})
.ToListAsync();
The Exception thrown:
Unhandled exception. System.InvalidOperationException: Processing of
the LINQ expression 'DbSet
.GroupJoin(
outer: DbSet,
inner: u => u.Id,
outerKeySelector: s => s.UserId,
innerKeySelector: (u, subscriptions) => new {
User = u,
Subscriptions = subscriptions
})' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See
https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed
information.
Glad you asked.
The problem is that EF Core team doesn't (and does not want to) provide GroupJoin translation. See my comments on this EF Core GitHub thread Query: Support GroupJoin when it is final query operator #19930 and linked discussions where I was trying to convince them to include such support (which should have been extremely easy for what they do to support LINQ left outer join pattern for instance). So please go there and vote, otherwise the argument is that it "has no value".
With that being said, with current EF Core either add and use collection navigation property (preferable), or use correlated subquery instead of GroupJoin, e.g. replace
.GroupJoin(dbContext.Subscriptions,
u => u.Id,
s => s.UserId,
(u, subscriptions) => new
{
User = u,
Subscriptions = subscriptions
})
with
.Select(u => new
{
User = u,
Subscriptions = dbContext.Subscriptions.Where(s => u.Id == s.UserId) // <--
})
Related
When executing the last line of the code below I hit a client evaluation error. How do I prevent this from happening?
IQueryable<Models.Data> dataIQ = _context.Data
.Include(d => d.Quotes).ThenInclude(q => q.Owner)
.Include(d => d.Location).ThenInclude(l => l.State)
//This works
dataIQ = dataIQ.OrderBy(d => d.Quotes.FirstOrDefault().QuoteName);
//This also works
dataIQ = dataIQ.OrderBy(d => d.Location.State.StateName);
//This throws the client evaluation error
dataIQ = dataIQ.OrderBy(d => d.Quotes.FirstOrDefault().Owner.OwnerName);
For reference, this is what the classes looks like:
public class Data
{
public int DataId { get; set; }
public string DataName { get; set; }
public int LocationId { get; set; }
public Models.Location Location { get; set; }
public IList<Models.Quote> Quotes { get; set; }
}
public class Quote
{
public int QuoteId { get; set; }
public string QuoteName { get; set; }
public int DataId { get; set; }
}
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
public int StateId { get; set; }
public Models.State State { get; set; }
}
Error text:
InvalidOperationException: The LINQ expression '{QUERY TEXT}' could
not be translated. Either rewrite the query in a form that can be
translated, or switch to client evaluation explicitly by inserting a
call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for
more information.
This is a continuation of the question asked in this post. The original issue was resolved, so breaking this into its own post
Hmm... one thing that seems to work is switching to the Max function. I think the problem is there's no translation to a SQL command for FirstOrDefault, but there is one for Max. I don't really understand why this works if you're accessing a property of the child and not a sub-child though. Interested to see if anyone has other suggestions for how to address this, or if I'm doing something dumb here
//changing to this seems to work
dataIQ = dataIQ.OrderBy(d => d.Quotes.Max(q => q.Owner.OwnerName));
I also don't expect this to work if there's a many-to-one relationship, but right now mine is one-to-one. If anyone has suggestions on what to do for many-to-one I'd be interested to hear those as well!
I am using Entity Framework and this is my view model:
public class UserDetailsModel:CityModel
{
public int Id { get; set; }
public string Fullname { get; set; }
}
public class VendorInCategoryModel
{
public int CategoryId { get; set; }
public int VendorId { get; set; }
public virtual CategoryMasterModel CategoryMaster { get; set; }
public virtual UserDetailsModel UserDetails { get; set; }
}
public class CategoryMasterModel
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
This is my query to fetch vendor details along with category details of particular vendor say v001:
UserDetailsModel workerDetails = context.UserDetails.
Where(d => d.Id == _vendorId).
Select(d => new UserDetailsModel
{
Id = d.Id,
Fullname = d.Fullname,
CategoryId = d.VendorInCategory.Select(v => v.CategoryId).FirstOrDefault(),
}).SingleOrDefault();
Here I have used FirstOrDefault to fetch categoryId (that is single value)
But I don't want to use FirstOrDefault as I have used in so many queries and it is giving me wrong output in some cases. So that the reason why I don't want to use FirstOrDefault.
When I have written SingleOrDefualt in place of FirstOrDefault it is throwing me error
that use FirstOrDefault.
So how to overcome this? Can anybody please help me?
It looks like maybe your outer select is capable of returning multiple results (e.g. if there are more than one UserDetailsModel with the same Id). If it returns multiple results then your call to .SingleOrDefault() will throw an exception as it expects only a single result or no results. See LINQ: When to use SingleOrDefault vs. FirstOrDefault() with filtering criteria for more details.
I am working with the EF6 and I am a big fan of the dynamic proxies, which enables lazy loading and change tracking. Anyway I am not happy, that the lazy loading is triggered once the property is accessed instead of loading the data, when the enumerator or the count property is called first. Therefore I tried to diesable the proxys and replace them by custom proxies. It was an easy thing to use a custom object context and overload the CreateObject method. Unfortantly the ObjectMaterialized event cannot replace the entity and I am not able to replace an entity from a query. The creation of the object lies deep in internal classes of the framework.
Has anybody an idea how to use custom proxies? Or how I am able to replace the entities materialized in an object query?
You should .Include the properties you want to fetch so that you avoid an N+1 query problem.
public class User
{
public int Id { get; set; }
public string Name { get; set ;}
public virtual ICollection<Post> Posts { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set ; }
public int AuthorId { get; set; }
public virtual User Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string Note { get; set ;}
public int PostId { get; set; }
public virtual Post Post { get; set; }
public int AuthorId { get; set; }
public virtual User Author { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
Then this is BAD in that it'll do tons of queries:
using (var db = new BlogContext())
{
var user = db.Users.Single(u => u.Id=5)); // +1 query
foreach (var post in user.Posts) // N queries
{
var message = String.Format("{0} wrote {1}", user.Name, post.Title);
Console.WriteLine(message);
foreach (var comment in post.Comments) // N * M queries!
{
// and that .Author make N * M MORE!
var message = String.Format("\t{0} commented {1}", comment.Author.Name, comment.Note);
Console.WriteLine(message);
}
}
}
And this is GOOD in that it'll do one query:
using (var db = new BlogContext())
{
var user = db.Users
.Single(u => u.Id=5))
.Include(u => u.Posts) // eliminates the N post queries
.Include(u => u.Posts.Comments) // eliminates the M comment queries
.Include(u => u.Posts.Comments.Author); // eliminates the M comment author queries
foreach (var post in user.Posts) // N queries
{
var message = String.Format("{0} wrote {1}", user.Name, post.Title);
Console.WriteLine(message);
foreach (var comment in post.Comments) // N * M queries!
{
// and that .Author make N * M MORE!
var message = String.Format("\t{0} commented {1}", comment.Author.Name, comment.Note);
Console.WriteLine(message);
}
}
}
I'm trying to create a single linq query which populates the following models in the CompanyViewModel constructor:
public class CompanyViewModel
{
public IList<CompanyUserViewModel> CompanyUsers { get; set; }
...
}
public class CompanyUserViewModel
{
public User User { get; set; }
public IList<UserOperationViewModel> UsersOperations { get; set; }
}
public class UserOperationViewModel
{
public Operation Operation { get; set; }
public int Permission { get; set; }
}
Currently I've got the following query:
return db.Users.Where(u => u.CompanyId == companyId)
.Select(u => new CompanyUserViewModel {
User = u,
UsersOperations = db.UsersInOperations
.Where(uo => uo.UserId == uo.UserId)
.Select(uo => new UserOperationViewModel{
Operation = uo.Operation,
Permission = uo.Permission
}).ToList()
}).ToList();
Which builds, but when the page runs I get
LINQ to Entities does not recognize the method 'System.Collections.Generic.List`1[WoodCo.Models.BusinessObject.UserOperationViewModel] ToList[UserOperationViewModel](System.Collections.Generic.IEnumerable`1[WoodCo.Models.BusinessObject.UserOperationViewModel])' method, and this method cannot be translated into a store expression.
What does one do?
Change your view model properties to use IEnumerable<T> instead of IList<T and remove the .ToList() calls.
I'm using Entity Framework 4 CTP5 Code First and I have a model along the lines of:
public class User {
public int UserId { get; set; }
public string Email { get; set; }
public ICollection<Customer> TaggedCustomers { get; set; }
}
public class Customer {
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<User> TaggedBy { get; set; }
}
There is a many to many relationship where a User can 'tag' a Customer and a Customer can be 'tagged' by many users. I have a working DbContext and I can query customers using
var customers = DbContext.Customers.Include(c => c.TaggedBy);
But each customer will have all users that have tagged the customer. How do I restrict the TaggedBy collection to just result with a specifed UserId?
I've tried along the lines of DbContext.Customers.Include(c => c.TaggedBy.Select(x => x.Id == userId)); but that throws an error.
EF Feature CTP5: Fluent API Samples - ADO.NET team blog - Site Home - MSDN Blogs
modelBuilder.Entity<Product>()
.HasMany(p => p.Tags)
.WithMany(t => t.Products)
.Map(m =>
{
m.MapLeftKey(p => p.ProductId, "CustomFkToProductId");
m.MapRightKey(t => t.TagId, "CustomFkToTagId");
});
Code First Mapping Changes in CTP5 - ADO.NET team blog - Site Home - MSDN Blogs
modelBuilder.Entity<Product>()
.HasMany(p => p.SoldAt)
.WithMany(s => s.Products)
.Map(mc => {
mc.ToTable("ProductsAtStores");
mc.MapLeftKey(p => p.Id, "ProductId");
mc.MapRightKey(s => s.Id, "StoreId");
});
Mark your collections as virtual and then you can easily do:
public class User
{
public int UserId { get; set; }
public string Email { get; set; }
public virtual ICollection<Customer> TaggedCustomers { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<User> TaggedBy { get; set; }
}
using(var context = new MyDbContext())
{
var user = context.Users.Single(o => o.UserId == 0);
var customers = user.TaggedCustomers;
}
Results in much cleaner code in my opinion.