I have a parent-child relationship that I've put a test case together between Users and Groups. I did this to replicate a failure in
a Parent-Child relationship when trying to perform a cacade insert using thes relationship.
The two SQL tables are as follows:
CREATE TABLE [dbo].[User]
(
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [varchar](50) NOT NULL,
)
CREATE TABLE [dbo].[Group]
(
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[GroupName] [varchar](50) NOT NULL,
[UserId] [int] NOT NULL,
)
ALTER TABLE [dbo].[Group] WITH CHECK ADD CONSTRAINT [FK_Group_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([Id])
The objects represent these two tables with the following mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("[User]");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable();
HasMany(x => x.Groups).KeyColumn("UserId").Cascade.SaveUpdate();
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table("[Group]");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.GroupName).Not.Nullable();
References(x => x.User).Column("UserId").Not.Nullable();
}
}
The code to created the objects is simply:
User u = new User() { Name = "test" };
Group g = new Group() { GroupName = "Test Group" };
u.Groups.Add(g);
using (var session = factory.OpenSession())
{
session.SaveOrUpdate(u);
}
However it fails with exception "Cannot insert the value NULL into column 'UserId', table 'test.dbo.Group'; column does not allow nulls. INSERT fails.
The statement has been terminated". I suspect that this is dude to the parent object's Id (an identity column) being passed through as NULL and not the new values. Is this a bug or is there a way to fix these mappings so that this cascade relationship succeeds?
I recently had this exact type of mapping working fine in a project. My advice is:
Learn how the Inverse attribute of a HasMany relationship works. Great explanation here
You need a two way association between the parent and child object. This is explained at the bottom of the article linked to above.
Another good advice is to encapsulate your collections better
- Don't access your collections modification methods directly. The collection properties should be read-only and the parent (User class in your case) should have AddGroup() and RemoveGroup() methods that changes the private collection. In order for this to work you have to let NHibernate access the private collection member by using the .Access.CamelCaseField(Prefix.Underscore) or similar mapping attribute. Good discussion about it here
I can post an example mapping and class files if needed.
You will have to save the user first then assign the group to the user and save that:
using (var session = factory.OpenSession())
{
User u = new User() { Name = "test"};
session.SaveOrUpdate(u);
Group g = new Group() { GroupName = "Test Group", User = u };
session.SaveOrUpdate(g)
}
I have found that you cannot cascade save child /parent related objects which have only just been created.
Related
I have the following mapping to support a many to many table (User_Role)
modelBuilder.Entity<Role>()
.HasMany<User>(u => u.users)
.WithMany(r => r.roles)
.Map(m =>
m.MapLeftKey("role_id")
m.MapRightKey("user_id")
m.ToTable("User_Role"));
This works great from mapping many roles to a user and many users to a role. The problem I am having is when I need to add a new row to the table User_Role via an entity as follows:
public class User_Role
{
[Key Column(Order=1)]
public int role_id {get;set;)
[Key Column(Order=1)]
public int user_id {get;set;)
}
Whenever I try to access this entity as follows:
dbContext.User_Role.Add(new User_Role {user_id ....
EF looks for a non existent table called User_Role1 ... its adding a '1' to the table name.
I then tried to add:
[Table("User_Role")]
This takes care of the adding a '1' but I now get this error:
"The EntitySet 'RoleUser' with schema 'dbo' and table 'User_Role' was already defined. Each EntitySet must refer to a unique schema and table"
I was able to confirm that the following lines together are causing the problem but I kind of need them both
m.ToTable("User_Role") and public class User_Role..
Any suggestions would be great...I am stumped.
You cannot represent the join table in a many-to-many relationship by an entity class in your model. This join table is managed by EF and you cannot directly access this table. If you want to create a relationship between an existing user and an existing role you must do this using these two entities:
var user = dbContext.Users.Single(u => u.id == user_id);
var role = dbContext.Roles.Single(r => r.id == role_id);
// if you don't have lazy loading and don't instantiate the collection
// in the constructor, add this: user.roles = new List<Role>();
user.roles.Add(role);
dbContext.SaveChanges();
This will write an entry for the new relationship (the (user_id,role_id) pair) into the join table.
Edit
If you only have the two key properties at hand and don't want to load the user and role from the database you can work with attached "stub entities":
var user = new User { id = user_id, roles = new List<Role>() };
var role = new Role { id = role_id };
dbContext.Users.Attach(user);
dbContext.Roles.Attach(role);
user.roles.Add(role);
dbContext.SaveChanges();
I have a model Post -> hasAndBelongsToMany -> Tag
For testing, I created the fixtures for each model, for example, a fixture for the model Post looks like this
class PostFixture extends CakeTestFixture {
var $import = array('model' => 'Post', 'records' => true, 'connection' => 'fixtures');
}
And everything works great for that Model, but when I try to create the fixture for the HABTM relationship, using the same approach doesn’t work:
class PostsTagFixture extends CakeTestFixture {
var $import = array('model' => 'PostTag', 'records' => true, 'connection' => 'fixtures');
}
The SQL generated by CakePHP is the following
CREATE TABLE `service_types_technicals` (
`technical_id` int(20) NOT NULL AUTO_INCREMENT,
`service_type_id` int(20) NOT NULL AUTO_INCREMENT,
`id` varchar(255) NOT NULL, PRIMARY KEY (`id`)) ;
Wich is not correct because the table does not have a field named id.
Then, I tried this:
class PostsTagFixture extends CakeTestFixture {
var $name = 'PostsTag';
var $import = array('table' => 'posts_tags', 'records' => true, 'connection' => 'fixtures');
}
And again, error, but this time the SQL was:
CREATE TABLE `service_types_technicals` (
`technical_id` int(20) NOT NULL AUTO_INCREMENT,
`service_type_id` int(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`service_type_id`)) ;
What am I doing wrong? What is the correct way to import fixtures from another database for has and belongs to many relationships?
Thank you!
Mauro Zadunaisky,
as you state that service_types_technicals does not have an id field, I guess that CakePHP automatically deduces a HABTM from service_types to technicals, as both are nouns written in plural (CakePHP convention). If this is not what you had in mind, then you are forced to alter the name of the table to stay within the conventions.
The problem was the missing of the field id in the habtm relation table, it's fixed now
I'm trying to map a List with an index column. This works fine, but I would also like to be able to query the index column from HQL. When I do that HQL throws an exception:
NHibernate.QueryException: could not resolve property: Position of: component[Location,Time]
To be able to query the WayPoint.Position column from HQL, I have created a Position-property in the WayPoint-class, and I would like to map this property to the Position-column.
I have tried with:
wp.Map(x => x.Position).Column("Position");
But this results in a MappingException:
NHibernate.MappingException: Repeated column in mapping for collection: Route.WayPoints column: Position
public class RouteMap : ClassMap<Route>
{
private const string LocationKey = "LocationIndex";
public RouteMap()
{
Not.LazyLoad();
ReadOnly();
Id(x => x.Id).GeneratedBy.Assigned();
Version(x => x.Version);
Map(x => x.Time);
References(x => x.StartingPoint).UniqueKey(LocationKey);
References(x => x.Destination).UniqueKey(LocationKey);
HasMany(x => x.WayPoints).Component( wp =>
{
wp.Map(x => x.Time);
//wp.Map(x => x.Position).Column("Position");
wp.Component( wp2 => wp2.Location, gl =>
{
gl.Map(x => x.Latitude);
gl.Map(x => x.Longitude);
}
);
}
).AsList(index => index.Column("Position")).Cascade.All();
}
}
create table `Route` (
Id VARCHAR(40) not null,
Version INTEGER not null,
Time BIGINT,
StartingPoint_id VARCHAR(40),
Destination_id VARCHAR(40),
primary key (Id),
unique (StartingPoint_id, Destination_id)
)
create table WayPoints (
Route_id VARCHAR(40) not null,
Latitude DOUBLE,
Longitude DOUBLE,
Time BIGINT,
Position INTEGER not null,
primary key (Route_id, Position)
)
Is it possible to map the Position property? Or is there another approach to make HQL aware of the Position-index-field?
I don't think you need to map the Position property at all; if you need to query on the index of a collection, you can do so as follows:
select item, index(item) from Order order
join order.Items item
where index(item) < 5
More information can be found in the HQL Documentation.
Try adding .ReadOnly() to your wp.Map(x => x.Position).Column("Position"); That has allowed me to map multiple properties to the same column before, at least when one of them was a References().
I have a legacy system where the relationship between 2 tables haven't been defined explictly and there aren't any keys (primary or unqiue defined). The only related columns is 'Username'
Something like:
Table: Customer
Column: Id,
Column: Username,
Column: FirstName,
Table: Customer_NEW
Column: Username
Column: FirstNameNew
I have following Mapping definitions:
public sealed class CustomerMap : ClassMap<Customer>, IMap
{
public CustomerMap()
{
WithTable("customers");
Not.LazyLoad();
Id(x => x.Id).GeneratedBy.Increment();
References(x => x.CustomerNew, "Username")
.WithForeignKey("Username")
.Cascade.All();
}
public sealed class CustomerNewMap : ClassMap<CustomerNew>, IMap
{
public CustomerNewMap()
{
WithTable("customers_NEW");
Not.LazyLoad();
Id(x => x.Username).GeneratedBy.Assigned();
Map(x => x.FirstNameNew);
}
}
The problem is nHibernate is generating an UPDATE statement for the 'Reference' in the Customer Mapping definition which is obviously failing when attempting to insert a new Customer object which has a reference to CustomerNew object.
How do I get the mapping definition to generate the INSERT statement instead of an UPDATE for the 'Save' command?
Cheers in advance
Ollie
The reason is because there isn't any referential integrity in the database, no primary keys, composite keys not foreign key constraints…
Yet again another application that has a database that’s not fit for purpose…
The following is a simplification of my problem domain. We have a series of Trades, that each gets a record in Valuations every business day.
We filter our Valuations List used for a specific day, but to populate the Trade against each of the Valuation rows, NHibernate fires single row selects on the Trades table for around 50k rows in the valuation table. How can I change this so NHibernate does a single select on the Trade table?
CREATE TABLE Trades
( TradeId INT
, InstrumentType VARCHAR(20)
, TradeDate DATETIME
, PRIMARY KEY
( TradeId ) )
CREATE TABLE Valuations
( TradeId INT
, ValueDate DATETIME
, PresentValue NUMERIC(12,4)
, PRIMARY KEY
( TradeId
, ValueDate ) )
.
class Trade
{
public int TradeId;
public string InstrumentType;
public DateTime TradeDate;
}
class Valuation
{
public int TradeId;
public DateTime ValueDate;
public double PresentValue;
public Trade Trade;
}
.
class ValuationMap : ClassMap<Valuation>
{
public ValuationMap()
{
WithTable("Valuations");
UseCompositeId()
.WithKeyProperty(x => x.ValueDate)
.WithKeyProperty(x => x.TradeId);
Map(x => x.PresentValue);
References(x => x.Trade, "TradeId")
.LazyLoad()
.Cascade.None()
.NotFound.Ignore()
.FetchType.Join();
}
}
class TradeMap : ClassMap<Trade>
{
public TradeMap()
{
WithTable("Trades");
Id( x => x.TradeId );
Map(x => x.InstrumentType);
Map(x => x.TradeDate);
Map(x => x.Valuations);
}
}
.
public List<Valuation> GetValuations(DateTime valueDate)
{
return (from p in _nhibernateSession.Linq<Valuation>()
where p.ValueDate == valueDate
select p).ToList();
}
You should also look at batch fetching. This is quoted from the Nhib manual - actually google's cache as the site seems to be down for maintenance atm:
Imagine you have the following
situation at runtime: You have 25 Cat
instances loaded in an ISession, each
Cat has a reference to its Owner, a
Person. The Person class is mapped
with a proxy, lazy="true". If you now
iterate through all cats and get the
Owner of each, NHibernate will by
default execute 25 SELECT statements,
to retrieve the proxied owners. You
can tune this behavior by specifying a
batch-size in the mapping of Person:
<class name="Person" lazy="true" batch-size="10">...</class>
NHibernate will now execute only three
queries, the pattern is 10, 10, 5. You
can see that batch fetching is a blind
guess, as far as performance
optimization goes, it depends on the
number of unitilized proxies in a
particular ISession.