I would like to be able to add a collection of Notes to any of my main entities in my NHibernate application. I can see how you could do this with a seperate junction table per entity. However, I would like to be able to avoid this and only have one junction table - if this is possible.
Below is the code so far, however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity. What are the alternative approaches I need to take?
public class Entity
{
public virtual int Id { get; set; }
}
public class EntityType1 : Entity
{
public EntityType1()
{
Notes = new List<Note>();
}
public virtual string EntityTypeName { get; set; }
public virtual IList<Note> Notes {get;set;}
}
public class EntityType2 : Entity
{
public EntityType2()
{
Notes = new List<Note>();
}
public virtual string EntityType2Name { get; set; }
public virtual IList<Note> Notes { get; set; }
}
public class Note
{
public virtual int Id { get; set; }
public virtual IList<Entity> Entities { get; set; }
public virtual string NoteText { get; set; }
}
}
namespace FluentNHib.Mappings
{
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Id(m => m.Id);
}
}
public class EntityType1Map : ClassMap<EntityType1>
{
public EntityType1Map()
{
Id(m => m.Id);
Map(m => m.EntityTypeName1);
HasManyToMany(m => m.Notes).Table("EntityToNotes")
.ParentKeyColumn("EntityId")
.ChildKeyColumn("NoteId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class EntityType2Map : ClassMap<EntityType2>
{
public EntityType2Map()
{
Id(m => m.Id);
Map(m => m.EntityType2ame);
HasManyToMany(m => m.Notes).Table("EntityToNotes")
.ParentKeyColumn("EntityId")
.ChildKeyColumn("NoteId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class NoteMap : ClassMap<Note>
{
public NoteMap()
{
Id(m => m.Id);
Map(m => m.NoteText);
}
}
I am not sure what the real issue is:
...however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity...
Is the issue in lazy loading? or in fact that Entity1 and Entity2 can have same ID, therefore the references are mixed? (I expect that and this should be part of the answer below)
Anyhow, I would say that we can achieve what you need: map the Note with just one table EntityToNotes. And that is good.
But, in general, I would descourage you from using the many-to-many. It is just my own feeling, experience. Below are some links with more explanation:
Am I doing many to many incorrectly when using fluent nhibernate?
NHibernate how do you map a crossreference table to a bag?
Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships?
Draft of the SOLUTION:
So, firstly we have to extend the table "EntityToNotes" with two columns
EntityToNoteId column - we need a primary key for new pairing object
Discriminator column
The Discriminator column will be used for (almost like a standard inheritance)
inserting Discriminator value during creation
filtering te IList<Notes> per Entity
These could be the pairing Entity (with an abstract base gathering the common stuff)
public abstract class EntityToNote<TEntity>
{
public abstract string Discriminator { get; set; }
public virtual TEntity Entity {get;set;}
public virtual Note Note {get;set;}
}
// the pairing objects
public class EntityType1ToNote : EntityToNote<EntityType1>
{
string _discriminator = "EntityType1"; // here we set the discriminator
public virtual string Discriminator
{
get { return _discriminator; }
set { _discriminator = value; }
}
...
// Similar for other pairing objects
The Entities will now be referencing lists of pairing objects
public class EntityType1 : Entity
{
public virtual IList<EntityType1ToNote> Notes {get;set;}
...
public class EntityType2 : Entity
{
public virtual IList<EntityType2ToNote> Notes { get; set; }
...
Here is snippet of the mapping (all other Entities will have usual mapping, including ClassMaps for EntityType1ToNote, EntityType2ToNote...)
public class EntityType1Map : ClassMap<EntityType1>
{
public EntityType1Map()
{
Id(m => m.Id);
Map(m => m.EntityTypeName1);
HasMany(m => m.Notes)
// this "table" setting is redundant, it will come from EntityType1ToNote
//.Table("EntityToNotes")
.KeyColumn("EntityId")
// here is the trick, that only related rows will be selected
.Where("Discriminator = 'EntityType1'")
.Cascade.AllDeleteOrphan();
}
}
As I tried to explain in the links provided, we gained this way a lot. Mostly the ability to use more columns on the pairing table - e.g. Discriminator (later we can have more columns like SortBy...) and we are able to use powerful searching with subqueries - see Query on HasMany reference
Also, in fact, the pairing could be mapped via the real inheritance... But the main point here is: Instead of many-to-many we introduced the pairing object and gained a lot
Related
I'm trying to configure a one-two-one relationship twice for an entity with the following classes.
public class Team
{
...
public virtual TeamGraphic TeamLogo { get; set; }
public virtual TeamGraphic TeamPlayer { get; set; }
}
public class TeamGraphic
{
...
public virtual Team Team { get; set; }
}
TeamGraphics will be image data.
In the datacontext class
modelBuilder.Entity<Team>(t =>
{
t.HasOne<TeamGraphic>(g => g.TeamLogo)
.WithOne(t => t.Team);
t.HasOne<TeamGraphic>(g => g.TeamPlayer)
.WithOne(t => t.Team);
});
So to put it in words. A team can have one logo, and a team would have one player (image).
But when I try to do a migration, I get the error:
Cannot create a relationship between 'TeamGraphic.Team' and 'Team.TeamPlayer' because a
relationship already exists between 'TeamGraphic.Team' and 'Team.TeamLogo'. Navigations can
only participate in a single relationship. If you want to override an existing relationship
call 'Ignore' on the navigation 'Team.TeamPlayer' first in 'OnModelCreating'.
Is this at all possible to do?
Is full explained on error: "Navigations can only participate in a single relationship." That means you should to create a second Team on TeamGraphic
public class TeamGraphic
{
...
public virtual Team TeamFromTeamLogo { get; set; }
public virtual Team TeamFromTeamPlayer { get; set; }
}
modelBuilder.Entity<Team>(t =>
{
t.HasOne<TeamGraphic>(g => g.TeamLogo)
.WithOne(t => t.TeamFromTeamLogo);
t.HasOne<TeamGraphic>(g => g.TeamPlayer)
.WithOne(t => t.TeamFromTeamPlayer);
});
It has sense. If you only have one reverse navigation, TeamGraphic.Team, is ambiguous. "Which one"?
I am trying to build a model with Entity Framework - Code First in which I use a "Exam" class and a "Subject" class that have a many-to-many relationship.
A "Exam" contains a list of "Subject" (Subjects).
A "Subject" contains a list of "Exam" (Exams).
Both "Exams" and "Subjects" are virtual properties.
When I use context.Exams.Include("Subjects").ToList();, I get all the exams and all the subjects related to each exam, which is OK. The problem is I also get all the exams related to the subjects.
Result :
Exam 1
Subject 1
Exam 3
Exam 4
Subject 2
Exam 3
Exam 2
...
In this particular case, I don't need the exams related to the subjects. I just need the following data :
Exam 1
Subject 1
Subject 2
Exam 2
...
Is there a way to include "Subjects" but without the "Exams" property ?
Thank you.
Function
public List<Exam> GetAllExams()
{
using (var context = new PedagogieContext())
{
return context.Exams.Include("Subjects").ToList();
}
}
Classes
public class Exam
{
public int Id { get; set; }
public string ExamLabel { get; set; }
public virtual List<Subject> Subjects { get; set; }
}
public class Subject
{
public int Id { get; set; }
public string SubjectLabel { get; set; }
public virtual List<Exam> Exams { get; set; }
}
Mappings
class SubjectMap : EntityTypeConfiguration<Subject>
{
public SubjectMap()
{
this.HasKey(e => e.Id);
this.Property(e => e.Id).HasColumnName("KeyDiscipline");
this.Property(e => e.SubjectLabel).HasColumnName("DisciplineLib");
this.ToTable("vpDisciplines");
}
}
class ExamMap : EntityTypeConfiguration<Exam>
{
public ExamMap()
{
this.HasKey(e => e.Id);
this.Property(e => e.Id).HasColumnName("KeyExamen");
this.Property(e => e.ExamenLabel).HasColumnName("ExamenLib");
this.ToTable("vExamens");
this.HasMany(e => e.Subjects)
.WithMany(d => d.Exams)
.Map(m =>
{
m.ToTable("vrExamensDisciplines");
m.MapLeftKey("KeyExamen");
m.MapRightKey("KeyDiscipline");
});
}
}
Context
public class PedagogieContext : DbContext
{
public PedagogieContext()
: base(ConnectionStringManager.GetConnectionString())
{
this.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Exam> Exams { get; set; }
public DbSet<Subject> Subjects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ExamMap());
modelBuilder.Configurations.Add(new SubjectMap());
}
}
The "problem" is that Entity Framework executes relationship fixup whenever you get data from the database (and on many more occasions). This is the process where EF automatically populates navigation properties (like Subject.Exams) of entities in its cache.
You are fetching exams and subjects and EF kindly populates their Subjects and Exams, respectively. There is no way to stop EF from doing this (some may think that setting Configuration.AutoDetectChangesEnabled = false will do that, but no).
Note that you don't get more exams from the database than you get from the query, if that's what you're worried about. It's just that EF also creates the associations. In a debug view you could expand the collections endlessly without ever hitting the database.
The solution is not to display Subject.Exams. If this is for serialization, you have to block circular references. Some serializers (like Json.Net) have settings to do that.
Thank you all for your answers.
Indeed, entity framework doesn't load more exams than expected. It just populates the sub-exams with the exams already loaded.
My problem was actually a circular reference serializer issue.
I chose to use DTO (with automapper) to specify exactly the data I need in my view.
http://cpratt.co/using-automapper-getting-started/
I have a (contrived and over-simplified, due to contractual constraints) data model that should look like this:
public class ProvisionalData
{
public int ProvisionalDataID { get; set; }
public string Data { get; set; }
public Lot Lot { get; set; }
}
public class Destination
{
public int DestinationID { get; set; }
public string Name { get; set; }
}
public class LotDestination
{
public int LotDestinationID { get; set; }
public int DestinationID { get; set; }
public DateTime Month { get; set; }
public Destination Destination { get; set; }
}
public class Lot
{
public int LotID { get; set; }
public int ProvisionalDataID { get; set; }
public int LotDestinationID { get; set; }
public ProvisionalData ProvisionalData { get; set; }
public LotDestination LotDestination { get; set; }
}
The relationship from Lot to ProvisionalData is a required one-to-one on both sides. Please note, this is not the whole model, nor is the domain the concern. The concern is with configuring the one-to-one relationship.
My relevant to the one-to-one mapping fluent configurations:
public class LotConfig : EntityTypeConfiguration<Lot>
{
public LotConfig()
{
ToTable("Lot");
HasKey(x => x.LotID);
HasRequired(x => x.ProvisionalData)
.WithRequiredDependent(x => x.Lot)
.WillCascadeOnDelete(true);
}
}
public class ProvisionalDataConfig : EntityTypeConfiguration<ProvisionalData>
{
public ProvisionalDataConfig()
{
ToTable("ProvisionalData");
HasKey(x => x.ProvisionalDataID);
}
}
The other relationships shown are in fact set up - I have verified that they are being configured in my context, and all IDbSets exist and function correctly. In fact, everything "works" with this setup, except that some ProvisionalData entities on navigation properties are not populated by the following query:
var lotDestination = db.lotDestinations
.Where(x => x.DestinationId == destinationId && x.Month == month)
.Include(x => x.Lots)
.Include("Lots.ProvisionalData")
.Include(x => x.Destination)
.SingleOrDefault();
In my real dataset, this query will return a destination with 30 Lots. 16 of those lots have their ProvisionalData navigation property loaded. 14 do not. This problem persists when I manually loop through each Lot and db.Entry(lot).Reference(ProvisionalData).Load(). When I examine those entries, all 30 return true for .IsLoaded. The query and .Includes appears to be doing what they are supposed to, but some of the entities aren't coming back for reasons I don't understand. I'm hoping it's something simple that I can't see because I've been staring at it for too long.
However, when I change the relationship (ignoring existing database constraints) to a one-to-many with ProvisionalData entities looking like this:
public class ProvisionalData
{
public int ProvisionalDataID { get; set; }
public string Data { get; set; }
public IList<Lot> Lots { get; set; }
}
and a new Lot configuration like this:
public class LotConfig : EntityTypeConfiguration<Lot>
{
public LotConfig()
{
ToTable("Lot");
HasKey(x => x.LotID);
HasRequired(x => x.ProvisionalData)
.WithMany(x => x.Lots)
.HasForeignKey(x => x.ProvisionalDataID);
}
}
everything works flawlessly. The only drawback here is that this doesn't reflect the true constraints in the database, so you could technically try to add multiple Lots to the same piece of ProvisionalData, which would break when trying to save. I can build the logic in to prevent this myself, but why can't I express it here in Entity Framework? Is my configuration incorrect?
Also interestingly, when I switch the above-mentioned query around to this dumb version to test things (with the one-to-one mapping still in place in EF):
var quota = db.Lots
.Where(l => l.LotDestination.DestinationID == destinationId && l.LotDestination.Month == m)
.Include(x => x.ProvisionalData)
.Include(x => x.LotDestination)
.Include(x => x.LotDestination.Destination)
.Select(x => x.LotDestination)
.FirstOrDefault();
all of the provisional data comes back, but some of the Destinations do not. This hints to me that it has something to do with including navigation properties multiple levels deep across a one-to-one. Has anyone else experienced this behavior before?
EF does not officially support 1:1 associations other than in shared primary key 1:1 association situations.
What you are doing is create 1:many's and trying to tell EF that it's really a 1:1. The problem is, the db schema is really a 1:many schema and EF will have problems here.
If your requirement is 1:1, then you need to use a shared primary key (both entities have the same primary key), and one also treats it as a foreign key).
I am facing the problems before, Mine solution was using 1:many because if POCOA include POCOB
.Include(x => x.POCOA.POCOB)
really return the POCOB as a list, since we know it 100% return one record, then during getting the data, we can say a.SingleOrDefault();
I was using Fluent NHibernate and I had these two entities:
public class Location
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
private Stack<Place> _places;
public virtual IList<Place> Places
{
get { return _places.ToList(); }
set { _places = new Stack<Place>(value); }
}
public virtual void AddPlace(Place place)
{
_places.Push(place);
place.LocationReference = this;
}
}
public class Place
{
public virtual int Id { get; protected set; }
public virtual Location LocationReference { get; set; }
}
mapped as follow:
class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Not.Nullable()
.Unique();
HasMany(x => x.Places)
.Cascade.All()
.Inverse();
}
}
class PlaceMap : ClassMap<Place>
{
public PlaceMap()
{
Id(x => x.Id);
References(x => x.LocationReference);
}
}
I was using SQLite as RDBMS and I was saving only the Location, relying on Cascade() for managing Places insertion.
The two entities were successfully inserted in the DB, but when I tried to read a Location back and to access its list of Places, the list was empty.
After removing Inverse(), the code seemed to properly work. But when I inspected the DB, I found two columns within the Place table, while I expected only one of them: Location_id (that was empty) and LocationReference_id (that was set).
After one whole day of desperate googling, I noticed that everyone was naming the reference property as the class itself. So, I renamed it from LocationReference to Location, I added back the Inverse() call and everything worked fine. Only the column Location_id was in the DB, of course.
Does anyone know why this happened? Thanks.
I have two tables in my database. One is called Users, and the other is called Widgets. The Widgets table represents 3 entities in my code model. One of the entities, Widget, is a parent class for the other two entities, WidgetTypeA and WidgetTypeB. Both WidgetTypeA and WidgetTypeB have navigation properties to the User entity, which is persisted to the Users table in the database. I'm having trouble getting Code First to use the same foreign key for both the WidgetTypeA and WidgetTypeB entities (UserId). Does anyone know how to do this? It seems like it should be a common problem with Table Per Hierarchy mapping.
My entity classes are as follows:
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
}
class WidgetMap : EntityTypeConfiguration<Widget>
{
public WidgetMap()
{
ToTable("Widgets");
HasKey(w => w.Id);
Property(w => w.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(w => w.Name)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
}
}
public class WidgetTypeA : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
public string Color { get; set; }
public int DepthLevel { get; set; }
}
class WidgetTypeAMap : EntityTypeConfiguration<WidgetTypeA>
{
public WidgetTypeAMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(1));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeAs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
Property(w => w.Color)
.IsOptional()
.IsUnicode(true)
.HasMaxLength(75);
Property(w => w.DepthLevel)
.IsOptional();
}
}
public class WidgetTypeB : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
}
class WidgetTypeBMap : EntityTypeConfiguration<WidgetTypeB>
{
public WidgetTypeBMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(2));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeBs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
}
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
public virtual ICollection<WidgetTypeA> WidgetTypeAs { get; set; }
public virtual ICollection<WidgetTypeB> WidgetTypeBs { get; set; }
}
class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
ToTable("Users");
HasKey(u => u.Id);
Property(u => u.Username)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
Property(u => u.Age)
.IsRequired();
}
}
At any rate, I keep getting the error
Invalid column name 'UserId1'
when I try to perform the following operations:
using (var entities = new MyEntities())
{
User u = new User
{
Username = "Frank",
Age = 14
};
entities.Users.Add(u);
entities.SaveChanges();
WidgetTypeA wa1 = new WidgetTypeA
{
Name = "0SDF81",
UserId = u.Id,
DepthLevel = 6
};
entities.WidgetTypeAs.Add(wa1);
entities.SaveChanges();
}
Not sure if this can be fixed or not. I can always specify a second UserId foreign key for the Widgets table, but that seems pointless. Perhaps there's a way to do this using Fluent API?
You cannot map properties defined in different derived entities to the same column. That is limitation in EF. If your WidgetTypeA has UserId property and your WidgetTypeB has UserId property they must be different columns in the database. It should work if you move both UserId and User properties from derived types to the parent Widget type.
I know its a long way late, but hopefully may help other readers.
Although Ladislav was correct that using a mapped Foreign Key is not supported in EF6, I did find a useful workaround.
It is possible to define a computed column specification whose expression simply refers to the original column. Userid in the description above. This can be used as the discriminator for the TPH mapping. With this approach, the column need not be persisted, but can be used for TPH, with the original column being available for use as a foreign key.