How to map a view without an identity column in NHibernate? - nhibernate

I have a view that I am going to only read from (no writes). This view does not have any unique key (not event composite).
So how can I map this view in NHibernate without touching the view? I do not want to add a new column to the view to generate a unique identity for me. Is there a way to map this view and generate the identity column on the NHibernate side?
I can generate a GUID in my entity class like:
public class MyViewClass
{
private Guid _id = new Guid();
public virtual Guid Id { get { return _id; } set { _id = value; } }
}
But how can I make the mapping work? The following code does not work:
public class MyViewClass: ClassMapping<MyViewClass>
{
public MyViewClass()
{
Mutable(false);
Id(x => x.Id, m => m.Generator(Generators.Guid));
}
}
It expects to have the Id column in view and throws:
System.Data.SqlClient.SqlException: Invalid column name 'Id'.
BTW, I am using NHibernate 3.2 and mapping by code.

Update: to use it in LINQ map all columns as CompositeId and Mutable(false) then override Equals and GetHashCode with default implementation.
public class MyViewClass
{
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
original answer:
i wouldnt map it at all if you dont want to insert/update it
public class MyViewClass
{
public virtual string Prop1 { get; set; }
public virtual int Prop2 { get; set; }
}
var viewObjects = session.CreateSQLQuery("SELECT col1 as Prop1, col2 as Prop2 FROM MyView")
.SetResultTransformer(Transformers.AliasToBean<MyViewClass>())
.List<MyViewClass>();

Related

One-to-one relationship in NHibernate using composite key

I'm trying to figure out the proper way to model a one-to-one (or one-to-zero) relationship in NHibernate, or indeed to learn categorically whether such a thing can be done.
Currently I have two models, Document and ScriptDocument whereby there should be a bidirectional relationship between the two, defined by a composite primary key made up of two properties that are shared/duplicated across both tables. A Document may have zero or one associated ScriptDocument and every ScriptDocument will have an associated Document. They both have a shared primary key made up of two properties: a string ("key") and int ("userref").
Currently I've set up my models and mappings as follows:
public class Document
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public virtual ScriptDocument ScriptDocument { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is Document document &&
Key == document.Key &&
UserRef == document.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
}
public class DocumentMap : ClassMapping<Document>
{
public DocumentMap()
{
Schema("Documents");
Table("Documents");
ComposedId(m =>
{
m.Property(x => x.Key);
m.Property(x => x.UserRef, m => m.Column("User_Ref"));
// the PK fields are named slightly differently across the two tables. Same data types though and same names in the models.
});
OneToOne(x => x.ScriptDocument, m => {
m.Cascade(Cascade.All);
m.Constrained(false);
});
// ... other property mappings ...
}
}
public class ScriptDocument
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public virtual Document Document { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is ScriptDocument sd &&
Key == sd.Key &&
UserRef == sd.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
}
public class ScriptDocumentMap : ClassMapping<ScriptDocument>
{
public ScriptDocumentMap()
{
Table("Script_Document");
ComposedId(m =>
{
m.Property(x => x.Key, m => m.Column("DocKey"));
m.Property(x => x.UserRef);
});
OneToOne(x => x.Document, m => m.Constrained(true));
// ... other property mappings ...
}
}
At this point, NHibernate seems happy with these models and mapping definitions, but the problem is that the relationships seem to be effectively ignored. When loading one or more Document entities, they all have a null ScriptDocument property and the same is true of the Document property on any ScriptDocument entities.
As far as I can tell, NHibernate isn't even attempting to fill those properties in any cases. I therefore assume one of two things is happening:
I've done something wrong (probably in the mappings). I'm sort of hoping there's just one or two little things I've missed, but I can't for the life of me work out what that might be.
This can't actually be done. My understanding is that this approach should be just fine if we had a single shared primary key but I'm not sure whether the shared composite key is something we can do. I can't find any comparable examples.
Note about this approach: you definitely don't need to tell me how unorthodox this is 😅 I'm painfully aware. But I'm working within the constraints of pre-existing systems. Unless this absolutely, categorically, isn't possible, this is the approach that I'd like to continue with at this point.
So the key to solving this seemed to be to use a component composite ID.
I added the following class to define the composite primary key for both tables:
[Serializable]
public class DocumentIdentifyingKey
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public override bool Equals(object obj)
{
return obj is DocumentIdentifyingKey key &&
Key == key.Key &&
UserRef == key.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
public override string ToString()
{
return $"{UserRef}/{Key}";
}
}
And was then able to update the entity model classes and associated mappings as follows, using ComponentAsId defining the actual database fields for the identities for each of the two classes/tables:
public class Document
{
public virtual DocumentIdentifyingKey Identity { get; set; }
public virtual ScriptDocument ScriptDocument { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is Document document &&
Identity == document.Identity;
}
public override int GetHashCode()
{
return Identity.GetHashCode();
}
}
public class DocumentMap : ClassMapping<Document>
{
public DocumentMap()
{
Schema("Documents");
Table("Documents");
ComponentAsId(x => x.Identity, m => {
m.Property(i => i.Key);
m.Property(i => i.UserRef, m => m.Column("User_Ref"));
});
OneToOne(x => x.ScriptMetadata, m => {
m.Cascade(Cascade.All);
m.Constrained(false);
m.Fetch(FetchKind.Join);
m.Lazy(LazyRelation.NoLazy);
});
// ... other property mappings ...
}
}
public class ScriptMetadata
{
public virtual DocumentIdentifyingKey Identity { get; set; }
public virtual Document Document { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is ScriptMetadata sd &&
Identity == sd.Identity;
}
public override int GetHashCode()
{
return Identity.GetHashCode();
}
}
public class ScriptDocumentMap : ClassMapping<ScriptMetadata>
{
public ScriptDocumentMap()
{
Table("Script_Document");
ComponentAsId(x => x.Identity, m =>
{
m.Property(i => i.Key, m => m.Column("DocKey"));
m.Property(i => i.UserRef);
});
OneToOne(x => x.Document, m => {
m.Constrained(true);
m.Fetch(FetchKind.Join);
m.Lazy(LazyRelation.NoLazy);
});
// ... other property mappings ...
}
}
I'm not entirely sure why this worked but having the identity of the document expressed as an instance of an object rather than just the combination of the two fields on each class seemed to be the key incantation which allowed NHibernate to understand what I was getting at.
Note: in this solution, I've added Fetch and Lazy calls to the two OneToOne relationships. These are not specifically part of this solution but instead were added to better instruct NHibernate what load behaviour would be preferred.

Fluent nHibernate: How to set up id convention so that override works on a table with a different id

Weird case with a legacy database:
Most tables have the same named field for the primary key, but I have one table that has a different primary key but also has that named field. When I override the automap for this table it uses the convention not the override unless I explicitly override the mapping for the name field.
public class IdConvention : IIdConvention, IIdConventionAcceptance,
{
public void Accept(IAcceptanceCriteria<IIdentityInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(string));
}
public void Apply(IIdentityInstance instance)
{
if (instance.Name == "StandardId")
instance.Column("FooBarThingyId");
}
}
public class WierdCase
{
virtual int CustomId {get;set;}
virtual int StandardId {get;set;}
}
public class WierdCaseOverride : IAutoMappingOverride<WierdCase>
{
public void Override(AutoMapping<WierdCase> mapping)
{
mapping.Id(x => x.CustomId).GeneratedBy.Identity();
mapping.Map(x => x.StandardId , "FooBarThingyId");
}
}
public class CustomConfiguration : DefaultAutomappingConfiguration
{
public override bool IsId(Member member)
{
if (member.DeclaringType.GetProperty("StandardId") != null)
return member.Name == "StandardId";
return base.IsId(member);
}
}
Without the mapping.Map() line, this uses StandardId as the key, not CustomId. I would expect calling mapping.Id() to override the convention, but it does not seem to.
What am I missing here? As I'd rather fix the convention than have to explicitly override it for the exceptions.

My custom ForeignKeyConvention is resulting in two foreign keys being created instead of one

I am trying to create my own foreign key convention that will name the FK in "FK_SourceTable_TargetTable" format.
However, when I run it I end up with two foreign keys instead of one.
My custom foreign key convention looks like this:
public class OurForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName(Member property, Type type)
{
if (property == null)
return string.Format("FK_{0}Id", type.Name); // many-to-many, one-to-many, join
if (property.Name == type.Name)
return string.Format("FK_{0}_{1}", property.DeclaringType.Name, type.Name);
return string.Format("FK_{0}_{1}_{2}", property.DeclaringType.Name, property.Name, type.Name);
}
}
My code to exercise it:
[TestMethod]
public void ShouldBeAbleToBuildSchemaWithOurConventions()
{
var configuration = new Configuration();
configuration.Configure();
Fluently
.Configure(configuration)
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<Widget>()
.Conventions.Add<OurForeignKeyConvention>()
)
.BuildSessionFactory();
new SchemaExport(configuration).Create(false, true);
}
My classes and mappings:
public class Widget
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
public virtual WidgetType Type { get; set; }
public virtual ISet<WidgetFeature> Features { get; set; }
}
public class WidgetFeature
{
public virtual int Id { get; set; }
public virtual Widget Widget { get; set; }
public virtual string FeatureDescription { get; set; }
}
public class WidgetMap : ClassMap<Widget>
{
public WidgetMap()
{
Id(w => w.Id);
Map(w => w.Description);
HasMany(w => w.Features).Cascade.AllDeleteOrphan().Inverse();
}
}
public class WidgetFeatureMap : ClassMap<WidgetFeature>
{
public WidgetFeatureMap()
{
Id(w => w.Id);
Map(w => w.FeatureDescription);
References(w => w.Widget);
}
}
The end result is two foreign keys, one called what I want - FK_WidgetFeature_Widget - and another one called FK_WidgetId.
If I change OurForeignKeyConvention to always return the same name regardless of whether the "property" parameter is null then I correctly get a single FK - but I then cannot get the "SourceTable" part of my FK name.
Can anyone explain what I am doing wrong here? Why is GetKeyName called twice? And why does one of the calls not provide a value for the "property" parameter?
Doh. ForeignKeyConvention provides the name for the FK column. What I should have been using is the IHasManyConvention, which can be used to name the FK constraint itself.
public class OurForeignKeyConstraintNamingConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.ForeignKey(string.Format("FK_{0}_{1}", instance.Relationship.Class.Name, instance.EntityType.Name));
}
}

how to set key column im many to many relation with fluent nhibernate

I am using flunet nhibernate and in a many to many relation I need to have key column for the table between these two entities
HasManyToMany(p => p.Signers)
//.Cascade.AllDeleteOrphan()
.Table("PersonnelDocumentSigner")
.Schema("personnel");
public partial class PersonnelDocument
{
private IList<Position> _signers;
virtual public IList<Position> Signers
{
get
{
if (_signers == null)
_signers = new List<Position>();
return _signers;
}
set
{
_signers = value;
}
}
}
the created table just consist of these two columns:PersonnelDocumentId,PositionId
but I need a column Id for this connector table "PersonnelDocumentSigner"
how exactly I can assign it?
link-tables like your PersonnelDocumentSigner normally dont need an Id column because PersonnelDocumentId and PositionId together are the Id/Primary key. If you still want to have additional Data in the linking Table you should Create a new Entity.
class PersonnelDocumentSigner
{
public virtual PersonnelDocument Document { get; set; }
public virtual Position Signer { get; set; }
public virtual int Id { get; set; }
// additional Properties
}
class PersonnelDocumentSignerMap : ClassMap<PersonnelDocumentSigner>
{
Table("PersonnelDocumentSigner");
CompositeId()
.KeyReference(pds => pds.Document)
.KeyReference(pds => pds.Signer);
Map(pds => pds.Id);
// additional Mapps
}

fluent nhibernate automap inferring incorrect key

I am having trouble using CreateCriteria to add an outer join to a criteria query while using Fluent NHibernate with automapping.
Here are my entities -
public class Table1 : Entity
{
virtual public int tb1_id { get; set; }
virtual public DateTime tb1_date_filed { get; set; }
.
.
.
virtual public IList<Table2> table2 { get; set; }
}
public class Table2: Entity
{
public virtual int tb2_id { get; set; }
public virtual int tb2_seqno { get; set; }
.
.
.
public virtual Table2 table2 { get; set; }
}
I try to use the following to add an outer join to my criteria query -
CreateCriteria("Table2", NHibernate.SqlCommand.JoinType.LeftOuterJoin);
But I am getting an error -
{"EIX000: (-217) Column (tbl1_id) not found in any table in the query (or SLV is undefined)."}
So it seems that it is trying to automatically set the id of the second table, but doesn't know what to set it to. Is there a way that I can specifically set the id? Here is my Session -
var persistenceModel = AutoMap.AssemblyOf<Table1>()
.Override<Table1>(c => {
c.Table("case");
c.Id(x => x.id).Column("tbl1_id");
})
.Where(t => t.Namespace == "MyProject.Data.Entities")
.IgnoreBase<Entity>();
Hope that makes some sense. Thanks for any thoughts.
You seem to have answered your own question so I'm just going to spout some recommendations...
One of the nice things about fluent nhibernate is that it follows conventions to automatically create mappings. Your entities seem to be very coupled to the names of your database tables.
In order to map to a different database convention while keeping idealistic names for entities and columns you can use some custom conventions:
public class CrazyLongBeardedDBATableNamingConvention
: IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.Table("tbl_" + instance.EntityType.Name.ToLower());
}
}
public class CrazyLongBeardedDBAPrimaryKeyNamingConvention
: IIdConvention
{
public void Apply(IIdentityInstance instance)
{
string tableShort = TableNameAbbreviator.Abbreviate(instance.EntityType.Name);
instance.Column(tableShort + "_id");
}
}
class CrazyLongBeardedDBAColumnNamingConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
string name = Regex.Replace(
instance.Name,
"([A-Z])",
"_$1").ToLower();
var tableShort = TableNameAbbreviator.Abbreviate(instance.EntityType.Name);
instance.Column(tableShort + name);
}
}
TableNameAbbreviator is a class that would know how to abbreviate your table names.
These would map from:
public class Table1 : Entity
{
virtual public int Id { get; set; }
virtual public DateTime DateFiled { get; set; }
}
To a table like:
CREATE TABLE tbl_table1 {
tbl1_id INT PRIMARY KEY
tbl1_date_filed datetime
}
I added a HasMany option to the override for my first table to define the relationship to my second table. I then added an override for my second table which defines the id column for that table.
Thank