How to Map a ValueObject Collection with Foreign Keys in FluentNHibernate - fluent-nhibernate

I've been looking all over for an example of this, but it seems pretty uncommon. Hopefully some NHibernate guru will know.
I have the following class which, by my understanding of Value Objects, is a Value Object. Assume every user has the ability to assign one or more tags to any Question (think Stack Overflow). The Tags don't need a primary key, but they do hold references to the User and Question, unlike most of the examples of ValueObjects I see out there.
public class Tag : ValueObject
{
public virtual User User { get; set; }
public virtual Question Question { get; set; }
public virtual string TagName { get; set; }
}
public class User
{
public virtual IList<Tag> Tags { get; set; }
}
public class Question
{
public virtual IList<Tag> Tags { get; set; }
}
Anyway, I am getting the following error:
{"The entity 'Tag' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id)."}
I have the following Fluent NHibernate mapping for User and Question:
public void Override(AutoMapping<XXX> mapping)
{
mapping.HasMany(x => x.Tags).Component(c =>
{
c.Map(x => x.TagName);
c.Map(x => x.Question);
c.Map(x => x.User);
});
}
As always, any thought greatly appreciated.
Late Update: Okay, so, maybe this isn't a value object. It doesn't need an identity, but I guess it's not something that could be used in multiple places, either. Any way to handle this without forcing a useless Id field on my object?

Try this:
public void Override(AutoMapping<XXX> mapping)
{
mapping.HasMany(x => x.Tags).AsBag().Component(c =>
{
c.Map(x => x.TagName);
c.References(x => x.Question);
c.References(x => x.User);
});
}
but you cant query (list all) tags then because its a value object.

Related

Entity Framework Core 7 relationship [duplicate]

I am new in EF. And I ran into a problem with creation many-to-many self referencing relation.
I've tried to use solution from: Entity Framework Core: many-to-many relationship with same entity
my entities :
public class WordEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string Json { get; set; }
public virtual List<WordSinonymEntity> Sinonyms { get; set; }
}
public class WordSinonymEntity
{
public long WordId { get; set; }
public virtual WordEntity Word { get; set; }
public long SinonymId { get; set; }
public virtual WordEntity Sinonym { get; set; }
}
and next configuration:
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany(p => p.Sinonyms)
.HasForeignKey(pt => pt.SinonymId);
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);`
but it leads to next exception.
System.InvalidOperationException: 'Cannot create a relationship between 'WordEntity.Sinonyms' and 'WordSinonymEntity.Word', because there already is a relationship between 'WordEntity.Sinonyms' and 'WordSinonymEntity.Sinonym'. Navigation properties can only participate in a single relationship.'
Does anyone can help me or may be suggest some examples to learn ?
Thanks.
The post you are following is definitely wrong.
Every collection or reference navigation property can only be a part of a single relationship. While many to many relationship with explicit join entity is implemented with two one to many relationships. The join entity contains two reference navigation properties, but the main entity has only single collection navigation property, which has to be associated with one of them, but not with both.
One way to resolve the issue is to add a second collection navigation property:
public class WordEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string Json { get; set; }
public virtual List<WordSinonymEntity> Sinonyms { get; set; }
public virtual List<WordSinonymEntity> SinonymOf { get; set; } // <--
}
and specify the associations via fluent API:
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany(p => p.SinonymOf) // <--
.HasForeignKey(pt => pt.SinonymId)
.OnDelete(DeleteBehavior.Restrict); // see the note at the end
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);
Another way is to leave the model as is, but map the WordSinonymEntity.Sinonym to unidirectional association (with refeference navigation property and no corresponding collection navigation property):
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany() // <--
.HasForeignKey(pt => pt.SinonymId)
.OnDelete(DeleteBehavior.Restrict); // see the note at the end
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);
Just make sure that WithMany exactly matches the presence/absence of the corresponding navigation property.
Note that in both cases you have to turn the delete cascade off for at least one of the relationships and manually delete the related join entities before deleting the main entity, because self referencing relationships always introduce possible cycles or multiple cascade path issue, preventing the usage of cascade delete.

Entity Framework 5 One to One relationship (e.g. User -> Profile) - ModelBuilder ASP.NET MVC4

I am trying to do a similar thing to what this previous answer had here:
How to declare one to one relationship using Entity Framework 4 Code First (POCO)
The problem is, im very new to this and am using Entity Framework 5 code first and the HasConstraint doesnt exist anymore, not to mention Im not good at lamda. I was wondering if anyone could help expand on this so I can map a User class to a Profile class effectively and easily? I need to know how to do this for the configuration files and model builder
Each user has one profile
Also, another quick question, say the profile model had Lists in this, how would I put these effectively in the model builder and configuration files?
Thank you
e.g.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public Profile Profile { get; set; }
// public int ProfileId { get; set; }
}
public class Profile
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PostalCode { get; set; }
}
// ...
modelBuilder.Entity<User>()
.HasOptional(x => x.Profile)
.WithRequired();
ProfileId is useless, FK is on the 'other side of the fence' (in Profile).
(this makes most sense IMO)
If you do need an Id in User (e.g. to be able to fill in Profile just by its ID when adding User - which if one-to-one is not really used - as you create both profile and user), then you can reverse...
modelBuilder.Entity<User>()
.HasRequired(x => x.Profile)
.WithOptional();
...and your ProfileId is actually in the Id (pk -> pk).
That solution worked for me
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.HasOne(d => d.Profile).WithOne(p => p.User);
});
modelBuilder.Entity<UserProfile>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasColumnType("nchar");
entity.HasOne(d => d.User).WithOne(p => p.Profile);
});
}

Fluent Nhibernate: Cant delete an item with as HasMany property

I dont normally deal with data like this but I thought id give it a try. As it turned out I failed :/ and am not sure how to proceed.
I have a database object track:
public virtual string Type { get; set; }
public virtual IList<DateTypeTrack> TrackDates { get; set; }
With a mapping file:
Table("Tracks");
Map(x => x.Type).Not.Nullable();
HasMany(x => x.TrackDates).KeyColumn("TrackID").Cascade.All();
The DateTypeTrack Object looks like this:
public virtual DateType DateType { get; set; }
public virtual Track Track { get; set; }
public virtual int Days { get; set; }
With a mapping file like this:
Table("DateTypeTracks");
References(x => x.DateType, "DateTypeID").Not.Nullable();
References(x => x.Track, "TrackID").Not.Nullable();
Map(x => x.Days).Not.Nullable();
If its necessary, Ill post the DateType code aswell, but I dont think its needed.
And am trying to write a delete method in my service layer that is pretty simple:
public void PushDelete(int id)
{
Track track = _tracks.Get(id);
try
{
_tracks.BeginTransaction();
_tracks.Delete(track);
_tracks.CommitTransaction();
}
catch (Exception)
{
_tracks.RollbackTransaction();
throw;
}
}
I keep getting an error:
could not delete collection: [TSE.Domain.DatabaseObjects.Track.TrackDates#12][SQL: UPDATE DateTypeTracks SET TrackID = null WHERE TrackID = #p0]
I dont know why its trying to do the update at the end, but I suppose that is what is causing the issue. What sort of recourse do I have?
Thanks.
since the DateTypeTrack already cares for the association between the two entities you should mark the HasMany as Inverse to tell NH that the hasmany does not maintain it (the Update)
HasMany(x => x.TrackDates).KeyColumn("TrackID").Cascade.All().Inverse();

NHibernate mapping by code: How to map IDictionary?

How can I map these Entities using mapping-by-code:
public class Foo
{
public virtual IDictionary<Bar, string> Bars { get; set; }
}
public class Bar
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
I found this thread, but it does not map an entity, only simple types. I tried many mappings, including automapping:
Map(x => x.Bars,
m =>
{
m.Key(k => k.NotNullable(true));
m.Cascade(Cascade.All);
},
But most of them throw these two errors:
Foreign key (Bars [idx])) must have same number of columns as the referenced primary key (Bars [FooId, idx]).
An association from the table FoosToStrings refers to an unmapped class: System.String.
Any help will be highly appreciated. Thanks. :)
i think this should work
Map(x => x.Bars,
entryMap => entryMap.Key(k => k.Column("foo_id")),
keymap => keymap.ManyToMany(m => m.Column("bar_Id")),
elementMap => elementMap.Element(m => m.Column("value")));

NHibernate exception: could not initialize a collection, Invalid column name. Fluent mapping. Maybe a many-to-one issue?

I am puzzled and frustrated by an exception I'm getting via NHibernate. I apologize for the length of this post, but I've tried to include an appropriate level of detail to explain the issue well enough to get some help!
Here's the facts:
I have a Person class which contains a property BillingManager, which is also a Person type. I map this as an FNH "Reference".
I have an ExpenseReport class which contains a property SubmittedBy, which is a Person type. I map this as an FNH "Reference".
I have a BillableTime class which contains a property Person, which is a Person type. I map this as an FNH "Reference".
Person contains a collection (IList) of ExpenseReport types (property ExpenseReports)
Person contains a collection (IList) of BilledTime types (property Time)
(See classes and mappings at bottom of post.)
All was cool until I added the IList<BilledTime> Time collection to Person. Now, when I try to access _person.Time, I get an exception:
The code:
// Get billable hours
if (_person.Time == null ||
_person.Time.Count(x => x.Project.ProjectId == project.ProjectId) == 0)
{
// No billable time for this project
billableHours = Enumerable.Repeat(0F, 14).ToArray();
}
The exception:
could not initialize a collection:
[MyApp.Business.Person.Time#211d3567-6e20-4220-a15c-74f8784fe47a]
[SQL: SELECT
time0_.BillingManager_id as BillingM8_1_,
time0_.Id as Id1_,
time0_.Id as Id1_0_,
time0_.ReadOnly as ReadOnly1_0_,
time0_.DailyHours as DailyHours1_0_,
time0_.Week_id as Week4_1_0_,
time0_.Person_id as Person5_1_0_,
time0_.Project_id as Project6_1_0_,
time0_.Invoice_id as Invoice7_1_0_
FROM [BillableTime] time0_
WHERE time0_.BillingManager_id=?]
It's true that BillingManager_id is an invalid column name, it doesn't exist in the BillableTime table. However, I don't understand why NHB has created this SQL... doesn't make sense to me. I have seen this "Invalid column name" exception a lot when searching for a solution, but nothing seems to work. Even more confusing: like BilledTime, the ExpenseReport type also contains a reference to Person and it works perfectly.
One thing I was able to figure out is that if I remove the BillingManager reference from the Person mapping (References(p => p.BillingManager)), the exception goes away and things seem to work (with respect to BillableTime; it of course breaks the BillingManager persistence). Now it seems like there is some "self-reference" problem, since the Person.BillingManager property is itself a reference to a Person.
Any idea what is going on here? I'm at a loss...
Thanks.
=== Classes & Mappings ===
public class Person
{
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual Person BillingManager { get; set; }
public virtual IList<ExpenseReport> ExpenseReports { get; set; }
public virtual IList<BillableTime> Time { get; set; }
}
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(p => p.UserId).GeneratedBy.Assigned();
Map(p => p.LastName).Not.Nullable();
Map(p => p.FirstName).Not.Nullable();
References(p => p.BillingManager);
HasMany(p => p.ExpenseReports).Cascade.AllDeleteOrphan();
HasMany(p => p.Time).Cascade.AllDeleteOrphan();
}
}
public class BillableTime
{
public virtual int Id { get; private set; }
public virtual Week Week { get; set; }
public virtual Person Person { get; set; }
public virtual Project Project { get; set; }
public virtual float[] DailyHours { get; set; }
public virtual Invoice Invoice { get; set; }
public virtual bool ReadOnly { get; set; }
}
public class BillableTimeMapping : ClassMap<BillableTime>
{
public BillableTimeMapping()
{
Id(x => x.Id);
References(x => x.Week);
References(x => x.Person);
References(x => x.Project);
References(x => x.Invoice);
Map(x => x.ReadOnly).Not.Nullable().Default("0");
Map(x => x.DailyHours).Length(28);
}
}
public class ExpenseReport
{
public virtual long Id { get; set; }
public virtual Person SubmittedBy { get; set; }
}
the following line should solve the issue, but i' dont know exactly why it is happening. if i have the spare time i will investigate.
HasMany(p => p.Time).Cascade.AllDeleteOrphan().KeyColumn("Person_Id");