I have fairly simple question about many to many relationships using entity framework.
Situation looks like this I have 3 models SectionName:
public class SectionName : BaseEntity
{
public SectionName()
{
SectionsSuffix = new List<SectionSuffix>();
}
[Required]
public string Name { get; set; }
public ICollection<SectionSuffix> SectionsSuffix { get; set; }
}
Section Suffix:
[Table("SectionsSuffix")]
public class SectionSuffix : BaseEntity
{
public SectionSuffix()
{
SectionLines = new List<SectionLine>();
SectionsName = new List<SectionName>();
}
[Required]
public string Name { get; set; }
public ICollection<SectionLine> SectionLines { get; set; }
public ICollection<SectionName> SectionsName { get; set; }
}
And SectionLines:
[Table("SectionLines")]
public class SectionLine : BaseEntity
{
public SectionLine()
{
SectionsSuffix = new List<SectionSuffix>();
}
[Required]
public string Name { get; set; }
public ICollection<SectionSuffix> SectionsSuffix { get; set; }
}
Now SectionsName is related by many to many to SectionsSuffix and that is related with many to many to SectionLines, in context using FluentApi and junction tables:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<SectionName>()
.HasMany(suffix => suffix.SectionsSuffix)
.WithMany(name => name.SectionsName)
.Map(nameSuffix =>
{
nameSuffix.ToTable("SectionsNameSuffix");
nameSuffix.MapLeftKey("SectionNameId");
nameSuffix.MapRightKey("SectionSuffixId");
});
modelBuilder.Entity<SectionSuffix>()
.HasMany(line => line.SectionLines)
.WithMany(suffix => suffix.SectionsSuffix)
.Map(nameSuffix =>
{
nameSuffix.ToTable("SectionsSuffixLines");
nameSuffix.MapLeftKey("SectionSuffixId");
nameSuffix.MapRightKey("SectionLinesId");
});
}
Now, if with no problem when I call SectionsNames I can get SectionsSuffix list, I want to with this one call get also list of SectionNames binged to specific SectionSuffix, is it possible?
For now using repository pattern procedure looks like this:
IList<SectionName> sections = SectionRepository.GetAll(x => x.SectionsSuffix).ToList();
public virtual IEnumerable<T> GetAll(params Expression<Func<T, object>>[] includes)
{
IQueryable query = includes.Aggregate(_dbSet.AsQueryable(), (current, include) => current.Include(include));
return (IEnumerable<T>) query;
}
If I would query sth. like you want to do, it would be like that:
using System;
using System.Data.Entity;
public class SectionRepository
{
private readonly _context;
public SectionRepository(IMyDbContext context)
{
_context = context
}
public ICollection<SectionName> GetAll()
{
return _context.SectionNames
.Include(sn => sn.SectionsSuffix.SectionLine)
.Select(sn => sn).ToList();
}
}
Please have a try, the code above is not tested.
Answer was fairly simple, i would need to use:
IList<SectionName> sections = SectionRepository.GetAll(name => name.SectionsSuffix,
name => name.SectionsSuffix.Select(suffix => suffix.SectionLines)).ToList();
public virtual IEnumerable<T> GetAll(params Expression<Func<T, object>>[] includes)
{
IQueryable query = includes.Aggregate(_dbSet.AsQueryable(), (current, include) => current.Include(include));
return (IEnumerable<T>) query;
}
Related
I'm using EF Core for my project. And I have a problem with nested query in EF Core.
I have 2 classes:
public class PermissionGroupDefinitionEntity : IEntity
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public string DisplayName { get; set; }
public virtual ICollection<PermissionDefinitionEntity> PermissionDefinitions { get; set; }
}
public class PermissionDefinitionEntity : IEntity
{
public string Name { get; set; }
public string NormalizedName { get; set; }
public string DisplayName { get; set; }
public bool IsEnabled { get; set; }
public virtual string GroupName { get; set; }
public virtual PermissionGroupDefinitionEntity Group { get; set; }
public virtual ICollection<PermissionDefinitionEntity> Children { get; set; }
}
and this is the ApplicationDbContext:
builder.Entity<PermissionDefinitionEntity>().HasOne(r => r.Group).WithMany(r => r.PermissionDefinitions).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PermissionDefinitionEntity>().HasOne(r => r.Parent).WithMany(r => r.Children).OnDelete(DeleteBehavior.Cascade);
I want query all PermissionGroupDefinitionEntity included PermissionDefinitionEntity and self referencing of PermissionDefinitionEntity.
Can I do that with EF Core?
You need to recursively load PermissionDefinitions that placed in the PermissionGroupDefinitionEntity.
First, you should load all PermissionGroupDefinitionEntities including its children using the following query :
var query = _dbContext.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(p => p.PermissionDefinitions )
.ThenInclude(p => p.Children)
.ToListAsync();
Since every PermissionGroupDefinitionEntity has a list of PermissionDefinition you need a nested loops like this code :
foreach (var PermissionGroupDefinitionEntity in PermissionGroupDefinitionEntities)
{
foreach (var PermissionDefinitions in PermissionDefinitions)
{
}
}
Then in the inner loop you should call your recursive function.
See following link (sample for get all children recursively in Entity Framework Core)
https://patrickdesjardins.com/blog/how-to-load-hierarchical-structure-with-recursive-with-entity-framework-5
This way has terrible performance and I don't recommend that.
In this case it's seems you must write a stored procedure in SQL for better performance.
You can use .ThenInclude(i => ...) like so
var query = _context.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(i => i.PermissionDefinitions)
.ThenInclude(i => i.Group)
.AsQueryable();
Edit:
var query = _context.PermissionGroupDefinitionEntity
.AsNoTracking()
.Include(i => i.PermissionDefinitions)
.ThenInclude(i => i.Children)
.AsQueryable();
Suppose I have following entities
public abstract class BaseEntity {
public Guid Id { get;set; }
public string Prop1 { get;set; }
public long Prop2 { get;set; }
public byte Type_Id { get;set; }
}
public class Type1 : BaseEntity { }
public class Type2 : BaseEntity { }
public class Type3 : BaseEntity {
public long? Prop3 { get;set; }
}
And following context mapping:
builder.ToTable("Entities").HasDiscriminator(a => a.Type_Id)
.HasValue<Type1>((byte)Types.Type1)
.HasValue<Type2>((byte)Types.Type2)
.HasValue<Type3>((byte)Types.Type3);
// in DbContext
public DbSet<BaseEntity> Entities { get; set; }
I want to create get IQueryable from DB (all records) Type1 and Type2 will have null in Prop3 ,
I do the following:
public DbSet<BaseEntity> DBSet { get;set; }
private static readonly MethodInfo FromSqlMethodInfo = typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethods("FromSql").Single(mi => mi.GetParameters().Length == 3);
public IQueryable<Type3> GetEntities(IEnumerable<Guid> ids) {
RawSqlString sql = #"select [Id]
,[Prop1]
,[Prop2]
,[Prop3]
,[Type_Id]
from [dbo].[Entities] where Id in (select item from #Ids)";
var ids = new SqlParameter("#Ids", SqlDbType.Structured);
ids.TypeName = typeof(Guid).Name.ToLowerInvariant() + "_item_list";
ids.Direction = ParameterDirection.Input;
ids.Value = CreateItemList(tpIds);
var param = new object[] { ids };
var conversion = from s in DBSet select (Type3)s;
var result = conversion.Provider.CreateQuery<Type3>(Expression.Call(null, FromSqlMethodInfo.MakeGenericMethod(typeof(Type3)), conversion.Expression,
Expression.Constant(sql), Expression.Constant(param)));
return result;
}
var query = GetEntities(someIds);
var result = query.OrderBy(m => m.Type_Id).Skip(skip).Take(take).ToList();
And query executes successfully, but when ToLIst is called, Exception is thrown:
Unable to cast object of type 'Type1' to type 'Type3', which is quite expected, since we not telling how it should be converted...so the question is: can such trick be done with EF Core ?
Ok, so for now only possible solution is to create one more entity with all properties required,
public class NewEntity {
public Guid Id { get;set; }
public string Prop1 { get;set; }
public long Prop2 { get;set; }
public byte Type_Id { get;set; }
public long? Prop3 { get;set; }
}
add new DbSet to context
public DbSet<NewEntity> NewEntities { get; set; }
Map it to old table and update context configuration properly,
to avoid redundant columns generation (in this scenario EF will probaly
try to add new columns with names NewEntity_Prop1 , NewEntity_Prop2 etc..
as well as Foreign keys and indexes
public override void Configure(EntityTypeBuilder<NewEntity> builder) {
builder.ToTable("Entities");
builder.Property(m => m.Prop1).HasColumnName("Prop1");
builder.Property(m => m.Prop2).HasColumnName("Prop2");
builder.Property(m => m.Type_Id).HasColumnName("Type_Id");
builder.HasOne<BaseEntity>().WithOne().HasForeignKey<NewEntity>(e => e.Id);
}
Or create View
public DbQuery<NewEntity> NewEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Query<NewEntity>().ToView("NewEntities");
base.OnModelCreating(modelBuilder);
}
You can use implicit Overload Cast Operators. Here is a link to help you.
Hello I have the next entities:
public class Area
{
public ICollection<Indicator> Indicators { get; set; }
}
public class Indicator
{
public Operator GreenCondition { get; set; }
}
public class Operator
{
public string Name { get; set; }
}
And in the logic I have:
public Area Get(int id)
{
List<Expression<Func<Area, object>>> includes = new List<Expression<Func<Area, object>>>();
includes.Add(x => x.Indicators);
includes.Add(x => x.Indicators.Select(i => i.GreenCondition));
includes.Add(x => x.Managers);
Area area = repAreas.Get(includes, a => a.Id == id && a.Active);
return area;
}
And in my logic of repository I have:
public T Get(IEnumerable<Expression<Func<T, object>>> includes, Func<T, bool> predicate)
{
var query = _objectSet.AsQueryable<T>();
if(!(includes is null))
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query.Where(predicate).FirstOrDefault();
}
But when is going to execute the return line it gives error that can't do the selected part.
Basically what I want is to show everyting from an Area using generic and reflection.
I used to generate all the helper tables like UsergroupUsers for many-to-many relations or relational Ids in POCO myself but now I want EF to take care of them. Now I don't think it's such a good idea after all.
Problem
When I try to get all UsergroupDynamicField for particular user it generates N+1 query for every usergroup user is in.
Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.
Code
public class User
{
...
public virtual ICollection<Usergroup> Usergroups { get; set; }
public IEnumerable<UserField> Fields
{
get
{
var fields = this.Usergroups.SelectMany(x => x.UsergroupDynamicFields); // N + 1 for every Usergroup
foreach (var field in fields)
{
yield return new UserField
{
Name = field.Name
};
}
}
}
}
Database
Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.
But the class that ends up implementing ICollection is EntityCollection<T>. This collection has a CreateSourceQuery() function that you can use:
var usergroupsQuery = ((EntityCollection<UserGroup>)this.Usergroups).CreateSourceQuery();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);
Update: as pointed out in the comments, ICollection<T> will only be implemented using EntityCollection<T> when change tracking is possible and enabled (non-sealed classes, and all relevant properties virtual). You can create a query another way:
var usergroupsQuery = db.Entry(this).Collection(u => u.Usergroups).Query();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);
Note that this requires that you have access to db somehow.
I try with something like
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
and it seems to be ok. I use EF5.
Of course... this is not a method in the User class. It requires to be able to invoke Include method on a DbSet object.
I hope this may help.
full solution
public class User {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<UserGroup> Groups { get; set; }
}
public class UserGroup {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<UserGroupDynamicField> DynFields { get; set; }
}
public class UserGroupDynamicField {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual UserGroup Group { get; set; }
}
public class UserGroupDynFEFCFConfiguration : EntityTypeConfiguration<UserGroupDynamicField > {
public UserGroupDynFEFCFConfiguration()
: base() {
HasRequired(x => x.Group);
}
}
public class UserGroupEFCFConfiguration : EntityTypeConfiguration<UserGroup> {
public UserGroupEFCFConfiguration()
: base() {
HasMany(x => x.Users).WithMany(y => y.Groups);
}
}
public class TestEFContext : DbContext {
public IDbSet<User> Users { get; set; }
public IDbSet<UserGroup> Groups { get; set; }
public TestEFContext(String cs)
: base(cs) {
Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new UserGroupDynFEFCFConfiguration());
modelBuilder.Configurations.Add(new UserGroupEFCFConfiguration());
}
}
class Program {
static void Main(String[] args) {
String cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext c = new TestEFContext(cs)) {
UserGroup g1 = new UserGroup {
Name = "G1",
DynFields = new List<UserGroupDynamicField> {
new UserGroupDynamicField { Name = "DF11"},
new UserGroupDynamicField { Name = "DF12"}
}
};
c.Groups.Add(g1);
UserGroup g2 = new UserGroup {
Name = "G2",
DynFields = new List<UserGroupDynamicField> {
new UserGroupDynamicField { Name = "DF21"},
new UserGroupDynamicField { Name = "DF22"}
}
};
c.Groups.Add(g2);
c.Users.Add(new User {
Name = "U1",
Groups = new List<UserGroup> { g1, g2 }
});
c.SaveChanges();
}
using (TestEFContext c = new TestEFContext(cs)) {
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
foreach (var v in res) {
Console.WriteLine(v.Name);
}
}
}
}
I have the following model
public class BaseEntity
{
public virtual int EntityID { get;set; }
public virtual bool Active { get;set; }
}
public class Brand : BaseEntity
{
public Brand()
{
Series = new List<Series>();
}
public virtual string BrandName { get;set; }
public virtual string Website { get;set; }
public virtual IList<Series> Series;
}
public class Series: BaseEntity
{
public Series()
{
Products = new List<Product>();
}
public virtual Brand Brand { get;set; }
public virtual string SeriesName { get;set; }
public virtual string SeriesDescription { get;set; }
public virtual IList<Product> Products;
}
public class Product : BaseEntity
{
public Product()
{
Series = new List<Series>();
}
public virtual Series Series { get;set; };
public virtual string ProductName { get;set; }
public virtual string ProductCode { get;set; }
public virtual string ProductDescription { get;set; }
public virtual double SellingPrice { get;set; }
}
I have set all mappings to not lazy load.
public class BrandMapping() : ClassMap<Brand>
{
public BrandMapping()
{
Table("Brand");
Id(item => item.EntityID).Column("BrandID").GeneratedBy.Increment();
Map(item => item.BrandName).Not.LazyLoad();
Map(item => item.Active).Not.LazyLoad();
Map(item => item.Website).Not.LazyLoad();
HasMany<Series>(item => item.Series).Cascade.All().Not.LazyLoad();
}
}
public class SeriesMapping() : ClassMap<Series>
{
public SeriesMapping()
{
Table("Series");
Id(item => item.EntityID).Column("SeriesID").GeneratedBy.Increment();
Map(item => item.SeriesName).Not.LazyLoad();
Map(item => item.SeriesDescription).Not.LazyLoad();
Map(item => item.Active).Not.LazyLoad();
HasMany<Product>(item => item.Products).Cascade.All().Not.LazyLoad();
Reference<Brand>(item => item.Brand).Column("BrandID");
}
}
public class ProductMapping() : ClassMap<Product>
{
public ProductMapping()
{
Table("Product");
Id(item => item.EntityID).Column("ProductID").GeneratedBy.Increment();
Map(item => item.ProductName).Not.LazyLoad();
Map(item => item.ProductDescription).Not.LazyLoad();
Map(item => item.ProductCode).Not.LazyLoad();
Map(item => item.SellingPrice).Not.LazyLoad();
Map(item => item.Active).Not.LazyLoad();
Reference<Series>(item => item.Series).Column("ProductID");
}
}
I have a generic Repository base that has the following code for the Load method so I don't need to create code for each typed Repo:
IEnumerable<T> Load()
{
IList<T> results = new List<T>();
StartTransaction();
results = _session.CreateCriteria(typeof(T)).List<T>();
CommitTransaction();
return results;
}
My issue is that one of my screens displays products. This screen needs to display the Series and Brand names for completeness in the grid. I have found that even with lazy loading disabled, the Series is not even loaded, never mind the Brand record. I need to find a way to add the SetFetchMode to the above load code to ensure that all relationship trees (Product->Series->Brand) are loaded when a record is loaded from the DB.
Anyone got ideas how I can do generic SetFetchmode?
It would be easiest to specify eager loading in the mapping files for each entity, this is done by adding the Fetch specification.
If you are using Fluent nHibernate then this should do it:
References(x => x.Series).Column("SeriesId").ForeignKey("Id").Fetch.Join();
If you put this inside your Product mapping it tells nHiberate to eager load the Series whenever a Product is loaded.
Likewise put something similar in your Series to load the Brand.