One-to-one mapping in S#arp Architecture - nhibernate

There's a distinct smell of burned out circuits coming from my head, so forgive my ignorance.
I'm trying to setup a one-to-one relationship (well, let Automapper do it) in S#arp Architecture.
I have
public class User : Entity
{
public virtual Profile Profile { get; set; }
public virtual Basket Basket { get; set; }
public virtual IList<Order> Orders { get; set; }
public virtual IList<Role> Roles { get; set; }
...
}
public class Basket : Entity
{
public virtual User User { get; set; }
...
}
public class Profile : Entity
{
public virtual User User { get; set; }
...
}
And my db schema is
CREATE TABLE [dbo].[Users](
[Id] [int] IDENTITY(1,1) NOT NULL,
...
CREATE TABLE [dbo].[Profiles](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserFk] [int] NULL,
...
CREATE TABLE [dbo].[Baskets](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserFk] [int] NULL,
...
When I run the unit test CanConfirmDatabaseMatchesMappings in MappingIntegrationTests I get the following error
NHibernate.ADOException : could not
execute query ...
System.Data.SqlClient.SqlException :
Invalid column name 'ProfileFk'.
Invalid column name 'BasketFk'.
and the sql it's trying to execute is
SELECT TOP 0
this_.Id AS Id6_1_ ,
..
user2_.ProfileFk AS ProfileFk9_0_ ,
user2_.BasketFk AS BasketFk9_0_
FROM
Profiles this_
LEFT OUTER JOIN Users user2_
ON this_.UserFk = user2_.Id
So it's looking for a ProfileFk and BasketFk field in the Users table.
I haven't setup any customer override mappings and as far as I can see I've followed the default conventions setup in S#.
The two other mappings for IList Orders and Roles seem to map fine. So I'm guessing that it've missed something for setting up a one-to-one relationship.
What am I missing?

Got it. This is really an NHibernate problem to solve with Fluent NHibernate syntax, but it happens to be relevant for S#.
Background reading: NHibernate Mapping and Fluent NHibernate HasOne
What you do is override the mapping for User and give it two .HasOne mappings. Then set a unique reference to the user on the Profile and Basket class:
public class UserMap : IAutoMappingOverride<User>
{
#region Implementation of IAutoMappingOverride<User>
/// <summary>
/// Alter the automapping for this type
/// </summary>
/// <param name="mapping">Automapping</param>
public void Override(AutoMapping<User> mapping)
{
mapping.HasOne(u => u.Profile);
mapping.HasOne(u => u.Basket);
}
#endregion
}
public class ProfileMap : IAutoMappingOverride<Profile>
{
#region Implementation of IAutoMappingOverride<Profile>
/// <summary>
/// Alter the automapping for this type
/// </summary>
/// <param name="mapping">Automapping</param>
public void Override(AutoMapping<Profile> mapping)
{
mapping.References(p => p.User).Unique().Column("UserFk");
}
#endregion
}
public class BasketMap : IAutoMappingOverride<Basket>
{
#region Implementation of IAutoMappingOverride<Basket>
/// <summary>
/// Alter the automapping for this type
/// </summary>
/// <param name="mapping">Automapping</param>
public void Override(AutoMapping<Basket> mapping)
{
mapping.References(b => b.User).Unique().Column("UserFk");
}
#endregion
}
As a side note, at the time of writing this, NHibernate 3 has just been released. There's a great book out called NHibernate 3.0 Cookbook which I've just bought and it looks extremely useful for working with S#.

Related

LINQ - What is the discriminator? [duplicate]

I have a table in my database called SEntries (see below the CREATE TABLE statement). It has a primary key, a couple of foreign keys and nothing special about it. I have many tables in my database similar to that one, but for some reason, this table ended up with a "Discriminator" column on the EF Proxy Class.
This is how the class is declared in C#:
public class SEntry
{
public long SEntryId { get; set; }
public long OriginatorId { get; set; }
public DateTime DatePosted { get; set; }
public string Message { get; set; }
public byte DataEntrySource { get; set; }
public string SourceLink { get; set; }
public int SourceAppId { get; set; }
public int? LocationId { get; set; }
public long? ActivityId { get; set; }
public short OriginatorObjectTypeId { get; set; }
}
public class EMData : DbContext
{
public DbSet<SEntry> SEntries { get; set; }
...
}
When I try to add a new row to that table, I get the error:
System.Data.SqlClient.SqlException: Invalid column name 'Discriminator'.
This problem only occurs if you are inheriting your C# class from another class, but SEntry is not inheriting from anything (as you can see above).
In addition to that, once I get the tool-tip on the debugger when I mouse over the EMData instance for the SEntries property, it displays:
base {System.Data.Entity.Infrastructure.DbQuery<EM.SEntry>} = {SELECT
[Extent1].[Discriminator] AS [Discriminator],
[Extent1].[SEntryId] AS [SEntryId],
[Extent1].[OriginatorId] AS [OriginatorId],
[Extent1].[DatePosted] AS [DatePosted],
[Extent1].[Message] AS [Message],
[Extent1].[DataEntrySource] AS [DataE...
Any suggestions or ideas where to get to the bottom of this issue? I tried renaming the table, the primary key and a few other things, but nothing works.
SQL-Table:
CREATE TABLE [dbo].[SEntries](
[SEntryId] [bigint] IDENTITY(1125899906842624,1) NOT NULL,
[OriginatorId] [bigint] NOT NULL,
[DatePosted] [datetime] NOT NULL,
[Message] [nvarchar](500) NOT NULL,
[DataEntrySource] [tinyint] NOT NULL,
[SourceLink] [nvarchar](100) NULL,
[SourceAppId] [int] NOT NULL,
[LocationId] [int] NULL,
[ActivityId] [bigint] NULL,
[OriginatorObjectTypeId] [smallint] NOT NULL,
CONSTRAINT [PK_SEntries] PRIMARY KEY CLUSTERED
(
[SEntryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[SEntries] WITH CHECK ADD CONSTRAINT [FK_SEntries_ObjectTypes] FOREIGN KEY([OriginatorObjectTypeId])
REFERENCES [dbo].[ObjectTypes] ([ObjectTypeId])
GO
ALTER TABLE [dbo].[SEntries] CHECK CONSTRAINT [FK_SEntries_ObjectTypes]
GO
ALTER TABLE [dbo].[SEntries] WITH CHECK ADD CONSTRAINT [FK_SEntries_SourceApps] FOREIGN KEY([SourceAppId])
REFERENCES [dbo].[SourceApps] ([SourceAppId])
GO
ALTER TABLE [dbo].[SEntries] CHECK CONSTRAINT [FK_SEntries_SourceApps]
GO
Turns out that Entity Framework will assume that any class that inherits from a POCO class that is mapped to a table on the database requires a Discriminator column, even if the derived class will not be saved to the DB.
The solution is quite simple and you just need to add [NotMapped] as an attribute of the derived class.
Example:
class Person
{
public string Name { get; set; }
}
[NotMapped]
class PersonViewModel : Person
{
public bool UpdateProfile { get; set; }
}
Now, even if you map the Person class to the Person table on the database, a "Discriminator" column will not be created because the derived class has [NotMapped].
As an additional tip, you can use [NotMapped] to properties you don't want to map to a field on the DB.
Here is the Fluent API syntax.
http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName {
get {
return this.FirstName + " " + this.LastName;
}
}
}
class PersonViewModel : Person
{
public bool UpdateProfile { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ignore a type that is not mapped to a database table
modelBuilder.Ignore<PersonViewModel>();
// ignore a property that is not mapped to a database column
modelBuilder.Entity<Person>()
.Ignore(p => p.FullName);
}
I just encountered this and my problem was caused by having two entities both with the System.ComponentModel.DataAnnotations.Schema.TableAttribute referring to the same table.
for example:
[Table("foo")]
public class foo
{
// some stuff here
}
[Table("foo")]
public class fooExtended
{
// more stuff here
}
changing the second one from foo to foo_extended fixed this for me and I'm now using Table Per Type (TPT)
I had a similar problem, not exactly the same conditions and then i saw this post. Hope it helps someone. Apparently i was using one of my EF entity models a base class for a type that was not specified as a db set in my dbcontext. To fix this issue i had to create a base class that had all the properties common to the two types and inherit from the new base class among the two types.
Example:
//Bad Flow
//class defined in dbcontext as a dbset
public class Customer{
public int Id {get; set;}
public string Name {get; set;}
}
//class not defined in dbcontext as a dbset
public class DuplicateCustomer:Customer{
public object DuplicateId {get; set;}
}
//Good/Correct flow*
//Common base class
public class CustomerBase{
public int Id {get; set;}
public string Name {get; set;}
}
//entity model referenced in dbcontext as a dbset
public class Customer: CustomerBase{
}
//entity model not referenced in dbcontext as a dbset
public class DuplicateCustomer:CustomerBase{
public object DuplicateId {get; set;}
}
Another scenario where this occurs is when you have a base class and one or more subclasses, where at least one of the subclasses introduce extra properties:
class Folder {
[key]
public string Id { get; set; }
public string Name { get; set; }
}
// Adds no props, but comes from a different view in the db to Folder:
class SomeKindOfFolder: Folder {
}
// Adds some props, but comes from a different view in the db to Folder:
class AnotherKindOfFolder: Folder {
public string FolderAttributes { get; set; }
}
If these are mapped in the DbContext like below, the "'Invalid column name 'Discriminator'" error occurs when any type based on Folder base type is accessed:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Folder>().ToTable("All_Folders");
modelBuilder.Entity<SomeKindOfFolder>().ToTable("Some_Kind_Of_Folders");
modelBuilder.Entity<AnotherKindOfFolder>().ToTable("Another_Kind_Of_Folders");
}
I found that to fix the issue, we extract the props of Folder to a base class (which is not mapped in OnModelCreating()) like so - OnModelCreating should be unchanged:
class FolderBase {
[key]
public string Id { get; set; }
public string Name { get; set; }
}
class Folder: FolderBase {
}
class SomeKindOfFolder: FolderBase {
}
class AnotherKindOfFolder: FolderBase {
public string FolderAttributes { get; set; }
}
This eliminates the issue, but I don't know why!
I get the error in another situation, and here are the problem and the solution:
I have 2 classes derived from a same base class named LevledItem:
public partial class Team : LeveledItem
{
//Everything is ok here!
}
public partial class Story : LeveledItem
{
//Everything is ok here!
}
But in their DbContext, I copied some code but forget to change one of the class name:
public class MFCTeamDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Other codes here
modelBuilder.Entity<LeveledItem>()
.Map<Team>(m => m.Requires("Type").HasValue(ItemType.Team));
}
public class ProductBacklogDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Other codes here
modelBuilder.Entity<LeveledItem>()
.Map<Team>(m => m.Requires("Type").HasValue(ItemType.Story));
}
Yes, the second Map< Team> should be Map< Story>.
And it cost me half a day to figure it out!
Old Q, but for posterity...it also also happens (.NET Core 2.1) if you have a self-referencing navigation property ("Parent" or "Children" of the same type) but the Id property name isn't what EF expects. That is, I had an "Id" property on my class called WorkflowBase, and it had an array of related child steps, which were also of type WorkflowBase, and it kept trying to associate them with a non-existent "WorkflowBaseId" (the name i suppose it prefers as a natural/conventional default). I had to explicitly configure it using HasMany(), WithOne(), and HasConstraintName() to tell it how to traverse. But I spent a few hours thinking the problem was in 'locally' mapping the object's primary key, which i attempted to fix a bunch of different ways but which was probably always working.
this error happen with me because I did the following
I changed Column name of table in database
(I did not used Update Model from database in Edmx) I Renamed manually Property name to match the change in database schema
I did some refactoring to change name of the property in the class to be the same as database schema and models in Edmx
Although all of this, I got this error
so what to do
I Deleted the model from Edmx
Right Click and Update Model from database
this will regenerate the model, and entity framework will not give you this error
hope this help you

Fluent Nhibernate Cascade.None() results in HasOne foreign key relationship not being persisted

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.

Fluent NH - illegal access to loading collection

In the CategoriesTranslated collection i have this error: illegal access to loading collection.
public class Category : Entity
{
public Category()
{
CategoriesTranslated = new List<CategoryTranslated>();
}
public virtual Category Parent { get; set; }
public virtual string Name { get; set; }
public virtual IList<CategoryTranslated> CategoriesTranslated { get; set; }
}
public class CategoryTranslated : Entity
{
public CategoryTranslated()
{
}
public virtual Category Category { get; set; }
public virtual LanguageType Language { get; set; }
public virtual string Name { get; set; }
}
public void Override(AutoMapping<Category> mapping)
{
mapping.HasMany(x => x.CategoriesTranslated)
.Inverse()
.Cascade.All();
}
public void Override(AutoMapping<CategoryTranslated> mapping)
{
mapping.References(x => x.Category);
}
The SQL:
CREATE TABLE Category(
[Id] smallint primary key identity(1,1),
[Parent] smallint null,
[Name] varchar(50) not null unique,
)
alter table [Category] add CONSTRAINT fk_Category_Category
FOREIGN KEY(Parent) references Category (Id)
go
CREATE TABLE CategoryTranslated(
[Id] smallint primary key identity(1,1),
[Category] smallint not null,
[Language] tinyint not null,
[Name] varchar(50) not null,
)
alter table [CategoryTranslated] add CONSTRAINT fk_CategoryTranslated_Category
FOREIGN KEY(Category) references Category (Id)
go
Where is it wrong?
UPDATE
The links to the hbm generater:
Category:
http://uploading.com/files/fmb71565/SubmitSiteDirectory.Core.Category.hbm.xml/
Category translated:
http://uploading.com/files/9c9aaem9/SubmitSiteDirectory.Core.CategoryTranslated.hbm.xml/
I am guessing it has to do with the creation of the list inside the constructor, especially if you left a default ctor for NHib. And that NHib is trying to set the list before it's created. The other complication here is that you have a bi-directional relationship, and CategoryTranslated may be trying to get at the list before its created too.
I doubt this is the only solution, but here is a pattern I use that should solve the error:
/// <summary>Gets the ....</summary>
/// <remarks>This is what is available to outside clients of the domain.</remarks>
public virtual IEnumerable<CategoryTranslated> CategoriesTranslated{ get { return _InternalCategoriesTranslated; } }
/// <summary>Workhorse property that maintains the set of translated categories by:
/// <item>being available to <see cref="Category"/> to maintain data integrity.</item>
/// <item>lazily instantiating the <see cref="List{T}"/> when it is needed.</item>
/// <item>being the property mapped to NHibernate, the private <see cref="_categoriesTranslated"/> field is set here.</item>
/// </list>
/// </summary>
protected internal virtual IList<Category> _InternalCategoriesTranslated
{
get { return _categoriesTranslated?? (_categoriesTranslated= new List<Category>()); }
set { _categoriesTranslated= value; }
}
private IList<StaffMember> _categoriesTranslated;
Now you need to set your mapping to access the private field, so assuming you use my casing preferences here, you'd have:
public void Override(AutoMapping<Category> mapping)
{
mapping.HasMany(x => x.CategoriesTranslated)
.Inverse()
.Access.CamelCaseField(CamelCasePrefix.Underscore)
.Cascade.All();
}
HTH,
Berryl
EDIT ============
The _Internal collection also gives the child of of the bi-directional relationship, CategoryTranslated in this case, a hook, as shown in the code below:
public virtual CategoryTranslated CategoryTranslated
{
get { return _categoryTranslated; }
set
{
if (_categoryTranslated!= null)
{
// disassociate existing relationship
_categoryTranslated._InternalCategoryTranslated.Remove(this);
}
_categoryTranslated= value;
if (value != null)
{
//make the association
_categoryTranslated._InternalCategoryTranslated.Add(this);
}
}
}
private CategoryTranslated _categoryTranslated;

Fluent NHibernate Mapping - Composite Key

I'm trying to map the following tables/entities in FNH and seem to be getting nowhere fast!
**Tables**
Contacts
ID (PK - int - generated)
...
PhoneTypes
ID (PK - varchar - assigned) (e.g. MOBILE, FAX)
ContactPhones
ContactRefId (PK - FK to Contacts)
PhoneTypeRefId (PK - FK to PhoneTypes)
...
(I should note that I am also using the S#arp Architecture framework)
**Entities**
public class Contact : Entity
{
(The ID property is defined in the Entity base class and is type int)
public virtual ICollection<ContactPhone> PhoneNumbers { get; set; }
}
public class PhoneType : EntityWithTypedId<string>, IHasAssignedId<string>
{
(The ID property is defined in the base class and is type string)
....
}
public class ContactPhone : EntityWithTypedId<ContactPhoneId>, IHasAssignedId<ContactPhoneId>
{
public virtual Contact Contact { get; set; }
public virtual PhoneType PhoneType { get; set; }
....
}
I read that it is advisable when working with composite ids, to separate the composite id into a different class.
hibernate composite key
public class ContactPhoneId : EntityWithTypedId<ContactPhoneId>, IHasAssignedId<ContactPhoneId>
{
public virtual Contact Contact { get; set; }
public virtual PhoneType PhoneType { get; set; }
}
...I could just make this class serializable and override
Equals and GetHashCode myself instead of using the S#arp Arch base class.
I've tried so many combinations of mappings that I'm now completely confused.
This is my latest shot:
public class ContactMap : IAutoMappingOverride<Contact>
{
public void Override(AutoMapping<Contact> mapping)
{
mapping.HasMany<ContactPhone>(x => x.PhoneNumbers)
.KeyColumns.Add("ContactRefId")
.KeyColumns.Add("PhoneTypeRefId")
.AsSet()
.Inverse()
.Cascade.All();
}
}
public class PhoneTypeMap : IAutoMappingOverride<PhoneType>
{
public void Override(AutoMapping<PhoneType> mapping)
{
mapping.Id(x => x.Id).Column("Id").GeneratedBy.Assigned();
}
}
public class ContactPhoneMap : IAutoMappingOverride<ContactPhone>
{
public void Override(AutoMapping<ContactPhone> mapping)
{
mapping.Table("ContactPhones");
mapping.CompositeId<ContactPhoneId>(x => x.Id)
.KeyReference(y => y.Contact, "ContactRefId")
.KeyReference(y => y.PhoneType, "PhoneTypeRefId");
}
}
I've had many exceptions thrown when trying to generate the mappings, the latest of which is:
Foreign key (FK672D91AE7F050F12:ContactPhones [ContactRefId, PhoneTypeRefId]))
must have same number of columns as the referenced primary key (Contacts [Id])
Does anyone see anything obvious that I'm doing wrong? I'm new to NH and FNH, which may be obvious from this post. :-) Also, has anyone used Composite Ids like this while using S#arp Architecture? What are the best practices (other than to use surrogate keys :-) ) ?
Many thanks...and sorry about the long post.
I have a many to many relationship too. I've got mine setup like this:
mapping.HasManyToMany(x => x.Artists).Cascade.All().Inverse().Table("ArtistImages");
The ArtistImages table has primary keys for tables Artists and Images.

Mapping a a simple collection of elements with Fluent NHibernate

I'm trying to map a collection of enum values with Fluent NHibernate.
IList<EnumType> lst;
I can't find any documentation about it but I'm quite sure it should be possible.
I had no problem at all with mapping a collection of Entities.
Thanks,
Leonardo
You can use the following FNH mapping signature to map simple value type collections.
HasMany(x => x.Collection)
.Table("TableName")
.KeyColumn("KeyColumnName")
.Element("ValueColumnName");
Where:
Collection: the collection of value types (can be enum because that will be mapped as int).
TableName: the name of the table that will store your collection values.
KeyColumnName: the name of the column that will store the key value back to the parent.
ValueColumnName : the name of the column that will store the actual value.
Lets see an example of how to map a few collections of value types.
public enum EnumType
{
Value1,
Value2,
Value3
}
public class Entity
{
/// <summary>
/// Primary key
/// </summary>
public virtual int Id { get; set; }
/// <summary>
/// Collection of strings
/// </summary>
public virtual IList<string> StringCollection { get; set; }
/// <summary>
/// Collection of enums
/// </summary>
public virtual IList<EnumType> EnumCollection { get; set; }
/// <summary>
/// Collection of dates/times
/// </summary>
public virtual IList<DateTime> DateTimeCollection { get; set; }
}
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
// Map primary key.
Id(x => x.Id);
// Map value collections
HasMany(x => x.StringCollection)
.Table("Entity_String")
.KeyColumn("EntityId")
.Element("String");
HasMany(x => x.EnumCollection)
.Table("Entity_Enum")
.KeyColumn("EntityId")
.Element("Enum");
HasMany(x => x.DateTimeCollection)
.Table("Entity_DateTime")
.KeyColumn("EntityId")
.Element("DateTime");
}
}
The result of this mapping will produce four (4) tables.
Table Entity with one column Id of int.
Table Entity_String with two columns - EntityId : int, String : varchar, and a foreign key EntityId to Entity table Id column.
... likewise, except column is of int type.
... likewise, except column is of datetime type.
HasMany(x => x.Items)
.Table("tbl")
.KeyColumn("fk")
.Element("eCol")
.AsBag()