I'm getting the "Save unsaved transient entities" error in NHibernate. I have an aggregate root, neighborhood that contains addresses and person, here's some quick pseudo code to explain the relationship:
public class Neighborhood {
public virtual int Id { get; set; }
public virtual IList<Address> Addresses { get; set; }
}
public class Address {
public virtual int Id { get; set; }
public virtual string Address { get; set; }
public virtual Person Person { get; set; } //Assume only one person per address
}
public class Person {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
In my Neighborhood map I have:
mapping.HasMany(x => x.Addresses)
.Inverse()
.KeyColumn("NeighborhoodFk")
.Cascade.All()
.AsBag();
In my code I will often want to create a and associate an Address and Person at the same time:
var address = new Address();
var person = new Person();
var address.Person = person;
var neighborhood = neighborhoodRepository.Get(id);
neighborhood.Add(address);
neighborhoodRepository.DbContext.BeginTransaction();
neighborhoodRepository.SaveOrUpdate(neighborhood);
neighborhoodRepository.DbContext.CommitTransation();
I will get the "unsaved transient entities" error on the Person entity because it is attached to the transient entity Address.
The only way I can see around this is to save the address first, make another call to the database to update neighborhood after the update, search for the address I just added, attach the person and then save again.
Is there something I'm missing to make this easier? This seems like a common use case and I don't want to be making a bunch of roundtrips to the database.
Make sure you're setting the "Cascade" attribute of your mapping from Address to Person to be "save-update" or "all". You have the cascade from Neighborhood to Address, but you didn't state that this lower cascade was present. If it isn't, you're getting this error not because a Person is attached to a transient Address, but because the Address references a transient Person.
If this cascade cannot be made for whatever reason, save the Person first, then save the Neighborhood, which will cascade to the Address, and the ORM will find the referenced Person in its session and set up the reference. This MAY result in some extra "round trips" depending on if you're letting NH or the DB generate autonumber columns. NHibernate is tricky in that it will make DB calls when it's good and ready, and that may be after the entire object graph is in the NH session, or just the person. Either way, it will make an Insert call into the DB for each object being persisted, so it will make multiple "roundtrips" no matter what the code to add the items to the session looks like.
Related
I have a Web API that uses entity framework. I have several tables there were created using the code first setup. My Competitions class is defined below.
Everything works great and I'm able to get my Competitions table data along with all the data in the navigation properties that are returning a collection. However, I'm not able to get any values for the CompetitionTypes and Users navigation properties. OwnerId references UserId in the Users table.
How would I get the linked data in my CompetitionTypes and Users table? I basically want the same thing as the three collection navigation properties, except that CompetitionTypes and Users would only return one row.
public partial class Competitions
{
[Key, Required]
public int CompetitionId { get; set; }
public int CompetitionTypeId { get; set; }
public int OwnerId { get; set; }
public string CompetitionName { get; set; }
public CompetitionTypes CompetitionTypeId { get; set; }
public Users UserId { get; set; }
public ICollection<Participants> Participants { get; set; }
public ICollection<ResultStats> ResultStats { get; set; }
public ICollection<Results> Results { get; set; }
}
}
EF auto-matches FK properties with navigation properties based on conventions. Namely, it expects FK properties to be named the same as navigation properties, just with Id at the end. In other words, for it to automatically match up OwnerId, you'd need a navigation property like:
public User Owner { get; set; }
Since your navigation property is UserId, it's actually looking for a property named UserIdId.
If you don't want to follow conventions, then you must either use the ForeignKey attribute or fluent config to tell EF which property belongs with which.
That said, there's some pretty major issues with your naming of things here. First, entities should always be singular User, not Users. Second, you should not have navigation properties that end with Id: e.g., User, not UserId. Only actual PK or FK properties should end with with Id. Finally, don't prefix properties on your entity with the entity name. This last one is mostly for readability. Which is more natural: competition.Id or competition.CompetitionId? Likewise with CompetitionName; it should just be Name. And, for what it's worth, you don't need Required for either a primary key or a non-nullable type (such as int). In either case, the property is required by default.
I am doing the entity framework code first to set up my database.
I have two classes where their relationships are one-to-one, Lecturer & LoginInfo.
public class Lecturer
{
public int LecturerId { get; set; }
public string FullName { get; set; }
public int UserId { get; set; }
public LoginInfo LoginInfo { get; set; }
}
public class LoginInfo
{
public int UserId { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public bool ChangePassword { get; set; }
public Lecturer Lecturer { get; set; }
}
So for my entity framework, I have written this for the one-to-one relationship.
modelBuilder.Entity<Lecturer>()
.HasOne(input => input.LoginInfo)
.WithOne(input => input.Lecturer)
.HasForeignKey<Lecturer>(input => input.UserId);
From the code above, does it mean that Lecture has one LoginInfo, LoginInfo with one Lecturer and Lecturer has a UserId as a foreign key?
Another question would be, for this one to one relationship, do I have to write another set of code for LoginInfo like this:
modelBuilder.Entity<LoginInfo>()
.HasOne(input => input.Lecturer)
.WithOne(input => input.LoginInfo)
.HasForeignKey<LoginInfo>(input => input.LecturerId);
I am just a beginner trying to learn, thanks for helping :).
From the code above, does it mean that Lecture has one LoginInfo, LoginInfo with one Lecturer and Lecturer has a UserId as a foreign key?
Correct. It also means that LoginInfo is the principal and Lecturer is the dependent end of the relationship. Also since the FK property is not nullable, the relationship is required, i.e. in order to create Lecturer you have to create LoginInfo first, which is not associated with another Lecturer.
Also note that since the UserId is not following the default convention for PK name, you should explicitly configure it as PK of the LoginInfo:
modelBuilder.Entity<LoginInfo>()
.HasKey(e => e.UserId);
Another question would be, for this one to one relationship, do I have to write another set of code for LoginInfo like this
No. Single relationship requires single configuration and single FK. If you do so, you would be defining a second relationship, which also would create circular dependency between the entity, which should be avoided in general. The first fluent configuration fully defines the desired relationship and is enough to handle loading related data and other CRUD operations.
For more info about terms, relationship types and configuration, see Relationships section of the EF Core documentation.
I'm having issues with Nhibernate persisting a HasOne Relationship for one of my entities with Cascade.None() in effect. My domain model involves 4 classes listed below.
public class Project
{
public virtual int Id {get;set;}
public virtual IList<ProjectRole> Team { get; protected set; }
}
public class ProjectRole
{
public virtual int Id { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
public class Role
{
public virtual int Id { get; set; }
public virtual string Value { get; set; }
}
public class User
{
public virtual int Id { get; protected set; }
public virtual string LoginName { get; set; }
}
So basically we have projects, which have a list of ProjectRoles available from the Team property. Each ProjectRole links a User to the specific Role they play on that project.
I'm trying to setup the following cascade relationships for these entities.
project.HasMany<ProjectRoles>(p=> p.Team).Cascade.All()
projectRole.HasOne<Role>(r => r.Role).Cascade.None()
projectRole.HasOne<User>(r => r.User).Cascade.SaveUpdate()
I've used fluent nhibernate overrides to setup the cascades as above, but I'm finding that the line
projectRole.HasOne<Role>(r => r.Role).Cascade.None()
is resulting in the ProjectRole.Role property not being saved to the database. I've diagnosed this be looking at the SQL Generated by Nhibernate and I can see that the "Role_id" column in the ProjectRoles table is never set on update or insert.
I've also tried using
projectRole.HasOne<Role>(r => r.Role).Cascade.SaveUpdate()
but that fails as well. Unfortunately leaving it Cascade.All() is not an option as that results in the system deleting the Role objects when I try to delete a project role.
Any idea how to setup Cascade.None() for the ProjectRole-> Role relationship with out breaking persistence.
HasOne is for a one-to-one relationship which are rare. You want to use References to declare the one side of a one-to-many relationship. Making some assumptions about your domain model, the mapping should look like:
project.HasMany<ProjectRoles>(p=> p.Team).Inverse().Cascade.AllDeleteOrphan()
projectRole.References<Role>(r => r.Role);
projectRole.References<User>(r => r.User);
See also this question about the difference between HasOne and References.
How do you handle look up values with NHibernate? For example, I have an Order entity and it has a BillingAddress property that is a value object named Address, it's simple if the Address object just contains State and Country properties as strings. But what if I want a Country contains a list of its states the Order form can populate appropriate state dropdown list for each selected country.
Can I still create Country and State as value objects? Or they must be entities? And if they are entities, can Address be a value object?
Below is a sample code of my example:
public class Order
{
public virtual int OrderId { get; set; }
public virtual Address BillingAddress { get; set; }
}
public class Address
{
public virtual State State { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public virtual string Name { get; set; }
public virtual ICollection<State> States { get; set; }
}
public class State
{
public virtual string Name { get; set; }
public virtual Country Country { get; set; }
}
If you want to store the lookup data in the database, then they need to be entities. Otherwise, it is up to you. If you do, I suggest marking them as immutable and putting them in a read only 2nd-layer cache.
If you store them as values, and they have multiple fields like Abbrevation, Name, Coordinates, etc. then you can save the id as a value in the data store, and have the lookup data hard-coded as a plain C# class. You'll just retrieve the id value from NHibernate, and then your calling code will have to run the lookup methods on the class. Not as elegant, but simplifies from the NHibernate/database perspective.
Either method is acceptable--it more depends on how you plan on using them: who is maintaining and using the code at each level, where you want the caching and/or lookup code, if you control the calling code or not, etc.
Database Strucutre:
Shows
ID
Name
Genres
ID
Name
ShowsGenres
ShowsID
GenresID
Above is my Database I am trying to figure out how to map this properly. My Show object is like this:
public class Show
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Genre> Genres { get; set; }
}
My Genre Object is:
public class Genre
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Show> Shows { get; set; }
}
I have tried several different varations of HasManyToMany, but none work the way I want them to.
I need to be able to delete a show and the relationship with the genre, or many genres, but not delete the genre(s).
I need to be able to delete a genre and its relationship with a show, or many shows, but not delete the show(s).
How can I map this or do I need to try something differently?
Update: Also thinking about it more I would also need to be able to remove the relationship between a show and a genre without removing the show or the genre.
Here are my mappings I have, but not exactly sure they are correct.
HasManyToMany<Genre>(x => x.Genres)
.Table("ShowGenres")
.ParentKeyColumn("ShowID")
.ChildKeyColumn("GenreID");
HasManyToMany<Show>(x => x.Shows)
.Table("ShowGenres")
.ParentKeyColumn("GenreID")
.ChildKeyColumn("ShowID");
This is an old post but I basically had the same question. Answered here:
HasManyToMany Fluent NHibernate Mapping Delete Error