EF7 Load Child of entity in List - entity-framework

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();

Related

Entity Framework Core 5.0 - Many to many select query

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.

ASP.NET Core cannot sort grouped items

I have the following model in my ASP.NET Core application:
public class LocationTypeGroup {
public string Name { get; set; }
public IEnumerable<LocationType> LocationTypes { get; set; }
}
public class LocationType
{
[Key]
public int LocationTypeID { get; set; }
public string Name { get; set; }
public string IntExt { get; set; }
}
I am trying to run a query that groups them by IntExt, and sorts by Name within each group.
The following works, but doesn't sort:
public async Task<List<LocationTypeGroup>> GetGroupedLocationTypes()
{
return await _context.LocationTypes
.GroupBy(p => p.IntExt)
.Select(g => new LocationTypeGroup
{
Name = g.Key,
LocationTypes = g.Select(x => x)
})
.OrderBy(x=>x.Name)
.ToListAsync();
}
If I change to this:
LocationTypes = g.Select(x => x).OrderBy(x => x)
Then I still do not get a sorted result.
What am I doing wrong?
It's possible that EF can't build SQL query.
So you need simplify it manually. and split to 2 queries:
var groups = await context.LocationTypes
.GroupBy(p => p.IntExt)
.ToListAsync();
return groups.Select(g => new LocationTypeGroup
{
Name = g.Key,
LocationTypes = g.Select(x => x)
})
.OrderBy(x=>x.Name);
The first query loads simply groups, and the second sorts them and converts to LocationTypeGroup.
May be it caused by too old version of Entity Framework Core. Try this approach, moreover it will be less expensive:
//data is loaded into memory
var data = await _context.LocationTypes.ToListAsync();
//data's transform
var answer = data.GroupBy(x => x.IntExt)
.Select(x => new LocationTypeGroup
{
Name = x.Key,
LocationTypes = x.AsEnumerable()
}).OrderBy(x => x.Name).ToList();

Entity Framework Many To Many exception with inheritance

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.

AutoMapping an Enum to an Entity Framework 5.0 complex type error

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!

EF Code First CTP5 Null child Object handling

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; }
}