Please believe me when I say I understand this isn't the "right" way to do this. File this under large-complex-legacy-system questions.
We have tables describing OCR'd data of documents. Each document type has its own table. Each table is generated based on the types of fields it expects. So have a schema which looks a bit like this:
Table: DocumentTypes
Field: Id
Field: Prefix
Table: DocumentFields
Field: Id
Field: DocId
Field: Name
And we would generate tables like:
Table: Type1_Data_Unit1000
Field: Id
Field: DocId
Field: DocField_A_Data
Field: DocField_A_Info1
Field: DocField_A_Info2
Field: DocField_Z_Data
Field: DocField_Z_Info1
Field: DocField_Z_Info2
NHibernate works well for all of our other data because the schema is more static.
My question: Is it at all possible to configure nhibernate to load one of the DataTables and bind the series of fields into a collection? If it is, at what interfaces should I start looking?
My idea is to have a class which is something like:
class FormData
{
public virtual int Id {get;set;}
public virtual int DocId {get;set;}
public virtual int Id {get;set;}
public virtual IList(Of FormFieldData) {get;private set;}
}
class FormFieldData
{
public virtual int Id {get;set;}
public virtual string Value {get;set;}
public virtual int Info1 {get;set;}
public virtual int Info2 {get;set;}
}
I've looked at "IInterceptor" a little bit and think that's where I should look first. But before investing days in it, I wanted to run it by the SO crowd.
Thanks!
If the schema isn't static, then it'll be hard for nhibernate to even create the query - and intercepters can't help you there as far as I'm concerned. I'd go with pure CreateSQLQuery and manipulating the returned values manually afterwards.
Related
First off, I am not a DBA, I am a C# developer. I am working on a pretty complex web application and I want to start with a solid database. So here is my issue:
I am trying to create a Settings table that will hold settings for multiple objects. ie: User settings, Season settings, League settings, Team settings.
Instead of creating a table for each of those I would like to keep everything in the Settings table but I can't figure out how to create the foreign key.
My approach is to have three columns in the Settings table: TableName PrimaryKey ID. These three columns would create a composite key that would reference the appropriate object. Is my approach considered bad practice or am I on the right track? Is there a way I can do this that will still work with entity framework?
Here is my way of handling this.
In this diagram, ConfigId is primary key which is used as an FK in Person table as EyeColor, BloodType, PersonType and many more. These columns also hold a Check constraint to ensure no value apart from eye color or corresponding column get stored based on ConfigType. However this comes at a cost of broader datatype which may be smallint or int instead of tinyint.
Only difference is, I am calling these settings as ConfigType in your case {User, Team, Season etc.} and any extension can be done by inheritance concept by creating another table using ConfigId as FK in child table.
If you use inheritance, by default EF will use one table for the entire hierarchy. This is known as Table Per Hierarchy or TPH
public abstract class Setting
{
public string Key { get; set; }
public string Value { get; set; }
}
public class UserSetting : Setting
{
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class SeasonSetting : Setting
{
public int SeasonId { get; set; }
public virtual Season Season { get; set; }
}
I have pre-existing tables, using a kind of open schema. I have an Item table, and various entities are classified as Items, and then have properties stored in Item property tables. A single entity type may have fields stored in multiple tables. We expose entities with views. So, most entities correspond to a view, and then when we insert/update we have to systematically update the tables or use stored procedures.
I'm trying to determine if NHibernate will gain us anything over our custom-built repositories (which follow a factory pattern). Right now, I'm seeing great difficulty in getting NHibernate to deal with this kind of database schema. The way I see it, we'd either have to completely refactor our database to follow NHibernate's conventions, or completely refactor or entities somehow.
I'm not seeing much in the documentation about how to do this, except for the very simplest of examples that involve databases that more or less follow NHibernate's conventions.
Here's a representative database diagram. We have Episode as an entity that pulls info from Item, IP_Episode, IP_EpisodeBroadcastInfo, IP_Show, etc. to build all the fields that it needs.
You mention conventions. That is a Fluent NHibernate concept, and yes, what you are doing is not exactly in line with Fluent NHibernate's existing conventions. However, it is well within NHibernate's capabilities. NHibernate excels at being able to be mapped to all sorts of different database schemas. Don't feel constrained to the way Fluent NHibernate wants you to go. I'm not saying don't use Fluent NHibernate. If you are consistent and reasonable in your database schema, you can write your own conventions to match.
To illustate NHibernate's flexibility, let's assume we have a table structure similar to this:
create table Episode (
Id int not null primary key,
NumberInSeries int null
);
create table Show (
Episode_id int not null primary key,
Title nvarchar(100) not null,
foreign key (Episode_id) references Episode (Id)
);
create table Broadcast (
Episode_id int not null primary key,
InitialAirDate datetime not null,
foreign key (Episode_id) references Episode (Id)
);
One row in Episode corresponds to zero or one rows in Show and zero or one rows in Broadcast. You could model this type of relationship several different ways in .NET. Here are the various options available to you via NHibernate:
1. Inheritance
public class Episode
{
public virtual int Id { get; set; }
public virtual int? NumberInSeries { get; set; }
}
public class Show : Episode
{
public virtual string Title { get; set; }
}
public class Broadcast : Episode
{
public virtual DateTime InitialAirDate { get; set; }
}
Use this when you want to model a relationship that does not change. If an Episode is a Show, it is always a Show. Also, this modeling would imply that an Episode cannot be both a Show and a Broadcast. I don't believe this is what you want, but you may find it useful elsewhere in your model.
For more info, see...
Official documentation on inheritance mapping
Ayende's blog post on inheritance mapping
2. one-to-one
public class Episode
{
public virtual int Id { get; set; }
public virtual int? NumberInSeries { get; set; }
public virtual Show Show { get; set; }
public virtual Broadcast Broadcast { get; set; }
}
public class Show
{
public virtual Episode Episode { get; set; }
public virtual string Title { get; set; }
}
public class Broadcast
{
public virtual Episode Episode { get; set; }
public virtual DateTime InitialAirDate { get; set; }
}
This gives you more control over which tables actually contain a row associated with a given Episode, because you can set episode.Broadcast = null for example. It's also fine to have both Show and Broadcast information for a given Episode.
For more info, see...
Official documentation on one-to-one
Ayende's blog post on one-to-one
3. join
public class Episode
{
// These properties come from the Episode table...
public virtual int Id { get; set; }
public virtual int? NumberInSeries { get; set; }
// This one comes from the Show table.
public virtual string Title { get; set; }
// This one comes from the Broadcast table.
public virtual DateTime InitialAirDate { get; set; }
}
This is a nice and simple way to represent the data, but you do not get control over whether on not rows are inserted into the Show and Broadcast tables or not.
For more info, see...
Official documentation on join
Ayende's blog post on join
Since you said, "A single entity type may have fields stored in multiple tables", it sounds to me like join should be able to handle the way you currently have things modeled.
I have a couple of classes that look like this:
public class Client
{
public int Id {get;set;}
public string Name {get;set;}
}
public class User
{
public int Id {get;set;}
public string Email {get;set;}
public Client Client {get;set;}
}
I'm using ConventionModelMapper and SchemaUpdate from NHibernate 3.2 to generate the schema in my SQL Server database and I want the Client property of the User class to be mapped to a ClientId column with foreign key. My convention code looks like this:
mapper.AfterMapManyToOne += (inspector, member, map) =>
{
map.Column(member.LocalMember.Name + "Id");
// ...
};
This works, in that I get a column ClientId that is mapped as a foreign key, but I also end up with a Client column that is also mapped as a foreign key. It seems that NHibernate is treating the Client property as both a standard Property (and thus generating the Client column for it), and also a ManyToOne property (resulting in the additional ClientId column). How can I prevent the Client column from being generated
I have just copied your EXACT code and, after making the properties virtual, the behavior is the expected (there's a single column, ClientId)
Is there any way to set-up a symmetric self-join relationship mapping in NHibernate? Suppose we have two tables:
Users
id
Relations
id
user1
user2
relation_type
The User and Relation classes should look like this:
class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<Relation> Relations { get; set; }
}
class Relation
{
public virtual int Id { get; set; }
public virtual User User1 { get; set; }
public virtual User User2 { get; set; }
// Let's leave the RealationType as string for the sake of brevity
public virtual string RelationType { get; set; }
}
I do NOT want the relations table to have two rows for the same relation. But the relation MUST be symmetric, which means if there's a relation between two users, A and B, the Relations collection of the user A must contain a relation with user B and the relations of user B must contain a relation to A.
It sounds almost like a challenge. But, can someone solve this? Please, if you can, post the xml mapping. I'm not using Fluent.
You can use Key-Many-To-One mapping and remove the Id field from the relation entity. Also you better use inheritance for different relation types.
I doubt it. If you think about the manual SQL query you'd need to write to pull a User & all his Relations out in an outer join query, you can see why NHibernate would struggle to generate something like this. Updates would be an even bigger headache - how do you decide which ids go in which field for a new Relation?
If you're stuck on this model, all I can suggest as a workaround is to map two private collections and implement a Union()ed read-only public collection. Implement update/remove methods that locate & modify the appropriate relation, and a round-robin Add() method. You won't have any NHibernate query support for queries on this collection.
Your other option is to change your data model so that User has a many-to-many relationship to Relation (eg a UserRelation table), rely on application code to enforce a 'two users per relation' rule, and add convenience methods like IList<User> GetRelations(RelationType)
I have a Class which looks something like this:
public class User {
public virtual int ID;
public virtual string Name;
public virtual IList<int> userRights;
}
I want to make a UserMap : ClassMap<User>
Mapping the name is no problem however i cant seem to figure out how to map the userRights.
Table looks like
UserTable
User_id int
User_Name nvarchar
User_group int
UserRights
User_group int
RightID int
How would you map this ?
Well if you want a List you need an index. So I would recommend just making it an ICollection unless the ordering is significant.
The mapping should look something like:
HasMany(x=> x.userRights).Element("RightID").AsBag();
However, upon looking at your tables, I noticed something odd. You're trying to use a one-to-many without having the primary key in the User_Rights table. If you had User_Id in UserRights the above should work.
Otherwise it looks like there's a UserGroup, which should be modeled by your classes.