Fluent NHibernate table per class hierarchy multiple tables mapping problem - nhibernate

I've got a problem with fluent nhibernate table per class hierarchy mapping. I've got 2 domain objects, container (baseclass) and album (subclass). Album only contains a constructor. Container dervies from EntityWithTypedId from Sharp Architect. EntityWithTypedId provides the key of type Guid (the name is ContainerId).
public class Container : EntityWithTypedId<Guid>
{
public Container()
{
}
protected Container(Guid userId)
: this()
{
UserId = userId;
}
public virtual int Type { get; set; }
public virtual Guid UserId { get; set; }
}
public class Album : Container
{
public Album()
: base()
{
Type = (int)ContainerType.Album;
}
public Album(Guid userId)
: base(userId)
{
Type = (int)ContainerType.Album;
}
}
I want all the domain objects to be saved in a single table called "Containers". I've got a mapping file for Container:
public class ContainerMap : IAutoMappingOverride<Container>
{
public void Override(AutoMap<Container> mapping)
{
mapping.DiscriminateSubClassesOnColumn<int>("Type");
}
}
NHibernate assumes 2 tables are used. The table "Containers" is mapped as expected, but NHibernate assumes theres another table "Album" only containing an Id called "Container" which is equal to ContainerId in table "Containers". How can i change the mapping so the table "Album" isn't needed?
If i provide a mapping class for Album i get a mapping error even if the album mapping is empty:
FluentNHibernate.Cfg.FluentConfigurationException : An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.
Database was not configured through Database method.
----> System.ArgumentException : Object of type 'FluentNHibernate.AutoMap.AutoMap1[Core.Album]' cannot be converted to type 'FluentNHibernate.AutoMap.AutoMap1[Core.Container]'.
Thanks!
/Marcus

Don't handle Type as a property, its handled automatically.

Related

What is the correct way to find and set the primary key generically with EF Core

I am trying to access the primary key from a generic parameter on my entity class which is what all of my DB object inhert from.
The following code works. But is it bad practise to access the DBcontext from within the Entity class? If so, how else can I do this? Is there something like IObjectContextAdapter from EF6?
public abstract class Entity
{
private DBContext _context = new DBContext(null);
[NotMapped]
public int ID
{
get
{
return _context.PrimaryKeyValueInt(this);
}
set
{
_context.PrimaryKeyProperty(this).PropertyInfo.SetValue(this, value);
}
}
}
So, I assume you have a bunch of entity classes with various names for their primary keys, like DoodadId, WidgetId, etc. and want them all to extend a base class with a single property to access the primary key? If so, I've done something like that with Attributes and Reflection. Create an Attribute to use to identify Primary Key properties:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class EntityIdAttribute : Attribute
{
}
Then create the base class and add the property to it. In my case I use Guid for primary keys:
public abstract class Entity
{
[NotMapped]
public virtual Guid EntityId
{
get
{
return (Guid)EntityIdProperty.GetValue(this);
}
set
{
EntityIdProperty.SetValue(this, value);
}
}
private PropertyInfo EntityIdProperty => this.GetType().GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(EntityIdAttribute)))
.FirstOrDefault();
}
Then in your entity classes, extend Entity and decorate the primary key property with [EntityId]:
public class Widget : Entity
{
[EntityId]
public Guid WidgetId { get; set; }
// ......
}

Entity Framework Core: using navigation properties without foreign key

I have following object model:
public class SharingRelation:BaseEntity
{
public Guid? Code { get; set; }
public string Value { get; set; }
}
public class SecondLevelShareEntity : BaseEntity
{
public string Name { get; set; }
public Guid? SharingCode { get; set; }
public List<SharingRelation> SharingRelations { get; set; }
}
In my database (it may be poor db design but I need to answer this question for research), SharingRelation is some sort of dependent entity of SecondLevelShareEntity on Code == SharingCode values. I can have two entities of type SecondLevelShareEntity with same SharingCode value. So, for each of them I need to get all related SharingRelation objects depending on Code and SharingCode values. I can do it using SQL and join on this columns. But how can I do it using EF Core and navigation properties (I want to get all dependent entities using Include() for example)? When I configure my entities like this
public class SharingRelationEntityTypeConfiguration : BaseEntityTypeConfiguration<SharingRelation>
{
public override void Configure(EntityTypeBuilder<SharingRelation> builder)
{
base.Configure(builder);
builder.HasOne<SecondLevelShareEntity>().WithMany(x => x.SharingRelations).HasForeignKey(x => x.Code)
.HasPrincipalKey(x => x.SharingCode);
}
}
EF Core creates foreign key and marks it unique. I am obviously getting an error that that is impossible to have several SecondLevelShareEntity with the same SharingCode
System.InvalidOperationException : The instance of entity type 'SecondLevelShareEntity' cannot be tracked because another instance with the key value '{SharingCode: 8a4da9b3-4b8e-4c91-b0e3-e9135adb9c66}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
How can I avoid creation of foreign key, but keep using navigation properties (as far, as I see normal queries with navigations generate simple JOIN statements)
UPDATED I can provide real data in database. SecondLevelShareEntity table looks like this:
_id Name SharingCode
----------------------------------------------------------------------
1 "firstSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSecondLevelEnt" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
And SharingRelation table looks like this:
_id Value Code
----------------------------------------------------------------------
1 "firstSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"
2 "secondSharingRelation" "efcb1c96-0ef1-4bb3-a952-4a6511ab448b"

BlToolkit insert data failure in BaseRepository class

I'm using BaseRepository in asp .Net MVC project. Edit operation works but in Add operation, I should make a trick to make it work. In detail, my base repository and BaseEntity classes:
public class BaseRepository<TEntity, T> : IRepository<TEntity, T> where TEntity : BaseEntity<T>
{
private DbManager _context;
private Table<TEntity> Table
{
get { return _context.GetTable<TEntity>(); }
}
public BaseRepository(DbManager context)
{
_context = context;
}
//...
public TEntity Add(TEntity entity)
{
//...
return entity;
}
public TEntity Edit(TEntity entity)
{
_context.Update(entity);
return entity;
}
//...
}
public class BaseEntity<T>
{
[PrimaryKey]
public T Id { get; set; }
}
I tried three ways for Add operation to make it work. First two ways gave errors.
First way(Doesn't work):
public TEntity Add(TEntity entity)
{
_context.Insert(entity);
return entity;
}
Error Message:
Cannot insert explicit value for identity column in table '...' when IDENTITY_INSERT is set to OFF.
--
Second way(Doesn't work):
public TEntity Add(TEntity entity)
{
Table.Insert(() => entity);
return entity;
}
Error Message:
Operation is not valid due to the current state of the object.
--
Third way(Working):
public TEntity Add(TEntity entity)
{
var l = new List<TEntity> { entity };
_context.InsertBatch(l);
return entity;
}
--
Edit operation works without error, but for Add operation I need to make some trick. What is the problem with normal Add operation and, is there a way to make it work?
I tried advice of #Mladen Macanović and I added Identity attribute to primary key in base BaseEntity class, then errors shown above gone for entities having int type primary key.
Errors shown above gone for entities having int type of primary key:
public class BaseEntity<T>
{
[PrimaryKey Identity]
public T Id { get; set; }
}
But, this is not a full solution because, some of my entities have primary key in type of Guid, so adding Identity attribute to them gives another error.
InsertBatch method works without using Identity attribute. So, you can add data without using Identity attribute in BaseEntity class for Identity column. What is the difference of insertbatch method? How can I resolve errors shown above without using InsertBatch method?
The problem is with your database table. That is why you're getting the IDENTITY_INSERT error. Go to the SQL Server Management Studio, right click on the table, Design, and for the primary key column set the property Identity Specification -> (Is identity) to Yes.

How to map an interface in nhibernate?

I'm using two class NiceCustomer & RoughCustomer which implment the interface ICustomer.
The ICustomer has four properties. They are:
Property Id() As Integer
Property Name() As String
Property IsNiceCustomer() As Boolean
ReadOnly Property AddressFullText() As String
I don't know how to map the interface ICustomer, to the database.
I get an error like this in the inner exception.
An association refers to an unmapped class: ICustomer
I'm using Fluent and NHibernate.
You can map directly to interfaces in NHibernate, by plugging in an EmptyInterceptor during the configuration stage. The job of this interceptor would be to provide implementations to the interfaces you are defining in your mapping files.
public class ProxyInterceptor : EmptyInterceptor
{
public ProxyInterceptor(ITypeHandler typeHandler) {
// TypeHandler is a custom class that defines all Interface/Poco relationships
// Should be written to match your system
}
// Swaps Interfaces for Implementations
public override object Instantiate(string clazz, EntityMode entityMode, object id)
{
var handler = TypeHandler.GetByInterface(clazz);
if (handler == null || !handler.Interface.IsInterface) return base.Instantiate(clazz, entityMode, id);
var poco = handler.Poco;
if (poco == null) return base.Instantiate(clazz, entityMode, id);
// Return Poco for Interface
var instance = FormatterServices.GetUninitializedObject(poco);
SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);
return instance;
}
}
After this, all relationships and mappings can be defined as interfaces.
public Parent : IParent {
public int ID { get; set; }
public string Name { get; set; }
public IChild Child { get; set; }
}
public Child : IChild {
public int ID { get; set; }
public string Name { get; set; }
}
public class ParentMap : ClassMap<IParent>
{
public ParentMap()
{
Id(x => x.ID).GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Name)
}
}
...
This type of technique is great if you want to achieve true decoupling of your ORM, placing all configuration/mappings in a seperate project and only referencing interfaces. Your domain layer is then not being polluted with ORM, and you can then replace it at a later stage if you need to.
how are you querying? If you're using HQL you need to import the interface's namespace with an HBM file with this line:
<import class="name.space.ICustomer, Customers" />
If you're using Criteria you should just be able to query for ICustomer and it'll return both customer types.
If you're mapping a class that has a customer on it either through a HasMany, HasManyToMany or References then you need to use the generic form:
References<NiceCustomer>(f=>f.Customer)
If you want it to cope with either, you'll need to make them subclasses
Subclassmap<NiceCustomer>
In which case I think you'll need the base class Customer and use that for the generic type parameter in the outer class:
References<Customer>(f=>f.Customer)
Regardless, you shouldn't change your domain model to cope with this, it should still have an ICustomer on the outer class.
I'm not sure if the 1.0RTM has the Generic form working for References but a quick scan of the changes should show the change, which I think is a two line addition.
It is not possible to map an interface in nhibernate. If your goal is to be able to query using a common type to retrieve both types of customers you can use a polymorphic query. Simply have both your classes implement the interface and map the classes normally. See this reference:
https://www.hibernate.org/hib_docs/nhibernate/html/queryhql.html (section 11.6)

Using Fluent NHibernate Auto Mapping to map IDs of type object from base Entity class

In the project I'm working on now, we have base Entity class that looks like this:
public abstract class Entity<T> where T : Entity<T>
{
public virtual object Id { get; protected set }
// Equals, GetHashCode overrides, etc...
}
Most classes inheriting from Entity should map Id to int column in SQL Server database, but at least one will need to map to long (bigint).
Is it possible to create FluentNH Auto Mapping convention to map those object Ids to int by default? Then we could use another convention or IAutoMappingOverride to handle long Ids.
Thanks!
To answer my own question... It's possible.
You can define convention like this:
internal class PrimaryKeyConvention : IIdConvention
{
public bool Accept(IIdentityPart id)
{
return true;
}
public void Apply(IIdentityPart id)
{
if (<ID should be long>)
id.SetAttribute("type", "Int64");
else
id.SetAttribute("type", "Int32");
}
}