Entity Framework include collection/reference where - entity-framework

I'm trying to select the latest version of all my clients and load each object with the latest version of their respective payments and the payments respective segment name.
It's a .net Core 2.0 project.
The controller is using:
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using CBFU.Data;
using CBFU.Models;
using Microsoft.EntityFrameworkCore;
A client is created with no foreign keys.
A payment is created with af the foreign keys: ClientId and SegmentId.
A segment is created with no foreign keys.
I'm thinking something like:
var clients = _context.Clients
.Where(client => client.IsLatest == 1)
.Include(client => client.Payments
.Select(payment => payment.IsLatest == 1)
.Select(payment => payment.Segment))
.ToList();
But that does not work. Below I've listed af few of the things I tried and if it worked. I've no examples with .ThenInclude as my intelliSense does not respond to it.
// 1 This works: Loading payments into clients
var clients = _context.Clients
.Where(client => client.IsLatest == 1)
.Include(client => client.Payments)
.ToList();
// 2 This does NOT work: Loading payment with IsLatest == 0 into clients
var clients = _context.Clients
.Where(client => client.IsLatest == 1)
.Include(client => client.Payments
.Select(p => p.IsLatest == 1))
.ToList();
// 3 This does NOT work: Loading segment into payments into clients
var clients = _context.Clients
.Where(client => client.IsLatest == 1)
.Include(client => client.Payments
.Select(p => p.Segment == 1))
.ToList();
Both 2 and 3 gives the same error:
The property expression 'client => {from Payment payment in client.Payments select ([payment].IsLatest == 1)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
Source =< Cannot evaluate the exception source>
My classes looks as follows:
public class Client
{
public int Id { get; set; }
public int IsLatest { get; set; }
public ICollection<Payment> Payments { get; set; }
}
public class Payment
{
public int Id { get; set; }
public Client Client { get; set; }
[Required]
[Display(Name = "Client")]
public int ClientId { get; set; }
public Segment Segment { get; set; }
[Required]
[Display(Name = "Segment")]
public int SegmentId { get; set; }
public int IsLatest { get; set; }
}
public class Segment
{
public int Id { get; set; }
public string Name { get; set; }
}

Filtered Includes were never supported in pre EF Core, and (as of current v2.0) are still not supported by EF Core. EF Core 2.0 introduced Model-level query filters, but they apply for all queries and have to be specifically turned off when not needed. Also are not flexible enough to handle dynamic query level filtering.
What you can do though is to utilize a combination of the so called navigation property fixup, eager loading and filtering loaded entities techniques described in the Loading Related Data section of the documentation:
var clientQuery = _context.Clients
.Where(client => client.IsLatest == 1);
var clients = clientQuery.ToList();
clientQuery
.SelectMany(c => c.Payments)
.Include(p => p.Segment)
.Where(p => p.IsLatest == 1)
.Load();

Related

Self referential join with Entity Framework Core and .net core 3.1

I am trying to create a self-referential join in EF Core, and have found several tutorials, but I keep getting the following error:
System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
My model looks like (showing only parts of interest:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int SettingCollectionId { get; set; }
public int? ParentId { get; set; }
public ICollection<SettingValue> SettingValues { get; set; }
public virtual ICollection<SettingCollection> SubCollections { get; set; }
public virtual SettingCollection ParentCollection { get; set; }
The context has this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SettingCollection>()
.HasMany<SettingValue>(sc => sc.SettingValues);
modelBuilder.Entity<SettingCollection>()
.HasMany(sc => sc.SubCollections)
.WithOne(sc => sc.ParentCollection)
.HasForeignKey(sc => sc.ParentId);
}
In the controller, I need to pull the SettingValues(this works), but I also need to pull the SubCollections. I am getting this like so:
var resultList = await _context.SettingCollections
.Where(sc => sc.ParentCollection == null)
.Where(sc => identifiers.Contains(sc.Identifier))
.Include(sc => sc.SubCollections)
.Include(sc => sc.SettingValues).OrderBy(sc => sc.Priority)
.ToListAsync();
The line:
.Include(sc => sc.SubCollections)
causes this error to be thrown:
System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
Any ideas? Thanks!
try using Newtonsoft.Json in your project, in startup.cs:
services.AddMvc()
.AddNewtonsoftJson(
options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
// if not using .AddMvc use these instead
//services.AddControllers().AddNewtonsoftJson(...);
//services.AddControllersWithViews().AddNewtonsoftJson(...);
//services.AddRazorPages().AddNewtonsoftJson(...);

GroupBy Expression failed to translate

//Model
public class Application
{
[Key]
public int ApplicationId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ConfirmedDate { get; set; }
public DateTime IssuedDate { get; set; }
public int? AddedByUserId { get; set; }
public virtual User AddedByUser { get; set; }
public int? UpdatedByUserId { get; set; }
public virtual User UpdatedByuser { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string TRN { get; set; }
public string EmailAddress { get; set; }
public string Address { get; set; }
public int ParishId { get; set; }
public Parish Parish { get; set; }
public int? BranchIssuedId { get; set; }
public BranchLocation BranchIssued { get; set; }
public int? BranchReceivedId { get; set; }
public BranchLocation BranchReceived {get; set; }
}
public async Task<List<Application>> GetApplicationsByNameAsync(string name)
{
if (string.IsNullOrEmpty(name))
return null;
return await _context.Application
.AsNoTracking()
.Include(app => app.BranchIssued)
.Include(app => app.BranchReceived)
.Include(app => app.Parish)
.Where(app => app.LastName.ToLower().Contains(name.ToLower()) || app.FirstName.ToLower()
.Contains(name.ToLower()))
.GroupBy(app => new { app.TRN, app })
.Select(x => x.Key.app)
.ToListAsync()
.ConfigureAwait(false);
}
The above GroupBy expression fails to compile in VS Studio. My objective is to run a query filtering results by name containing a user given string and then it should group the results by similar TRN numbers returning a list of those applications to return to the view. I think I am really close but just cant seem to figure out this last bit of the query. Any guidance is appreciated.
Error being presented
InvalidOperationException: The LINQ expression 'DbSet<Application>
.Where(a => a.LastName.ToLower().Contains(__ToLower_0) || a.FirstName.ToLower().Contains(__ToLower_0))
.GroupBy(
source: a => new {
TRN = a.TRN,
app = a
},
keySelector: a => a)' 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()
UPDATE
Seems it is definitely due to a change in how .net core 3.x and EF core play together since recent updates. I had to change it to client evaluation by using AsEnumerable() instead of ToListAsync(). The rest of the query given by Steve py works with this method. I was unaware even after reading docs how the groupby really worked in LINQ, so that has helped me a lot. Taking the query to client side eval may have performance issues however.
The GroupBy support in EF core is a joke.
This worked perfectly on the server in EF6
var nonUniqueGroups2 = db.Transactions.GroupBy(e => new { e.AccountId, e.OpeningDate })
.Where(grp => grp.Count() > 1).ToList();
In EF core it causes an exception "Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side." The message is misleading, do not call AsEnumerable because this should be handled on the server.
I have found a workaround here. An additional Select will help.
var nonUniqueGroups = db.Transactions.GroupBy(e => new { e.AccountId, e.OpeningDate })
.Select(x => new { x.Key, Count = x.Count() })
.Where(x => x.Count > 1)
.ToList();
The drawback of the workaround is that the result set does not contain the items in the groups.
There is an EF Core issue. Please vote on it so they actually fix this.
Based on this:
I want to group by TRN which is a repeating set of numbers eg.12345, in the Application table there may be many records with that same sequence and I only want the very latest row within each set of TRN sequences.
I believe this should satisfy what you are looking for:
return await _context.Application
.AsNoTracking()
.Include(app => app.BranchIssued)
.Include(app => app.BranchReceived)
.Include(app => app.Parish)
.Where(app => app.LastName.ToLower().Contains(name.ToLower()) || app.FirstName.ToLower()
.Contains(name.ToLower()))
.GroupBy(app => app.TRN)
.Select(x => x.OrderByDescending(y => y.CreatedAt).First())
.ToListAsync()
.ConfigureAwait(false);
The GroupBy expression should represent what you want to group by. In your case, the TRN. From there when we do the select, x represents each "group" which contains the Enumarable set of Applications that fall under each TRN. So we order those by the descending CreatedAt date to select the newest one using First.
Give that a shot. If it's not quite what you're after, consider adding an example set to your question and the desired output vs. what output / error this here produces.
I experience a similar issue where I find it interesting and stupid at the same time. Seems like EF team prohibits doing a WHERE before GROUP BY hence it does not work. I don't understand why you cannot do it but this seems the way it is which is forcing me to implement procedures instead of nicely build code.
LMK if you find a way.
Note: They have group by only when you first group then do where (where on the grouped elements of the complete table => does not make any sense to me)

How to compare the value between the one from database and the client .Net Core 2

We have this edit Razor pages (edit.cshtml) which is extended from the following page model and it's very basic only include the PopulateRolesDropDownList:
public class RoleNamePageModel : PageModel
{
public SelectList RoleNameSL { get; set; }
public void PopulateRolesDropDownList(ApplicationDbContext _context,
object selectedRole = null)
{
var rolesQuery = from d in _context.Roles
orderby d.Name // Sort by name.
select d;
RoleNameSL = new SelectList(rolesQuery,
"RoleId", "Name", selectedRole);
}
}
Also in this Edit page, we added:
<input type="hidden" asp-for="User.UserRoles.ElementAt(0).RoleId" name="User.Current.RoleId" />
We also do the [BindProperty] in the code behind
public ApplicationUser User { get; set; }
We need to find out whether there is a change on this model. What is the approach to do this?
ENVIRONMENT:
.NET Core 2.2
Razor Pages
UPDATE - 1:
On the PostAsync, we made another call to the database:
var userRoleToUpdate = await _context.UserRoles
.FirstOrDefaultAsync(m => m.UserId == id.ToString());
We just need to compare this value with the change on a drop-down list or not. We could not work how.
UPDATE - 2:
We did change as per recommend by #NevilleNazerane below:
public class AssignClubUserViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid SelectedRoleID { get; set; }
}
[BindProperty]
public AssignClubUserViewModel AssignClubUser { get; set; }
and added OnGetAsync:
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
var user = await _context.Users
.Include(u => u.ClubApplicationUsers)
.FirstOrDefaultAsync(m => m.Id == id.ToString());
AssignClubUser.FirstName = user.FirstName;
AssignClubUser.LastName = user.LastName;
AssignClubUser.UserName = user.UserName;
AssignClubUser.SelectedClubID =
user.ClubApplicationUsers.ElementAt(0).ClubID;
....
Is this right? I got the error: NullReferenceException: Arg_NullReferenceException on line AssignClubUser.FirstName = user.FirstName;
UPDATE - 3:
Fixed by creating a ModemView and then on the OnGetAsync() for query ensure to mapped with the ModelView:
var user = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
SelectedClubID = t.ClubApplicationUsers.ElementAt(0).ClubID
}).SingleAsync();
Since you have a view model, I recommend you simplify your bindings and let your behind code handle the other functionalities. You can first make a SelectedRoleId property:
public int SelectedRoleId { get; set; }
In your view model, you can assign this property's default value to User.UserRoles.ElementAt(0).RoleId in either your constructor or your OnGet, based on how you need it set up. This way the drop down is bound to a simple property.
For binding dropdowns (HTML selects) .NET Core provides the asp-items tag helper.
<select asp-for="SelectedRoleId" asp-items="Model.RoleNameSL"></select>
In your OnPostAsync, you can use SelectedRoleId to access the selected value.

How to not assign an id when id is a fk with a custom id generator in ef

I have a project where I'm using EF5, I made a custom Guid Generator and I have an override of the SaveChanges method to assign the ids of my entities.
Everything is working fine except in one case: when the ID of one entity is a FK to another ID of another entity.
A little bit of code to explain the problem:
I have two entities I cannot change:
public class FixedEntityA
{
public Guid Id { get; set;}
public string SomeText { get; set; }
}
public class FixedEntityB
{
public Guid Id { get; set;}
public int OneInt { get; set; }
}
In my project I have an entity defined like this:
public class ComposedEntity
{
public Guid Id { get; set;}
public FixedEntityA FixedA { get; set; }
public FixedEntityB FixedB { get; set; }
public double OneDouble { get; set; }
}
The relationships are:
ComposedEntity may have 0 or 1 FixedEntityA
ComposedEntity may have 0 or 1 FixedEntityB
The constraints on the id are:
The Id of FixedEntityA is a FK pointing to the Id of ComposedEntity
The Id of FixedEntityB is a FK pointing to the Id of ComposedEntity
The mapping class are:
public ComposedEntity(): EntityTypeConfiguration<ComposedEntity>
{
HasOptional(fea => fea.FixedA).WithRequired();
HasOptional(feb => feb.FixedB).WithRequired();
}
Here is my SaveChanges override:
foreach (var entry in ChangeTracker.Entries<IEntity>().Where(e => e.State == EntityState.Added))
{
Type t = entry.Entity.GetType();
List<DatabaseGeneratedAttribute> info = t.GetProperty("Id")
.GetCustomAttributes(typeof (DatabaseGeneratedAttribute), true)
.Cast<DatabaseGeneratedAttribute>().ToList();
if (!info.Any() || info.Single().DatabaseGeneratedOption != DatabaseGeneratedOption.Identity)
{
if (entry.Entity.Id == Guid.Empty)
entry.Entity.Id = (Guid) _idGenerator.Generate();
}
}
return base.SaveChanges();
This code works fine everywhere for all kind of relationships except in this case, I am missing a test to make sure I'am not setting an id on id that are foreign keys, and I have no clue on how to check if an Id is a FK...
Here is a sample object where this code fails:
var fea = new FixedEntityA();
var feb = new FixedEntityB();
var composedEntity = new ComposedEntity();
composedEntity.FixedA = fea;
composedEntity.FixedB = feb;
If you insert the whole graph, all three objects are marked as Added and all Ids are default.
The problem is, with the current SaveChanges method, I will go through all object with the Added state in the change tracker and I will assign an Id to all entity with a default Guid and break my FK constraints.
Thanks in advance guys!
Here is some code that will get the FK properties for a given type (it's horrible I know). Should be simple enough to plug this into your code.
var typeName = "Category";
var fkProperties = ((IObjectContextAdapter)db)
.ObjectContext
.MetadataWorkspace
.GetItems<AssociationType>(DataSpace.CSpace)
.Where(a => a.IsForeignKey)
.Select(a => a.ReferentialConstraints.Single())
.Where(c => c.FromRole.GetEntityType().Name == typeName)
.SelectMany(c => c.FromProperties)
.Select(p => p.Name);

Using Entity Framework 4.0 with Code-First and POCO: How to Get Parent Object with All its Children?

I'm new to EF 4.0, so maybe this is an easy question. I've got VS2010 RC and the latest EF CTP. I'm trying to implement the "Foreign Keys" code-first example on the EF Team's Design Blog, http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx.
public class Customer
{
public int Id { get; set;
public string CustomerDescription { get; set;
public IList<PurchaseOrder> PurchaseOrders { get; set; }
}
public class PurchaseOrder
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public DateTime DateReceived { get; set; }
}
public class MyContext : ObjectContext
{
public RepositoryContext(EntityConnection connection) : base(connection){}
public IObjectSet<Customer> Customers { get {return base.CreateObjectSet<Customer>();} }
}
I use a ContextBuilder to configure MyContext:
{
var builder = new ContextBuilder<MyContext>();
var customerConfig = _builder.Entity<Customer>();
customerConfig.Property(c => c.Id).IsIdentity();
var poConfig = _builder.Entity<PurchaseOrder>();
poConfig.Property(po => po.Id).IsIdentity();
poConfig.Relationship(po => po.Customer)
.FromProperty(c => c.PurchaseOrders)
.HasConstraint((po, c) => po.CustomerId == c.Id);
...
}
This works correctly when I'm adding new Customers, but not when I try to retrieve existing Customers. This code successfully saves a new Customer and all its child PurchaseOrders:
using (var context = builder.Create(connection))
{
context.Customers.AddObject(customer);
context.SaveChanges();
}
But this code only retrieves Customer objects; their PurchaseOrders lists are always empty.
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
}
What else do I need to do to the ContextBuilder to make MyContext always retrieve all the PurchaseOrders with each Customer?
You could also use:
var customers = context.Customers.Include("PurchaseOrders").ToList();
Or enable LazyLoading in the ContextOptions :
context.ContextOptions.LazyLoadingEnabled = true;
Just be careful with deferred loading if you are serializing the objects or you may end up querying the entire database.
Well the solution turned out to be simple, as I suspected it might. I called the context.LoadProperty() method for each individual customer:
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
foreach (var customer in customers)
{
context.LoadProperty<Customer>(customer, c => c.PurchaseOrders);
}
return customers;
}