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.
Related
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;
}
I have a mapping like:
public class Order
{
public int Id {get;set;}
public virtual ICollection<Item> Items {get;set;}
}
public class Item
{
public int Id;
public int OrderId;
}
I mapped them using:
public class OrderConfiguration : EntityTypeConfiguration<Order>
{
public OrderConfiguration()
{
this.HasKey(x => x.Id);
this.HasMany(x => x.Items).WithOptional().HasForeignKey(v => v.OrderId);
}
}
But now I want to be able to do this (for read-only operations, I won't make updates to this):
someItem.Order
How can I change my mapping to allow for this?
Also, if I load say 100 items, is it possible to eager load the Order graph for all of them?
public class Item
{
public int Id;
public int OrderId;
public virtual Order Order { get; set; }
}
I don't understand what you mean by graph. But you could do this
context.Items.Include(e => e.Order).Where(e => filter100(e)).ToList();
That should eager load Orders as well.
I have the following data structure:
//property Notification
abstract class BindableBase { }
//base class for all tenant-scoped objects
abstract class TenantModelBase : BindableBase
{
int TenantId;
}
abstract class Order : TenantModelBase
{
Customer Customer; //works: mapped using TenantId and CustomerId
Product Product; //again, works with TenantId and ProductId
string ProductId;
string CustomerId;
}
class Customer: TenantModelBase
{
string CustomerId;
}
class Product : TenantModelBase
{
string ProductId;
}
class SpecialOrder : Order
{
OtherClass OtherClass; //this fails!, see below
string OtherClassId;
}
class SuperSpecialOrder : SpecialOrder { }
class OtherClass : TenantModelBase
{
string OtherClassId;
}
I get the following error:
The foreign key component 'TenantId' is not a declared property on
type 'SpecialOrder'. Verify that it has not been explicitly excluded
from the model and that it is a valid primitive property.
Error occurs using the Fluent Api Configuration:
config.HasRequired(p => p.OtherClass)
.WithMany(oc => oc.SpecialOrders)
.HasForeignKey(p => new { p.TenantId, p.OtherClassId});
Without the OtherClass reference in SpecialOrder I can create objects freely without problems (including SpecialOrder, SuperSpecialOrder etc).
Anyone have a clue what's going on? I'm lost here :(
Edit
I've seen in other questions that people remove the TenantId from tables, this is not an option since primary keys are not unique across tenants and we want to keep the shared data architecture.
I know the workaround is having a second TenantId in the SpecialOrder class, but this does not seem logical to me.
Are you trying to do a TPT, where there's a separate table for Order/Tenant? If so, I think the other poster is correct, and there's a bug in EF.
If customers/products are your based mapped classes, this might work for you.
What happened to me when I created your database is it tried to map the abstract Order class.
By adding:
modelBuilder.Ignore<Order>();
The builder kept the mapped properties due to the MapInheritedProperties, but didn't create the table so that all the FK's were correctly created.
I'm assuming you wanted separate tables for each of your classes like the related post above, and to not map your abstract table.
Entire Model:
public abstract class BindableBase { }
//base class for all tenant-scoped objects
public abstract class TenantModelBase : BindableBase
{
[Key]
public virtual int TenantId { get; set; }
}
public abstract class Order : TenantModelBase
{
public Customer Customer { get; set; } //works: mapped using TenantId and CustomerId
public Product Product { get; set; } //again, works with TenantId and ProductId
public string ProductId { get; set; }
public string CustomerId { get; set; }
}
public class Customer : TenantModelBase
{
[Key]
public string CustomerId { get; set; }
}
public class Product : TenantModelBase
{
[Key]
public string ProductId { get; set; }
}
public class SpecialOrder : Order
{
[Key]
public int SpecialOrderId { get; set; }
public OtherClass OtherClass { get; set; } //this fails!, see below
public string OtherClassId { get; set; }
}
public class SuperSpecialOrder : SpecialOrder { }
public class OtherClass : TenantModelBase
{
public string OtherClassId { get; set; }
public ICollection<SpecialOrder> SpecialOrders { get; set; }
}
public class Model : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<SpecialOrder> SpecialOrders { get; set; }
public DbSet<SuperSpecialOrder> SuperSpecialOrders { get; set; }
public DbSet<OtherClass> OtherClasses { get; set; }
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<OtherClass>()
.HasKey( k => new { k.TenantId, k.OtherClassId } );
modelBuilder.Entity<Customer>()
.HasKey( k => new { k.TenantId, k.CustomerId } );
modelBuilder.Entity<Product>()
.HasKey( k => new { k.TenantId, k.ProductId } );
modelBuilder.Entity<SpecialOrder>()
.Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "SpecialOrders" );
} );
modelBuilder.Entity<SpecialOrder>().HasKey( k => new { k.TenantId, k.SpecialOrderId } );
modelBuilder.Entity<SuperSpecialOrder>()
.Map( m =>
{
m.MapInheritedProperties();
m.ToTable( "SuperSpecialOrders" );
} )
.HasKey( k => new { k.TenantId, k.SpecialOrderId } );
modelBuilder.Entity<SpecialOrder>()
.HasRequired( p => p.OtherClass )
.WithMany( o => o.SpecialOrders )
.HasForeignKey( p => new { p.TenantId, p.OtherClassId } );
modelBuilder.Entity<Order>()
.HasRequired( o => o.Customer )
.WithMany()
.HasForeignKey( k => new { k.TenantId, k.CustomerId } );
modelBuilder.Entity<Order>()
.HasRequired( o => o.Product )
.WithMany()
.HasForeignKey( k => new { k.TenantId, k.ProductId } );
modelBuilder.Ignore<Order>();
}
}
Created Database:
Hope this helps.
Im going to take a guess here as i saw an unusual bug in a blog Julie Lermann did with ef4.1
The namespace in the query caused an issue.
Just to quickly test if this bug is your issue, change the namespace of all objects to be the same.
Namespace xyz // same as the dbcontext and and entities
public class OtherClass{}
Quick test. If it isnt, sorry for wasting your time.
public class Category
{
public virtual int Id { set; get; }
public virtual string Name { set; get; }
public virtual int CategoryOrder { set; get; }
public virtual IEnumerable<News> LatestNews { set; get; }
}
public sealed class CategoryMap :ClassMap<Category>
{
public CategoryMap()
{
LazyLoad();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.CategoryOrder);
HasMany(x => x.LatestNews);
}
}
IRepository<Category> newsRepo = new NHibernateRepository<Category>();
using(var session = newsRepo.GetSessionFactory().OpenSession())
using(var transaction = session.BeginTransaction())
{
var result = session.Query<Category>().OrderBy(x => x.CategoryOrder);
transaction.Commit();
}
I have this category class Which I want to display a (only one) News per category. Is this correct mapping? or should i change it to Map
When i run this, it gets all the news per category. But i want the latest news per category (only one). I can get the latest news by querying News.DateUpdated.
How should i change the query to get one news per category?
or how do I get some of the News? ie: limit the number of news I can query?
I think you can have your cake and eat it too. You have a list of News items, so just leave that. But add in another property that gets just the News item you want.
public class Category
{
public virtual int Id { set; get; }
public virtual string Name { set; get; }
public virtual int CategoryOrder { set; get; }
public virtual IEnumerable<News> AllNews { set; get; }
public virtual News LatestNews
{
get
{
// probably needs some work
return this.AllNews.OrderByDescending(n => n.SomeDateField).Take(1);
}
}
}
public sealed class CategoryMap :ClassMap<Category>
{
public CategoryMap()
{
LazyLoad();
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.CategoryOrder);
HasMany(x => x.AllNews);
}
}
you can use lazy mode extra to achieve this,
public News GetLatestNews(Category cat)
{
var session = SessionFactory.CurrentSession;
var news = session.CreateFilter(cat.LatestNews , "order by categoryorder").SetFirstResult((page - 1) * pageSize).SetFirstResult(0).SetMaxResults(1).List().FirstOrDefault();
return news;
}
however if you try to access the LatestNews collection directly, this won't be of any use as it will load all the news objects associated with Category from db which would lower the performance.
Is there a way I can setup mapping for a table that doesn't have a direct reference to another table? It actually gets it's reference from another table that I do have a direct reference from.
This is what I have so far, but I'm not sure how to map the "LookupValue" in my MetaData model. It would need to map to MetaData if the [mdd].DefinitionType equals the [mdl].LookupType and the [md].DataValue equals the [mdl].LookupKey.
public class MetaData {
public virtual long TableID { get; set; }
public virtual MetaDataDefinition Definition { get; set; }
public virtual int DefinitionID { get; set; }
public virtual String DataValue { get; set; }
public virtual MetaDataLookup LookupValue { get; set; }
public override bool Equals(object obj) { ... }
public over int GetHashCode() { ... }
}
public class MetaDataDefinition {
public virtual long ID { get; set; }
public virtual string DefinitionName { get; set; }
public virtual string DefinitionType { get; set; }
}
public class MetaDataLookup {
public virtual string Type { get; set; }
public virtual string LookupKey { get; set; }
public virtual string LookupValue { get; set; }
public override bool Equals(object obj) { ... }
public over int GetHashCode() { ... }
}
public class MetaDataMap : ClassMap<MetaData> {
public MetaDataMap() {
Table("PPOMetaData");
CompositeId()
.KeyProperty(x => x.TableID, "TableID")
.KeyProperty(x => x.DefinitionID, "DefinitionID");
References(x => x.Defintion, "DefinitionID").Not.LazyLoad().Cascade.All().Fetch.Join();
Map(x => x.TableID);
Map(x => x.DataValue);
}
}
public class MetaDataDefinitionMap : ClassMap<MetaDataDefinition> {
public MetaDataDefinitionMap() {
Table("MetaDataDefinitions");
Id(x => x.ID);
Map(x => x.DefinitionName);
Map(x => x.Type);
}
}
public class MetaDataLookupMap : ClassMap<MetaDataLookup> {
public MetaDataLookupMap() {
CompositeId()
.KeyProperty(x => x.LookupType)
.KeyProperty(x => x.LookupKey);
Map(x => x.LookupValue);
}
}
Ideally, I want to have it run a query similar to this:
SELECT data.TableID, data.DefinitionID, def.DefinitionName, data.DataValue,lu.LookupValue AS DataValue
FROM dbo.PPOMetadata AS data
INNER JOIN dbo.MetaDataDefinitions AS def ON def.ID = data.DefinitionID
LEFT OUTER JOIN dbo.MetaDataLookup AS lu ON lu.LookupType = def.Type AND lu.LookupKey = data.DataValue
WHERE data.TableID = 1
In terms of update ability, the only thing I would ever create, update or delete would be in the MetaData table. The definitions and Lookup values would never change (at least from this part of the application). Is mapping the "MetaDataLookup" directly to the MetaData model possible? If so, can someone point me in the right direction of what I should be looking at?
Thanks!
I came up with a workaround that seems to be working and might take some of the complexity out. Instead of trying to handle the complex joins in a ClassMap, I built a view in Sql Server that does this for me. In my application, I built a new Model and ClassMap for the view. I haven't implemented any update logic yet, but I think I'll have the update logic work directly on the MetaData model, while the read logic (which needs everything joined together) will use the new MetaDataView model.
I'm still curious if complex joins like this are possible in Fluent NHibernate, but for now this solution seems to be working for me.