I am using NHibernate with Fluent NHibernate as the ORM of my application. I frequently use export schema to generate the database, and then compare with an existing database. Due to the fact that NHibernate generates the name of the constraints I get a lot of noise when comparing. All tables are marked as changed each time.
I'd like to stabilize the constraint names using some convention such as FK_TableName_ColumnName.
I cannot get this to work however.
I've got this so far:
public class ForeignConstraintNamingConvention : IReferenceConvention, IHasManyConvention, IHasManyToManyConvention
{
public void Apply(IManyToOneInstance instance)
{
instance.ForeignKey(GetForeignConstraintName(instance.Name, instance.Columns.First().Name));
}
public void Apply(IOneToManyCollectionInstance instance)
{
if (instance.OtherSide != null)
{
instance.OtherSide.ForeignKey(GetForeignConstraintName(instance.TableName, instance.OtherSide.Columns.First().Name));
}
}
private static string GetForeignConstraintName(string tableName, string columnName)
{
return string.Format("FK_{0}_{1}", tableName, columnName);
}
public void Apply(IManyToManyCollectionInstance instance)
{
if (instance.Relationship != null)
{
instance.Relationship.ForeignKey(string.Format("FK_{0}_{1}", instance.TableName, instance.Relationship.Columns.First().Name));
}
if (instance.OtherSide != null && instance.OtherSide.Relationship != null)
{
instance.OtherSide.Relationship.ForeignKey(GetForeignConstraintName(instance.OtherSide.TableName, instance.OtherSide.Relationship.Columns.First().Name));
}
}
}
But instance.OtherSide is often null, I assume this is because the reference is unidirectional most of the time, and I'm not interested in the other side of the relation, except that I need to use stable key names. Given that the "other side" is null, there is no way for me to set the foreign key constraint name for that key.
Does anyone have any idea how to achieve fully stable constraint names, ideally based on the actual columns / tables involved?
Related
I'm having a problem trying to use linq to sql to create a relationship between my two database entities. I've been following this basic Microsoft guide but without getting it to work.
I'm trying to store a tree-like structure. The ConfigUnit class contains information about a category of items and stores the tree name as well as the root node.
storeItem contains the information for each node in that tree.
[Table(Name = "ConfigUnit")]
public class ConfigUnit
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int cuID;
...
private EntitySet<storeItem> _rootNode;
[Association(Storage = "_rootNode", ThisKey = "cuID", OtherKey = "cuID")]
public EntitySet<storeItem> rootNode
{
get
{
return _rootNode;
}
set
{
_rootNode = value;
}
}
}
And the storeItem class:
[Table(Name = "storeItem")]
public class storeItem
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int siID;
[Column]
public int cuID;
private EntityRef<ConfigUnit> _CU;
[Association(Storage = "_CU", ThisKey = "cuID")]
public ConfigUnit ConfigUnit
{
get { return this._CU.Entity; }
set { this._CU.Entity = value; }
}
...
}
However when I run the code and the database is created using DataContext.CreateDatabase() the relation does not seem to be created. The ConfigUnit table has no relationship column and no relationship values seem to be stored when I query the database afterwards.
What am I missing? I'm running a near-identical relationship as the linked Microsoft article.
Thanks for your time!
EDIT:
The generated SQL code is simply this:
CREATE TABLE [ConfigUnit](
[cuID] Int NOT NULL IDENTITY,
[topID] Int NOT NULL,
CONSTRAINT [PK_ConfigUnit] PRIMARY KEY ([cuID])
)
Ie no relation is generated at all.
The only other related problem I could find was people who were missing primary keys on their entities, which I am not.
EDIT 2:
I tried using a full SQL server instead of a Compact edition one. Still the same result.
I am using Nhibernate and it's mapping-by-code flavour to retrieve a set of records using a list of composite keys. I am using composite keys like this:
public class PersonAccountKey : IKey
{
public virtual string PersonId { get; set; }
public virtual string AccountNo{ get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as PersonKey;
if (t == null)
return false;
if (PersonId == t.PersonId && AccountNo == t.AccountNo)
return true;
return false;
}
public override int GetHashCode()
{
return (PersonId).GetHashCode() + "|" + (AccountNo).GetHashCode();
}
}
With a list of PersonAccountKey objects, I am trying to get NHibernate to send a single query down to database. I would imagine the query would look like this:
Select PersonId, AccountNo, AccountNickName
From PersonAccount
Where (PersonId = '11' and AccountNo = '10001111')
or (PersonId = '22' and AccountNo = '10001150')
I'm not sure how to achieve this? I tried to use Criteria with composite keys, but I don't think it was meant to be used together. I am trying now Linq 2 NHibernate but am not really getting anywhere either.
Ideally, I would like a method that would take a IEnumerable of keys such as Session.Get<T>(IEnumerable<object>), but this doesn't exist from my searching.
Is this possible out of the box with NHibernate?
Cheers.
Unlike scalar Ids, there's no direct support to use use a list of composite keys as a query parameter. This is another example of why composite keys should be avoided.
Here's an easy workaround:
IEnumerable<PersonAccountKey> keys = GetKeys();
var query = session.CreateCriteria<PersonAccount>();
var keyCriterion = Restrictions.Disjunction();
foreach (var key in keys)
keyCriterion.Add(Restrictions.Eq("id", key));
query.Add(keyCriterion);
var result = query.List<PersonAccount>();
There is an article on MSDN about Enumerable.Contains which does what I think you want.
I don't know if NHibernate's linq supports this at all. It may refuse to do it with an exception.
We are facing problem applying many-to-many relationship using fluent nhibernate automapping.
The simplified form of domain model are as follows:
public class Group
{
private readonly IList<Recipient> _recipients = new List<Recipient>();
public virtual IList<Recipient> Recipients
{
get { return _recipients; }
}
}
public class Recipient
{
private readonly IList<Group> _groups = new List<Group>();
public virtual IList<Group> Groups
{
get { return _ groups; }
}
}
As the code above describes that Group and Recipient are having many-to-many relationship.
We are using automapping feature of fluent nhibernate to map our domain model with database. So, we needed to use Convention for automapping.
Following is code we used for many to many convention:-
public class ManyToManyConvention : IHasManyToManyConvention
{
#region IConvention<IManyToManyCollectionInspector,IManyToManyCollectionInstance> Members
public void Apply(FluentNHibernate.Conventions.Instances.IManyToManyCollectionInstance instance)
{
if (instance.OtherSide == null)
{
instance.Table(
string.Format(
"{0}To{1}",
instance.EntityType.Name + "_Id",
instance.ChildType.Name + "_Id"));
}
else
{
instance.Inverse();
}
instance.Cascade.All();
}
#endregion
}
I found this solution here :
http://blog.vuscode.com/malovicn/archive/2009/11/04/fluent-nhibernate-samples-auto-mapping-part-12.aspx#Many%20to%20Many%20convention
But in above code while debugging both time for Recipients-> Groups and for Groups->Recipients instance.OtherSide is coming not null. The assumption was 1st time instance.OtherSide will be not null and second time it will be null as relationship is applied on one side so we will just apply inverse to that.
So it’s creating 2 mapping tables which are same.
It is load to database to have 2 tables of same schema. Even when I try to save our domain model to database using many to many relationship. It’s saving only 1 side i.e it saves Recipients in the Groups , But not saving Groups in Recipients.In database also it is having entry in only one mapping table not in both.
So , the question is Are we doing the right thing? If not then how to do it.
you could take inverse itself as a criteria
public void Apply(IManyToManyCollectionInstance instance)
{
Debug.Assert(instance.OtherSide != null);
// Hack: the cast is nessesary because the compiler tries to take the Method and not the property
if (((IManyToManyCollectionInspector)instance.OtherSide).Inverse)
{
instance.Table(
string.Format(
"{0}To{1}",
instance.EntityType.Name + "_Id",
instance.ChildType.Name + "_Id"));
}
else
{
instance.Inverse();
}
instance.Cascade.All();
}
Let's assume I have a table of Products with columns: Id, Name, Price
and using NHibernate (or ActiveRecord) I map the table to the POCO:
public class Product
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
}
Now if someday a new column named ShipmentPrice (let's assume it's double too)
will be added to the Products table, is there any way I can automatically know that?
For saying automatically I mean adding code to do that or getting an exception?
(I assume I don't have control on the columns of the table or a way to
know of any changes to the table's schema in advance)
You do recall correctly, Mauricio. The following code shows how you can create or update a schema. The update will run when Validate() raises an exception. No exception will be thrown when a field is available in the database but not in the configuration. It is perfectly legal to have extra fields: you don't want them to be deleted, I hope? That could cause tremendous damage...
The following code shows Test, Create, Validate and Update, each step with the proper exception handling. The code is simplified, but it should give you a handle on how to do a validation.
This code helps with Entity-centric (POCO) ORM configurations, where you can add a field to your class and it will automatically be updated in the database. Not with table-centric, where fields are leading.
// executes schema script against database
private static void CreateOrUpdateSchema(Configuration config)
{
// replace this with your test for existence of schema
// (i.e., with SQLite, you can just test for the DB file)
if (!File.Exists(DB_FILE_NAME))
{
try
{
SchemaExport export = new SchemaExport(config);
export.Create(false, true);
}
catch (HibernateException e)
{
// create was not successful
// you problably want to break out your application here
MessageBox.Show(
String.Format("Problem while creating database: {0}", e),
"Problem");
}
}
else
{
// already something: validate
SchemaValidator validator = new SchemaValidator(config);
try
{
validator.Validate();
}
catch (HibernateException)
{
// not valid, try to update
try
{
SchemaUpdate update = new SchemaUpdate(config);
update.Execute(false, true);
}
catch (HibernateException e)
{
// update was not successful
// you problably want to break out your application here
MessageBox.Show(
String.Format("Problem while updating database: {0}", e),
"Problem");
}
}
}
}
-- Abel --
You could use NHibernate's SchemaValidator, but IIRC it only checks that your mapped entities are valid so it doesn't check if there are more columns than mapped properties since that wouldn't really break your app.
I am new to fluent nhibernate and nhibernate. I want to write a fluent nhibernate autopersistence convention to handle creating the many to many mappings for my entities.
This is what I have right now:
using System;
using FluentNHibernate.Conventions;
using FluentNHibernate.Mapping;
namespace Namespace
{
public class HasManyToManyConvention : IHasManyToManyConvention
{
public bool Accept(IManyToManyPart target) {
return true;
}
public void Apply(IManyToManyPart target) {
var parentName = target.EntityType.Name;
var childName = target.ChildType.Name;
const string tableNameFmt = "{0}To{1}";
const string keyColumnFmt = "{0}Fk";
string tableName;
if (parentName.CompareTo(childName) < 0) {
tableName = String.Format(tableNameFmt, parentName, childName);
}
else {
tableName = String.Format(tableNameFmt, childName, parentName);
}
target.WithChildKeyColumn(String.Format(keyColumnFmt, childName));
target.WithParentKeyColumn(String.Format(keyColumnFmt, parentName));
target.WithTableName(tableName);
target.Cascade.All();
}
}
}
It seems to work, but I feel that there is a better way to do this.
Now my questions:
Do you have a better way to do this?
Do you usually want the Cascade behavior here?
Do I need to worry about something besides making sure both sides of this association end up with the same table name?