fluent nhibernate map hasmany using propertyref without fetch.join - c#

I am using Nhibernate 3.2, along with a build of FluentNhibernate compatible with NH 3.2 and I have come to map a legacy part of my system. I believe it is possible to do what I require, but need some assistance in mapping it correctly.
I have a User Class below, with a list of Applications.
public class User
{
public virtual string UserID { get; set; }
public virtual int Id { get; set; }
public virtual IList<Application> Applications { get; set; }
}
I also have a Application class which has a foreign key back to the User Class which is not the "primary key". See Below
public class Application
{
// Not sure if this 'User' is needed?
public virtual User User { get; set; }
public virtual int IdFromUserTable { get; set; }
public virtual string ApplicationName { get; set; }
}
I have the corresponding ClassMaps, I think the UserMap is where the issue lies
UserMap
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserID);
HasMany(x => x.Applications)
.AsBag()
.KeyColumn("UserID")
.PropertyRef("Id")
.LazyLoad()
.Inverse();
Table("usertable");
ReadOnly();
}
}
ApplicationMap
public class ApplicationMap : ClassMap<Application>
{
public ApplicationMap()
{
CompositeId()
.KeyProperty(x => x.ApplicationName)
.KeyProperty(x => x.IdFromUserTable, "UserID");
Table("applicationstable");
}
}
The Table structures are as follows:
User Table
Primary Key - string, ColumnName = UserID
int (Identity) - int, ColumnName = Id
Applications Table
Composite Key - string, ColumnName = ApplicationName
and
int, ColumnName = UserId (references Id column in user table)
My question is how to get the mapping to work for the above scenario.
One caveat is: I cannot change the structure of the database, but I can change the classes / classmaps
Any help greatly appreciated..
PS - I did get this to work by adding in Fetch.Join in the HasMany userMap, but I would prefer to lazily evaluate the Application List when needed
Thanks, Mark

Related

What type of relationship is this and how to implement it in .NET Core (EFCore, FluentAPI)?

The problem I have is pretty easy, but my mind stopped working. Sorry if my questions is dumb, but I'm not really good with databases (neither I am with EFCore).
I want to have the following tables:
CVs: with ID and Name/Title (string)
Skills: with ID and Name/Title (string)
SkillsCV: with ID, CvID (foreign key to a record in CVs), SkillID (foreign key to a record in Skills)
I don't want to have a foreign key to SkillsCV in the CVs and Skills tables. Is it possible? Is it possible in .NET Core and more importantly with Fluent API?
I've made a small research for FluentAPI and there are foreign keys in the both ends in one-to-one relationships. Is this needed? BTW, it is a one-to-one relationship, right? I don't want the one side to know about the other side. Is this what's called 0 to 1, or this is a completely different thing? I'm really confused.
So what I've seen for one-to-one relationships in FluentAPI, I need the following code:
modelBuilder.Entity<Skill>()
.HasOne(skill => skill.SkillCV) // but I don't have a SkillCV object in Skill model
.WithOne(skillCV => skillCV.Skill) // I have skillCV.Skill
.HasForeignKey<SkillCV>(skillCV => skillCV.SkillID); // I have this foreign key in skillCV as well
But I don't want to have an object (or foreign key) in the Skills table (as I don't want such in the CVs table). Is this possible? I'm for sure doing something wrong. Can you help me to find my mistake (if there is one)?
If you know a better way to do this, please share it. Thanks in advance!
EDIT: A quick example to what I want to create:
CVs Table:
ID, Name
1 "CV1"
2 "CV2"
3 "CV3"
Skills Table:
ID, Name
1 "C#"
2 "Java"
3 "Python"
SkillsCVs Table:
ID, CvID, SkillID
1 1 1
2 1 3
3 2 1
Is this a good solution to solve this problem? I haven't created the SkillsCVs table yet, now I have only CVs and Skills (every skill has a CV_ID), but this way when I need to populate a select box in the frontend, I need to return DISTINCT Skills from the API (because there are 800 C# for example records for different CVs). I thought a SkillsCVs table will solve this issue, but I'm not entirely sure now :D
What you have is a many-to-many relationship. You could model things like this:
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CV
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SkillCV
{
public int SkillID { get; set; }
public Skill Skill { get; set; }
public int CVID { get; set; }
public CV CV { get; set; }
}
Then to set this up with Fluent API, you could do this:
modelBuilder.Entity<SkillCV>()
.HasKey(t => new { t.SkillID, t.CVID});
modelBuilder.Entity<SkillCV>()
.HasOne(pt => pt.Skill)
.WithMany()
.HasForeignKey(pt => pt.SkillID);
modelBuilder.Entity<SkillCV>()
.HasOne(pt => pt.CV)
.WithMany()
.HasForeignKey(pt => pt.CVID);
This way your joins later will be simple, and the SkillCV table will have a composite key made up of SkillID and CVID (ensuring referential integrity).
You can do it only with annotations if I understand you correctly.
public class Skill
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Cv
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class SkillCv
{
[ForeignKey("Skill")]
public int SkillId { get; set; }
[ForeignKey("Cv")]
public int CvId { get; set; }
public virtual Skill Skill{ get; set; }
public virtual Cv Cv { get; set; }
}
public class TestContext : DbContext
{
public DbSet<Skill> Skills { get; set; }
public DbSet<Cv> Cvs { get; set; }
public DbSet<SkillCv> SkillCvs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//The entity does not have its own key, only the relationship of the two
modelBuilder.Entity<SkillCv>().HasNoKey();
}
}
So you will have a row in the table for each skill that has a cv
var allSkillFromCv = await _context.SkillCvs.Where(s => s.CvId == 1).ToListAsync()
If you want to use the navigation property
var allSkillFromCv = await _context.SkillCvs.Where(s => s.CvId == 1).Include(s => s.Skill).ToListAsync();
You need three table here: CVs Table, Skills Table and SkillsCVs Table. You have a
Many-to-Many relationship between CVs Table and Skills Table. You have to define the Many-to-Many relationship using Fluent API. you need navigation property in both CVs Table and Skills Table
public class CV
{
....
public IList<SkillCV> SkillsCVs { get; set; }
}
public class Skill
{
....
public IList<SkillCV> SkillsCVs { get; set; }
}
in DB context
public DbSet<SkillCV> SkillsCVs { get; set; }
You also need to define relationship using Fluent API
modelBuilder.Entity<SkillCV>()
.HasOne<Skill>(sc => sc.Skill)
.WithMany(s => s.SkillsCVs)
.HasForeignKey(sc => sc.SkillID);
modelBuilder.Entity<SkillCV>()
.HasOne<CV>(sc => sc.CV)
.WithMany(s => s.SkillsCVs)
.HasForeignKey(sc => sc.CvID);

Can you customise foreign key names in self referencing entities in entity framework?

Some of the entities in my application have 4 audit properties on them:
public virtual DateTime WhenAdded { get; set; }
public virtual DateTime? WhenUpdated { get; set; }
public virtual User AddedBy { get; set; }
public virtual User UpdatedBy { get; set; }
I am using a code first approach and have the following extension method to map the user properties:
public static void MapAuditFields<T>(this EntityTypeConfiguration<T> configuration) where T : class, IAuditable
{
configuration.HasOptional<User>(e => e.AddedBy)
.WithOptionalDependent()
.Map(a => a.MapKey("AddedByUserId"));
configuration.HasOptional<User>(e => e.UpdatedBy)
.WithOptionalDependent()
.Map(a => a.MapKey("UpdatedByUserId"));
}
This is working fine in most cases, but not on the User class, which of course has a recursive relationship with itself. I have seen various posts on the internet suggesting that entity framework has a bug when you try to customise join table column names in this scenario, for example:
Self-referencing many-to-many recursive relationship code first Entity Framework
and
http://social.msdn.microsoft.com/Forums/en-US/f058097d-a0e7-4393-98ef-3b13ab5b165d/code-first-sequence-contains-more-than-one-matching-element-exception-when-generating-schema?forum=adonetefx
The error I am getting is "Sequence contains more than one matching element".
Does anyone know if this has been fixed in entity framework 6?
Many thanks.
Use WithMany() instead of WithOptionalDependent() as a user can add or update multiple other users
Class:
public class User
{
public int Id { get; set; }
public virtual User AddedBy { get; set; }
public virtual User UpdatedBy { get; set; }
}
Fluent API calls:
modelBuilder.Entity<User>()
.HasOptional( u => u.AddedBy )
.WithMany()
.Map( fkamc => fkamc.MapKey( "AddedByUserId" ) );
modelBuilder.Entity<User>()
.HasOptional( u => u.UpdatedBy )
.WithMany()
.Map( fkamc => fkamc.MapKey( "UpdatedByUserId" ) );
Results:

Cannot Split Table with EF 5 - Code First - with existing database

I am trying to split a legacy user table into two entities using a one to one mapping but keep getting migration errors stating that my database is out of sync, even though everything (i think is mapped) and i am trying to make a one-to-one relationship.
This is an existing database (although i am using code first as migrations will become important down the line) but i have not added any changes to the database (although i am unsure what exactly the one-to-one table split expects), i keep getting this:
The model backing the 'Context' context has changed since the database was created. Consider using Code First Migrations to update the database
I can update the database (either manually or via Migrations) but have no idea what is actually out of sync as no new fields have been added and the names match up.
BaseEntity:
public abstract class BaseEntity<T>
{
[Key]
public T Id { get; set; }
public DateTime CreatedOn { get; set; }
}
Membership Model:
public class Membership : BaseEntity<Guid>
{
public string UserName { get; set; }
public bool Approved { get; set; }
public bool Locked { get; set; }
public Profile Profile { get; set; }
}
Profile Model:
public class Profile : BaseEntity<Guid>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Telephone { get; set; }
public string Extension { get; set; }
public Membership Membership { get; set; }
}
Membership Mapping (this has the 1 to 1 Definition):
public class MembershipMap : EntityTypeConfiguration<Membership>
{
public MembershipMap()
{
//Primary Key
this.HasKey(t => t.Id);
//**Relationship Mappings
this.HasRequired(m => m.Profile)
.WithRequiredPrincipal(p => p.Membership);
//Properties & Column mapping
this.Property(m => m.Id)
.HasColumnName("PKID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(m => m.UserName)
.HasColumnName("Username")
.HasMaxLength(255);
this.Property(m => m.Approved)
.HasColumnName("IsApproved");
this.Property(m => m.Locked)
.HasColumnName("IsLocked");
this.Property(m => m.CreatedOn)
.HasColumnName("CreationDate");
this.ToTable("AppUser");
}
}
Profile Mapping:
public class ProfileMap : EntityTypeConfiguration<Profile>
{
public ProfileMap()
{
//Primary Key
this.HasKey(t => t.Id);
//Properties & Column mapping
this.Property(m => m.Id)
.HasColumnName("PKID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(m => m.FirstName)
.HasColumnName("FirstName");
this.Property(m => m.LastName)
.HasColumnName("LastName");
this.Property(m => m.Email)
.HasColumnName("Email");
this.Property(m => m.Telephone)
.HasColumnName("Telephone");
this.Property(m => m.Extension)
.HasColumnName("Extension");
this.ToTable("AppUser");
}
}
Database Table
I know that not all fields are mapped, but i do not need them at this stage, surely that wouldn't be the issue would it?
Issue was not Code First Mappings but caused by me switching databases and some rouge migrations coming into play.
To reset migrations you can see the answer here from a follow up question:
Resetting Context for Entity Framework 5 so it thinks its working with a initialised Database - Code First
Kudos to EvilBHonda

Why are my Fluent NHibernate SubClass Mappings generating redundant columns?

I have the following entities
public abstract class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Product Product { get; set; }
public virtual Sprint Sprint { get; set; }
}
public class Story:Card
{
public virtual double Points { get; set; }
public virtual int Priority { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Story> Stories { get; private set; }
public Product()
{
Stories = new List<Story>();
}
}
And the following mappings
public class CardMap:ClassMap<Card>
{
public CardMap()
{
Id(c => c.Id)
.Index("Card_Id");
Map(c => c.Name)
.Length(50)
.Not.Nullable();
Map(c => c.Description)
.Length(1024)
.Not.Nullable();
References(c=>c.Product)
.Not.Nullable();
References(c=>c.Sprint)
.Nullable();
}
}
public class StoryMap : SubclassMap<Story>
{
public StoryMap()
{
Map(s => s.Points);
Map(s => s.Priority);
}
}
public class ProductMap:ClassMap<Product>
{
public ProductMap()
{
Id(p => p.Id)
.Index("Product_Id");
Map(p => p.Name)
.Length(50)
.Not.Nullable();
HasMany(p => p.Stories)
.Inverse();
}
When I generate my Schema, the tables are created as follows
Card
---------
Id
Name
Description
Product_id
Sprint_id
Story
------------
Card_id
Points
Priority
Product_id
Sprint_id
What I would have expected would have been to see the columns Product_id and Sprint_id ONLY in the Card table, not the Story table.
What am I doing wrong or misunderstanding?
NB: Tested on the NH2 project only
Well, you are probably going to want to chew on a door once you read this, but the TLDR reason is because the Product_id and Spring_id columns in your Story table are not redundant - they exist for the HasMany(x => x.Stories) relations in your SpringMap and ProductMap. They just happen to be share the same naming convention as the CardMap References(x => x.Product and References(x => x.Sprint).
Validate this for yourself by commenting out ProductMap.cs:24-25 and SprintMap.cs:22 and rebuilding.
If the above does not make sense, let me know and I will try to explain in further detail.
So, it should work fine as is. If you want to clarify the columns, you could explicitly define the column names like so:
ProductMap.cs
HasMany(p => p.Stories)
.KeyColumn("ProductOwner_id")
.Inverse();
SprintMap.cs
HasMany(s => s.Stories)
.KeyColumn("SprintOwner_id")
;
CardMap.cs
References(c=>c.Product)
.Column("Product_id")
.Not.Nullable();
References(c=>c.Sprint)
.Column("Sprint_id")
.Nullable();
Here I am guessing that the 1:N relationships between a Story and a Product/Sprint are an "owner". You would want to rename it to whatever is appropriate semantically.
One other thing. I would have thought the last changes (the changes to CardMap.cs) would be unnecessary - but they seem to be for some reason, or the Sprint_id column becomes SprintOwner_id. I have no idea why this would happen - I would speculate that this is some sort of bidirectional relationship inferencing on fluent/nhibernates part gone awry, but I'd put very little money on that.
I see that the Story entity inherits from the Card entity you created, but you don't know why you have Product_Id and Sprint_Id properties in the Story table Schema, since they're virtual properties in the Card class.
I'm guessing that this happens because in NHibernate, all properties need to be virtual but only at first. They don't really stay virtual. The NHibernate framework overrides them, and probably because of this, this is happening to you.

NHibernate + Fluent NHibernate exception

Problem:
There are searches that can be stored in the DB. Each search has a collection of filters. Also there are roles. Each role may have (nullable column) a default search assigned to it. Also, each search is visible to zero or many roles (many-to-many relationship).
When I try to access the search filters, NH tries to access filters.DefaultSearchId, which doesn't exist in filters table.
DB:
CREATE TABLE [dbo].[Searches]
(
Id int identity(1,1) primary key,
Description nvarchar(2000) not null
);
CREATE TABLE [dbo].[Filters]
(
Id int identity(1,1) primary key,
Description nvarchar(2000) not null,
SearchId int not null references Searches(Id)
);
CREATE TABLE [dbo].[Roles]
(
Id int identity(1,1) primary key,
Name nvarchar(255) not null,
DefaultSearchId int null references Searches(Id)
);
CREATE TABLE [dbo].[SearchesRoles]
(
SearchId int not null references Searches(Id),
RoleId int not null references Roles(Id)
);
Entities:
public class Search {
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual ICollection<Filter> Filters { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Filter {
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual Search Search { get; set; }
}
public class Role {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Search DefaultSearch { get; set; }
}
Mappings:
public class SearchMap : ClassMap<Search>{
public SearchMap() {
Table("Searches");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Filters).Inverse().Cascade.All().AsBag();
HasManyToMany(x => x.Roles).Table("SearchesRoles").ParentKeyColumn("SearchId").ChildKeyColumn("RoleId");
}
}
public class FilterMap : ClassMap<Filter> {
public FilterMap() {
Table("Filters");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
References(x => x.Search).Column("SearchId");
}
}
public class RoleMap : ClassMap<Role> {
public RoleMap() {
Table("Roles");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
References(x => x.DefaultSearch).Column("DefaultSearchId");
}
}
Code:
class Program {
static void Main() {
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession()) {
var search = session.Get<Search>(1);
foreach (var filter in search.Filters) {
Console.WriteLine(filter);
}
}
}
static ISessionFactory CreateSessionFactory(){
string connectionString = #"server=.\sql2008; user id = sa; pwd=1; database = nhbug;";
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
.Mappings(m=>m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())).BuildSessionFactory();
}
}
ERROR:
When accessing the search.Filters property, NHibernate tries to access Filters.DefaultSearchId db column which is not supposed to be there. This column exists in Roles table but not in filters.
QUESTION:
Is it invalid configuration, Fluent NHibernate or NHibernate bug?
I'm using SQL Server 2008 R2, NHibernate 2.1.2 and Fluent NHibernate 1.1.0.685, although this issue exists in NHibernate 3 beta 2 as well.
Thank you.
UPDATE:
Here is the actual SQL generated
UPDATE2: CDMDOTNET, same error, same sql, unfortunately.
UPDATE3: Actual exception
UPDATE4: This is a particular use case of a general bug: Entity references other entities as 'many-to-many' and on the other side of 'many-to-many' assoc. the other entity references the source entity (DefaultQuery in my case). NH goes nuts when accessing any child collection (one-to-many) of a source entity (Filters in my case).
UPDATE5: Sample data
UPDATE6: XML issued by Fluent NHibernate
Update the HasMany mapping on the SearchMap to include the KeyColumn():
HasMany(x => x.Filters).KeyColumn("SearchId").Inverse().Cascade.All().AsBag();
You didn't specify the column name for the Filters bag. It should be set to SearchId.

Categories

Resources