I have the following mapping:
.ForMember(dest => dest.AskerAverageRating, opt => opt.MapFrom(src =>
src.ReviewsReceived != null?
((float)src.ReviewsReceived.Sum(r => r.Rating) / (float)src.ReviewsReceived.Count()) : 0))
This alone:
(float)src.ReviewsReceived.Sum(r => r.Rating)
Works.
This alone:
(float)src.ReviewsReceived.Count()
Also works.
Devided however, they give the following error:
System.InvalidOperationException: Nullable object must have a value.
Why???
Here are the relevant Properties and entities:
This is the destination
public float AskerAverageRating { get; set; }
These is the source:
public ICollection<Review> ReviewsReceived { get; set; }
This is Review:
public class Review
{
..
public int Rating { get; set; }
..
..And I found nothing funky in the DB.
You are missing parentheses around src.ReviewsReceived != null.
Please find working solution here: https://dotnetfiddle.net/gaGZp0
Related
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.
//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)
I am trying to create two many-to-many relationship maps on a Record object:
Record object that is inherited from
public class Record {
public virtual ICollection<Language> SourceLanguages { get; set; }
public virtual ICollection<Language> TargetLanguages { get; set; }
}
Second Object
public class Language
{
public int Language { get; set; }
public string Locale { get; set; }
public string LanguageName { get; set; }
public virtual ICollection<Record> Records { get; set; }
}
Map for Record
public class RecordMap : EntityTypeConfiguration<Record>
{
this.HasMany(r => r.SourceLanguages)
.WithMany(c => c.Records)
.Map(sl =>
{
sl.ToTable("SourceLanguageRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("LanguageId");
});
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
}
When I run migration on the object listed above I get the following error:
System.Data.Entity.Core.MetadataException: Schema specified is not
valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available. ...
Schema specified is not valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available.
If I comment the following line out, it will work with just one many to many map, however, it will add RecordId_Record to Language Table. Any idea why?
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
Any idea as to what I am doing wrong?
If you have 2 Many-to-Many relationships to the same table you need to create 2 separate ICollection properties in order for Entity Framework to fully pick up on what you're trying to do. You can't combine them into one, or else you'll get that lovely error that you're seeing there.
So the error is this: Expression must resolve to top-level member and not any child object's properties.
Remuneration in the DTO is a Enum. ContractEntity uses a Remuneration that is a ComplexType.
The code throwing the error:
Mapper.CreateMap<ContractDTO, ContractEntity>()
.ForMember(d => d.Remuneration.ContractType, s => s.MapFrom(z => z.ContractType))
.ForMember(d => d.Remuneration.Currency, s => s.MapFrom(z => z.Currency))
.ForMember(d => d.Remuneration.RateUnit, s => s.MapFrom(z => z.RateUnit));
Entity Framework complex type:
[ComplexType]
public class Remuneration
{
public decimal Amount { get; set; }
public int Currency { get; set; }
public int RateUnit { get; set; }
public int ContractType { get; set; }
}
Because I want the destination (ContractEntity) to use the integer values, I thought I could just cast the source enum to the destination integer like this:
.ForMember(d => d.Remuneration.ContractType, s => s.MapFrom(z => (int)z.ContractType))
.. obviously I cant, and was hoping that someone could clarify why this doesn't work..
If you have problmes with complex type mapping, you can implement manual mapping based on AutoMapper:
Mapper.CreateMap<PatternDto, PatternModel>().ConvertUsing(pattern =>
{
if(pattern == null) return new PatternModel();
return new PatternModel
{
EmailPattern = pattern.EmailPattern,
SmsPattern = pattern.SmsPattern
};
});
Good luck!
Okay, I've been staring at the screen for a couple of hours here and have no idea why I am getting this error. I've used Code First on a number of other projects and have had no problem with this before...
Here is the error:
System.InvalidOperationException was unhandled by user code
Message=The properties expression 'sci => sci.ShoppingCartItemId' is not valid. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. When specifying multiple properties use an anonymous type: C#: 't => new { t.MyProperty1, t.MyProperty2 }' VB.Net: 'Function(t) New From { t.MyProperty1, t.MyProperty2 }'.
Source=EntityFramework
StackTrace:
at System.Data.Entity.ModelConfiguration.Utilities.ExpressionExtensions.GetSimplePropertyAccessList(LambdaExpression propertyAccessExpression)
at System.Data.Entity.ModelConfiguration.EntityTypeConfiguration`1.HasKey[TKey](Expression`1 keyExpression)
at BillingPlatform.DataLayer.BillingDb.OnModelCreating(DbModelBuilder modelBuilder) in [somepath]\BillingDb.cs:line 57
at System.Data.Entity.Internal.LazyInternalContext.CreateModelBuilder()
at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
InnerException:
Here is the code that's throwing the error. The first line:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ShoppingCartItem>().HasKey(sci => sci.ShoppingCartItemId);
modelBuilder.Entity<Product>().HasKey<Guid>(p => p.ProductId);
modelBuilder.Entity<DependentItemType>().HasKey<Guid>(dit => dit.DependentItemTypeId);
modelBuilder.Entity<ProductCategory>().HasKey<Guid>(pc => pc.ProductCategoryId);
base.OnModelCreating(modelBuilder);
}
Here is just the ShoppingCartItem class as a reference:
namespace BillingPlatform.Libraries
{
public class ShoppingCartItem
{
/// <summary>
/// The unique identifier of this shopping cart item.
/// </summary>
public Guid ShoppingCartItemId { get; set; }
public Product Product { get; set; }
public decimal Price { get; set; }
public decimal Tax { get; set; }
public Guid UserId { get; set; }
public bool InCart { get; set; }
public string ProductData { get; set; }
public DependentItemType DependentItemType { get; set; }
public string DependentItemId { get; set; }
}
}
Does anyone understand why Entity Framework would throw this error? My lambda expression:
modelBuilder.Entity<ShoppingCartItem>().HasKey(s => s.ShoppingCartItemId);
is super simple. I don't see what could be going wrong... Thank you for any help you can give!
Okay the problem was that my class members were originally just fields. Code First expects properties. After making the code change and rebuilding, I was still getting the same error. But once Visual Studio was forced to push the updated DLLs, everything worked fine.
Not the case here (landed here from a Google search), but might be worth posting that this exact exception can also occur if HasKey uses an anonymous type that defines its own field names:
HasKey(t => new { KeyField1 = t.KeyField1, KeyField2 = t.KeyField2 });
Should be fixed to look like this:
HasKey(t => new { t.KeyField1, t.KeyField2 });