I have a BaseScript, in the Script there is a GetAll which not load related entities.
In the DbContext there is LazyLodingEnabled disabled (false).
Following some Code to understand my issue. In this example it doesn't load the user and messageViews when I want to get the messages by language.
BaseScript
public abstract class BaseScript<TEntity> where TEntity : EntityBase
{
public virtual IEnumerable<TEntity> GetAll()
{
using (EntityDbContext ctx = new EntityDbContext())
{
return ctx.Set<TEntity>().ToList();
}
}
}
MessageScript
public class MessageScript : BaseScript<Message>
{
public IEnumerable<Message> GetAllByLanguagePublic(string language)
{
return this.GetAllPublic().Where(x => x.Language == language).ToList();
}
}
Message entity:
public class Message : EntityBase
{
public string Text { get; set; }
public string Language { get; set; }
public virtual User User { get; set; }
public Guid UserId { get; set; }
public virtual ICollection<MessageView> MessageViews { get; set; }
}
I suppose your question is "Why aren't User and MessageViews loaded?" If that is the case, you are answering yourself. If you disable lazy loading, you have to load these properties explicitly, using Include (for example):
Maybe in your BaseScript.GetAll you want to return an IQueryable instead of a IEnumerable so the query will not be exectued yet (do not call ToList()). Then, in your GetAllByLanguagePublic method you could do this:
public class MessageScript : BaseScript<Message>
{
public IEnumerable<Message> GetAllByLanguagePublic(string language)
{
return this.GetAll()
.Include(x => x.User)
.Include(x => x.MessageViews)
.Where(x => x.Language == language).ToList();
}
}
Note that you need to include System.Data.Entity to use Include with lambda expression.
Related
I've implemented a generic spec pattern for my generic repo, but I don't know how I can add a .ThenInclude() to the code.
FYI - I have 3 entities (User->PracticedStyles->YogaStyles) and when I go to fetch my User I want to fetch all the YogaStyles he/she practices (ex. bikram, vinyasa, etc). But I can't get the YogaStyle entities, I can fetch all the PracticedStyle entities for the User because it's only one entity deep, but I can't figure out how to fetch/include the YogaStyle entity from each PracticedStyle.
I'm using a generic specification pattern with a generic repository pattern and I've created an intermediate table to hold all the styles, maybe this is wrong or I don't know how to use the generic spec pattern correctly?
public class User : IdentityUser<int>
{
public ICollection<PracticedStyle> PracticedStyles { get; set; }
}
public class PracticedStyle : BaseEntity
{
public int UserId { get; set; }
public User User { get; set; }
public int YogaStyleId { get; set; }
public YogaStyle YogaStyle { get; set; }
}
public class YogaStyle : BaseEntity
{
public string Name { get; set; } // strength, vinyasa, bikram, etc
}
Here is my controller and the methods the controller calls from
[HttpGet("{id}", Name = "GetMember")]
public async Task<IActionResult> GetMember(int id)
{
var spec = new MembersWithTypesSpecification(id);
var user = await _membersRepo.GetEntityWithSpec(spec);
if (user == null) return NotFound(new ApiResponse(404));
var userToReturn = _mapper.Map<MemberForDetailDto>(user);
return Ok(userToReturn);
}
public class MembersWithTypesSpecification : BaseSpecification<User>
{
public MembersWithTypesSpecification(int id)
: base(x => x.Id == id)
{
AddInclude(x => x.UserPhotos);
AddInclude(x => x.Experience);
AddInclude(x => x.Membership);
AddInclude(x => x.PracticedStyles);
// doesn't work - yogastyles is not a collection
// AddInclude(x => x.PracticedStyles.YogaStyles);
AddInclude(x => x.InstructedStyles);
}
}
Here is the 'AddInclude' from BaseSpecification
public class BaseSpecification<T> : ISpecification<T>
{
public BaseSpecification()
{
}
public BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
protected void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
}
Here is the getEntityWithSpec
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
and spec evaluator
public class SpecificationEvaluator<TEntity> where TEntity : class // BaseEntity // when using BaseEntity, we constrain it to on base entities
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> spec)
{
var query = inputQuery;
if (spec.Criteria != null)
{
query = query.Where(spec.Criteria); // e => e.YogaEventTypeId == id
}
if (spec.OrderBy != null)
{
query = query.OrderBy(spec.OrderBy);
}
if (spec.OrderByDescending != null)
{
query = query.OrderByDescending(spec.OrderByDescending);
}
if (spec.IsPagingEnabled)
{
query = query.Skip(spec.Skip).Take(spec.Take);
}
query = spec.Includes.Aggregate(query, (current, include) => current.Include(include)); // 'current' represents entity
return query;
}
}
I figured out what I needed.
Following this link
I needed to add
AddInclude($"{nameof(User.PracticedStyles)}.{nameof(PracticedStyle.YogaStyle)}");
and
query = specification.IncludeStrings.Aggregate(query,
(current, include) => current.Include(include));
and
public List<string> IncludeStrings { get; } = new List<string>();
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
and that allowed me to use .thenInclude(), but as a series of strings.
Here is a solution. Make your AddInclude methods return something like ISpecificationInclude:
public interface ISpecificationInclude<TFrom, TTo>
where TFrom : IEntity
where TTo : IEntity
// I know that you do not have a `IEntity` interface, but I advise you to
// add it to your infrastructure and implement it by all your entity classes.
{
ISpecificationInclude<TTo, TAnother> ThenInclude<TAnother>(Expression<Func<TTo, TAnother>> includeExpression);
ISpecificationInclude<TTo, TAnother> ThenInclude<TAnother>(Expression<Func<TTo, IEnumerable<TAnother>>> collectionIncludeExpression);
}
Implement this interface appropriately. The implementation should be a wrapper around a single "include" expression. You probably need two implementations: one for wrapping a collection include and one for a simple object include.
The Includes property of BaseSpecification class should be a collection of this interface.
In your SpecificationEvaluator, process your Includes, and all the ThenIncludes they may have, recursively.
I know that it's lots of code, but I'm afraid there is no other way :)
I'm trying to add a new sub-entity, product component ProductRevComp to an existing entity ProductRev. However when I retrieve an instance of the ProductRev class, the Comps collection is never populated (even when explicitly Including() it). I BELIEVE I have mapped everything correctly, but it has taken more fiddling than I want and this is the most likely place for a mistake to be hiding. However profiling the SQL statements show the relevent columns are being populated with the correct data.
Checking db.ProductRevComps (i.e. the DbSet of all my comps) shows the records can be loaded, and that mapping is working as expected.
Mappings:
public class ProductRevConfiguration : EntityTypeConfiguration<ProductRev>
{
public ProductRevConfiguration()
{
HasKey(p => p.ProductRevId);
HasMany(p => p.Comps).WithRequired().HasForeignKey(p => p.ParentProductRevId);
Ignore(p => p.ProgrammedParts);
}
}
public class ProductRevCompConfiguration : EntityTypeConfiguration<ProductRevComp>
{
public ProductRevCompConfiguration()
{
HasKey(p => new { p.ParentProductRevId, p.CompProductRevId });
HasRequired(p => p.ParentProductRev).WithMany().HasForeignKey(p => p.ParentProductRevId);
HasRequired(p => p.CompProductRev).WithMany().HasForeignKey(p => p.CompProductRevId);
}
}
Product entity (amazingly simplified):
public class ProductRev
{
public string ProductRevId { get; set; }
public virtual List<ProductRevComp> Comps { get; set; }
public virtual List<ProductRevComp> ProgrammedParts { get { return Comps; } }//Will be filtered once I get this working
public ProductRev() { }
}
Comp entity:
public class ProductRevComp
{
public string ParentProductRevId { get; set; }
public virtual ProductRev ParentProductRev { get; set; }
public string CompProductRevId { get; set; }
public virtual ProductRev CompProductRev { get; set; }
public int CompTypeValue { get; set; }
public ProductRevCompType CompType
{
get { return (ProductRevCompType)CompTypeValue; }
set { CompTypeValue = (int)value; }
}
public enum ProductRevCompType { ProgrammedPart = 1 };
public ProductRevComp() { }
public override string ToString()
{
return base.ToString();
}
}
Removing the extra prog parts collection doesn't change anything.
How can I get the ProductRev entity to populate the Comps property without resorting to a manual DB hit?
(Must run as the office is closing and I don't have a key - I hope I have included all details, please comment if anything is missing.)
I'm trying to setup a simple inheritance scenario with EF 4.3.1 using code first approch and fluent configuration.
I've created an abstract base type 'A' with a one-to-one navigation property and an inherited class 'AA' also with a one-to-one navigation property has following :
public abstract class A
{
public Guid ID { get; set; }
public B ChildB { get; set; }
}
public class AA : A
{
public C ChildC { get; set; }
}
public class B
{
public Guid ID { get; set; }
public A Parent { get; set; }
}
public class C
{
public Guid ID { get; set; }
public AA Parent { get; set; }
}
public class AConfiguration : EntityTypeConfiguration<A>
{
public AConfiguration()
{
this.HasRequired(o => o.ChildB)
.WithRequiredPrincipal(o => o.Parent);
this.Map(o =>
{
o.ToTable("A");
});
}
}
public class AAConfiguration : EntityTypeConfiguration<AA>
{
public AAConfiguration()
{
this.HasRequired(o => o.ChildC)
.WithRequiredPrincipal(o => o.Parent);
this.Map(o =>
{
o.ToTable("AA");
});
}
}
public class BConfiguration : EntityTypeConfiguration<B>
{
public BConfiguration()
{
this.HasRequired(o => o.Parent)
.WithRequiredDependent(o => o.ChildB);
this.Map(o =>
{
o.ToTable("B");
});
}
}
public class CConfiguration : EntityTypeConfiguration<C>
{
public CConfiguration()
{
this.HasRequired(o => o.Parent)
.WithRequiredDependent(o => o.ChildC);
this.Map(o =>
{
o.ToTable("C");
});
}
}
public class DataContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add<A>(new AConfiguration());
modelBuilder.Configurations.Add<AA>(new AAConfiguration());
modelBuilder.Configurations.Add<B>(new BConfiguration());
modelBuilder.Configurations.Add<C>(new CConfiguration());
}
public DbSet<AA> AASet { get; set; }
public DbSet<B> BSet { get; set; }
public DbSet<C> CSet { get; set; }
}
When I try to get my data back with the first level of navigation property, it works as expected :
... dataContext.AASet.Include("ChildB") ...
But when I try to include the navigation property of the inherited type like following :
... dataContext.AASet.Include("ChildC") ...
I get an EntityCommandCompilationException at runtime with the following inner exception message :
The ResultType of the specified expression is not compatible with the
required type. The expression ResultType is
'Transient.reference[...A]' but the required type is
'Transient.reference[...AA]'. Parameter name: arguments[0]
Has anybody encountered a similar issue ?
I am probably missing something but I can't see what's wrong with this sample.
What can I do to get my model works as expected ?
No, you don't miss anything. Actually you ran into an old Entity Framework bug. Your second query can be written like this:
var result = dataContext.ASet.OfType<AA>().Include("ChildC").ToList();
(when you replace your DbSet AASet by ASet).
For this type of eager loading of one-to-one mapped children on inherited types this article applies: http://weblogs.asp.net/johnkatsiotis/archive/2010/04/28/huge-ef4-inheritance-bug.aspx
The bug has been reported long time ago here: https://connect.microsoft.com/VisualStudio/feedback/details/544639/ef4-inheritance-defined-using-queryview-doesnt-work-properly-with-association
The bug still exists in EF 4.3.1. But Microsoft has announced in this thread that the bug is fixed in .NET 4.5 ( = EF 5.0).
The code would work if the relationship is one-to-many instead of one-to-one. Lazy or explicit loading would work as well (also with one-to-one relationship), I believe:
var result = dataContext.ASet.OfType<AA>().ToList();
foreach (var item in result)
dataContext.Entry(item).Reference(a => a.ChildC).Load();
But this will generate multiple queries. If you don't have performance problems with multiple queries I would prefer the last workaround - until you can migrate to EF 5.0.
I'm trying to get Fluent NHibernate to map a collection for me. My class definitions are as follows:
public abstract class Team
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class ClientTeam : Team
{
public virtual IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Identifiers { get; set; }
}
My mappings:
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
Table("Team");
Id(x => x.Id).GeneratedBy.Assigned();
Map(t => t.TeamName);
}
}
public class ClientTeamMap : SubclassMap<ClientTeam>
{
public ClientTeamMap()
{
HasMany(t => t.Clients);
}
}
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
Table("Client");
Id(c => c.Id);
Map(c => c.Name);
Map(c => c.Identifiers);
}
}
I've built a unit test that instantiates a team and then attempts to persist it (the test base has dependency configuration, etc. in it):
public class TeamMapTester : DataTestBase
{
[Test]
public void Should_persist_and_reload_team()
{
var team = new ClientTeamDetail
{
Id = Guid.NewGuid(),
TeamName = "Team Rocket",
Clients = new[]
{
new ClientDetail {ClientName = "Client1", ClientIdentifiers = "1,2,3"}
}
};
using (ISession session = GetSession())
{
session.SaveOrUpdate(team);
session.Flush();
}
AssertObjectWasPersisted(team);
}
}
When I run the test, I get this error:
SetUp : FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
Database was not configured through Database method.
----> NHibernate.MappingException: Could not compile the mapping document: (XmlDocument)
----> NHibernate.PropertyNotFoundException : Could not find field '_clients' in class 'ClientTeam'`
I've looked through the NHibernate documentation and done some google searching, but I can't find anything that appears to address this issue. The documentation for Fluent NHibernate's Referencing methods explicitly uses auto properties, so I'm sure that's not the issue.
Why might NHibernate think that _clients is the field it should map in this case?
And the reason turns out to be: Conventions.
The Fluent mappings were set up to try to enforce read-only collection properties, by requiring a backing field. The ICollectionConvention in question:
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
instance.Fetch.Join();
instance.Not.LazyLoad();
instance.Access.CamelCaseField(CamelCasePrefix.Underscore);
}
}
which requires that collection backing fields be camelCased and start with an underscore.
Help me, please, solve one problem.
I have project, which uses Nhibernate and Fluent Nhibernate. There I created one base class
(it is not real classes, but they describe my situation):
public class Document
{
public virtual int Id { get; private set; }
public virtual Account Acc { get; private set; }
}
And mapping for it:
public class DocumentMap: ProfileEntityMap<Document>
{
public DocumentMap()
{
Id(m => m.Id);
References(m => m.Acc);
DiscriminateSubClassesOnColumn("Type");
}
}
Then I implemented subclass:
public class PaymentDocument: Document
{
public virtual Card AccountCard { get; set;}
}
Mapping for class PaymentDocument:
public class PaymentDocumentMap : SubclassMap<PaymentDocument>
{
public PaymentDocumentMap()
{
References(t => t.AccountCard);
}
}
And after that I try execute this query:
payments = session.Query<PaymentDocument>()
.Fetch(t => t.Acc)
.Fetch(t => t.AccountCard)
.ToList();
And when I insert first fetch I get next exception:
Object reference not set to an instance of an object.
Can somebody answer me where is a problem?
Actually it was a bug fixed in 3.0.0.Alpha2. Right now it works with the trunk.