Fluent NHibernate Relationships that Share Columns - fluent-nhibernate

I have a database that shares columns for relationships. Here is the condensed table structure
Order
----------
OrderId int IDENTITY
OrderLine
----------
OrderId int NOT NULL
OrderLine int NOT NULL
OrderNote
----------
NoteId uniqueidentifier NOT NULL
OrderId int NOT NULL
OrderLineId int NULL
Basically the OrderNote table is shared between Order and OrderLine. If the OrderLineId column is null, the note belongs to the order. If the OrderLineId column is present, the note belongs to the order line. Here is what my classes look like:
class Order {
int OrderId {get; set;}
IList<OrderLine> Lines {get;}
IList<OrderNote> Notes {get;}
}
class OrderLine {
int OrderLineId {get; set;}
Order Order {get; set;}
IList<OrderNote> Notes {get;}
}
class OrderNote {
Order Order {get; set;}
OrderLine Line {get; set;}
Guid NoteId {get; set;}
}
If the OrderNote.Line is null, the note belongs only to the Order. If OrderNote.Line is not null, the note belongs to the order and the line. Here is what my mappings look like:
class OrderClassMap {
Id(x => x.OrderId).Generated.Identity();
HasMany(x => x.Lines).Inverse().KeyColumn("OrderId");
HasMany(x => x.Notes).Inverse().KeyColumn("OrderId");
}
class OrderLineClassMap {
CompositeId().KeyReference(x => x.Order, "OrderId").KeyProperty(x => x.OrderLineId);
HasMany(x => x.Notes).Inverse().KeyColumns.Add("OrderId", "OrderLineId");
}
class OrderNoteClassMap {
Id(x => x.NoteId).Generated.Assigned();
References(x => x.Order).Column("OrderId");
References(x => x.Line).Columns("OrderId", "OrderLineId");
}
When I try to save a new OrderNote, I get an error saying "Invalid index 11 for this SqlParameterCollection with Count=11. How do I correctly model and map this?

It seems you are talking about only 1 note per order and 1 note per orderline? If so, map the note as a column in order and also a note column for the orderline. The will make a big impact on performance having less joins and simple queries - (otherwise you need left outer joins and coalesce statements).
If you can have multiple notes per order and orderline then create seperate ordernote and orderlinenote value objects. Depends on the needs of having more that 1 note per order/orderline. Would lean towards 1 note per entity rather as this is the easiest and offers best performance and a ordernote and orderlinenote are different in terms of the domain design.
class Order {
int OrderId {get; set;}
IList<OrderLine> Lines {get;}
string Note {get;set;}
}
class OrderLine {
int OrderLineId {get; set;}
Order Order {get; set;}
string Note {get;set}
}
or
class Order {
int OrderId {get; set;}
IList<OrderLine> Lines {get;}
IList<OrderNote> Notes {get;}
}
class OrderNote {
int Id {get;set}
Order Order {get; set;}
string Note {get; set;}
}
class OrderLine {
int OrderLineId {get; set;}
Order Order {get; set;}
IList<OrderLineNote> Notes {get;}
}
class OrderLineNote {
int Id {get; set;}
OrderLine OrderLine {get; set;}
string Note {get;set}
}

Related

Entity Framework - Dropped a foreign key column successfully from table but getting an error while accessing the table

I am using a code first method to generate database. My models are as follows -
public class Employee
{
public int Id {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
[ForeignKey("ReferenceTbl1")]
public int Reference1Id {get; set;}
// Navigation property
public virtual Reference1 ReferenceTbl1 {get; set;}
}
public class Reference1
{
public int Id {get; set;}
public string Property1 {get; set;}
public string Property2 {get; set;}
}
The tables were created successfully using migration. The data was in both the tables.
Then after couple of months for some reason I had to change the schema. I had to create other table called Reference2. Drop the foreign key and column named "Reference1Id" from the table "Employee". And introduce another foreign key pointing to table "Reference2" in "Employee". So I made following changes in model
public class Employee
{
public int Id {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
[ForeignKey("ReferenceTbl1")]
public int Reference1Id {get; set;}
// New foreign key
[ForeignKey("ReferenceTbl2")]
public int Reference2Id {get; set;}
// Navigation property
public virtual Reference1 ReferenceTbl1 {get; set;}
// New navigation property
public virtual Reference2 ReferenceTbl2 {get; set;}
}
public class Reference2
{
public int Id {get; set;}
public string Property3 {get; set;}
public string Property4 {get; set;}
}
I successfully dropped the old foreign key and column from table "Employee" by creating new migration as follows
public override void Up()
{
DropForeignKey("dbo.Employee", "Reference1Id","dbo.Reference1");
DropIndex("dbo.Employee", new[] { "Reference1Id" });
DropColumn("dbo.Employee", "Reference1Id");
}
public override void Down()
{
AddColumn("dbo.Employee","Reference1Id",
e=>e.String(nullable:false,defaultValue: ""));
CreateIndex("dbo.Employee", "Reference1Id");
AddForeignKey("dbo.Employee", "Reference1Id",
"dbo.Reference1", "Id", cascadeDelete: true);
}
So after I ran the command "Update-database" in package manager console, the "Employee" table was updated exactly as I wanted with new foreign key column "Reference2Id" and no column with name "Reference1Id". I also altered the data appropriately.
But now when I try to access the table "Employee", it gives me this error "Invalid column name 'Reference1Id'".
The column "Reference1Id" is not present in the table "Employee" then why would this error occur?
I would really appreciate some help here.
Thanks

Database structure with Entity Framework Core - Discrimination

I have the following problem:
I'd like to be able sell a few kinds of products in my store.
I have created following infrastructure:
public class Product
{
public int Id {get; set;}
public string Name {get; set;}
...
public int? ParentId {get; set;}
public Product Parent {get; set;}
public ICollection<Product> Children {get; set;}
}
public class AudioFile : Product
{
public string SomeValue {get; set;}
}
public class DocummentFile : Product
{
public string SomeString {get; set;}
}
In fluent mapping I am using discriminator.
So, to Select the amount of documment files without a parent, I am doing something like this:
DbContext.DocummentFiles.Count(x=> !x.ParentId.HasValue)
It works with Audio files too.
I'd like to select sample dictionary of all children for some audio file with SomeValue:
DbContext.AudioFiles
.Where(x => x.ParentId == 222)
.Select(x => x.Children
.OfType<AudioFile>()
.ToDictionary(t => t.Name, t => t.SomeValue));
Unfortunately, it does not work. I have high CPU usage by the long time, but I have not any result.

Fluent NHibernate HASMANY mapping without references

I am a beginner at using Fluent NHibernate.
I am developing a C# application that has to interact with an existing database.Let say I have 2 tables: Items and ItemsList.
Items: ID INT ItemName VARCHAR(100)
ItemsList: ID INT ChildItemID INT
I've built 2 classes and their mapping:
public class Items
{
public virtual int id {get; set;}
public virtual string itemName {get; set;}
}
public class ItemsMap : ClassMap<Items>
{
public ItemsMap()
{
Id(x => x.id).GeneratedBy.Increment();
Map(x => x.itemsName);
}
}
public class ItemsList()
{
public virtual int id {get; set;}
public virtual IList<Items> childItems {get; set;}
public ItemsList()
{
childItems = new List<Items>();
}
}
public class ItemsListMap : ClassMap<ItemsList>
{
public ItemsListMap()
{
Id(x => x.id).GeneratedBy.Increment();
HasMany(x => x.childItems).KeyColumn("childID").Cascade.All();
}
}
And finally, I insert an item in the itemsList and save it all:
try
{
using( ISession session = NH.OpenSession())
{
using(ITransaction transaction = session.BeginTransaction())
{
Items i = New Items()
i = session.get<Items>(1);
ItemsList il = new ItemsList();
il.childID.Add(i);
session.SaveOrUpdate(il);
transaction.Commit();
}
}
}
So when I commit, I have a new entry in ItemsList table, but the childID is blank.
Question:
All the examples I see has a reference to ItemsListID in Items table. But I don't want to have this reference since I want the item to be unique in the items table. How can I acheve that?
The NHibernate native way for expressing the unique reference, is:
5.1.12. one-to-one
There are two varieties of one-to-one association:
primary key associations
unique foreign key associations
Primary key associations don't need an extra table column; if two rows are related by the association then the two table rows share the same primary key value. So if you want two objects to be related by a primary key association, you must make sure that they are assigned the same identifier value!...
Other words, Tables would look like this (Table Items generates the value of ItemID, table ItemsList takes that value and stores it in the ItemID ) :
Items: ItemID INT ItemName VARCHAR(100)
ItemsList: ItemID INT
The C# would be (I changed Items into Item and ItemList into ItemMoreDetails, because it is not a list anymore)
public class Item
{
public virtual int ItemId { get; set; }
...
public virtual ItemMoreDetails ItemMoreDetails {get; set; }
public class ItemMoreDetails
{
public virtual int ItemId { get; set; }
...
public virtual Item Item {get; set;}
The mapping would be (in fluent):
// Parent side
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Id(x => x.id).GeneratedBy.Increment();
...
HasOne(x => x.ItemMoreDetails).Cascade.All();
// child side
public class ItemMoreDetailsMap: ClassMap<ItemMoreDetails>
{
public ItemMoreDetailsMap()
{
...
References(x => x.parent).Unique();
See the doc:
HasOne / one-to-one

Trouble formulating inner joins using NHibernate Linq query

Using NHibernate 3.3.1.400, I'm having problems expressing what is a simple SQL statment using NHibernate's Linq provider.
My domain model looks like this:
public class Site
{
public virtual Guid Id {get; set;}
public virtual string Name {get; set;}
}
// a site has many filers
public class Filer
{
public virtual Guid Id {get set;}
public virtual Site Site {get; set;}
public virtual string Name {get; set;}
}
// a filer has many filings
public class Filing
{
public virtual Guid Id {get set;}
public virtual Filer Filer {get; set;}
public virtual DateTime FilingDate {get; set;}
}
//a filing has many items
public class Item
{
public virtual Guid Id {get set;}
public virtual Filing Filing {get; set;}
public virtual DateTime Date {get; set;}
public virtual decimal Amount {get; set;}
}
public class SearchName
{
public virtual Guid Id {get set;}
public virtual string Name {get; set;}
}
// there are potentially many NameLink objects tied to a single search name
public abstract class NameLink
{
public virtual Guid Id {get set;}
public virtual SearchName SearchName {get; set;}
}
public class NameLinkToFiler: NameLink
{
public virtual Filer Filer {get; set;}
}
public class NameLinkToItem: NameLink
{
public virtual Item Item {get; set;}
}
My query is supposed to return a list of matching Item elements:
var query = session.Query<Item>()
.Where(x => x.Filing.Filer.Site == mySite);
The joins connecting Site -> Filer -> Filing -> Item are working great through my mappings, but the problems start when I try to join the NameLinkToFiler or NameLinkToItem classes based on user input.
If the user wants to filter the Query results with a filer name, I want to join the results of the Item query with the results of this query:
var filerNameQuery = session.Query<NameLinkToFiler>()
.Where(q=>q.SearchName.Contains('some name'));
I want the results of the NameLinkToFiler.Filer property to join the Item.Filing.Filer property, so my list of returned items is reduced.
Note: The 'Contains' keyword above is a full-text index search I'm using as described here. It's working fine, and let's just assume the filerNameQuery is an IQueryable<NameLinkToFiler>.
It's pretty easy to do this in straight SQL:
select filer.Name, filing.FilingDate, filer.Name, item.Date, item.Amount
from Search_Name searchNameForFiler, Search_Name searchNameForItem, Name_Link_Filer nameLinkFiler,
Name_Link_Item nameLinkItem, Item item, Filing filing, Filer filer, Site s
where
contains(searchNameForFiler.Name, :filerName) and searchNameForFiler.Id = nameLinkFiler.SearchNameId and nameLinkFiler.FilerId = filer.Id and
contains(searchNameForItem.Name, :itemName) and searchNameForItem.Id = nameLinkItem.SearchNameId and nameLinkItem.ItemId = item.Id
and item.FilingId = filing.Id
and filing.FilerId = filer.Id
and filing.SiteId = :site
...but I don't want to lose the compile-time checks for this sort of query.
Thanks.
Apparently, the answer is to not use lambda syntax.
This works fine:
query = from t in parentItemQuery
join l in Session.Query<NameLinkToFiler>() on t.Filing.Filer.Id equals l.Filer.Id
join n in Session.Query<SearchName>() on l.SearchName.Id equals n.Id
where sn.Contains(request.FilerName)
select t;

Fluent NHibernate mapping a class where the Id is a reference to another class

I am trying to use fluent nHibernate to map a class that only exists as a container for other classes, and doesn't have an Id of its own. But I want the Id of the class to be a different class. That really doesn't make sense, it might be easier to explain with an example data structure.
abstract QueryBuilder
{
public IEnumerable<string> Category1Keys {get; set;}
public IEnumerable<int> Category2Ids {get; set;}
public IEnumerable<int> Category3Ids {get; set;}
}
Set
{
public int Id {get; set;}
public string Name {get; set;}
public SetQueryBuilder QueryBuilder{get; set;}
}
SetQueryBuilder : QueryBuilder
{
}
News
{
public int Id {get; set;}
public string Name {get; set;}
public NewsQueryBuilder QueryBuilder{get; set;}
}
NewsQueryBuilder : QueryBuilder
{
}
Now the QueryBuilder is the name is a concept that we use to map various (unrelated) items to the same categories. So things that might use QueryBuidler are News, Pages, Permissions.
Becuase all the various items that use QueryBuilder map that relationship in the same way, and ocasionally we actually use the paramters from one type of QueryBuilder as a the select criteriea for another type of QueryBuilder (ie if news has QB of A, get all the Sets that match the same criteria), I want to have a abstract QueryBuilder class, and then extend it for all the things that have mappings to all the categories.
Here is an example of the Set DB for query builder. There isn't an actual item in the DB for QueryBuilder - its composed of all the info from the category class join tables.
f_set
{
set_id (PK, int)
name (varchar)
}
f_set_cat1
{
set_id (PK, FK, int)
cat1_key (PK, FK, char(3))
}
f_set_cat2
{
set_id (PK, FK, int)
cat2_id (PK, FK, int)
}
f_set_cat3
{
set_id (PK, FK, int)
cat3_id (PK, FK, int)
}
My problem is how to map each instance of the QueryBuilder class in nHibernate, as it doesnt' have a real key or table entry, I would like to say that the Id is a reference to the Set that all the category mappings use.
Here are my fluent mappings so far:
SetMapping : ClassMap<Set>
{
public SetMapping()
{
Schema("cam");
Table("f_set");
Id(x => x.Id, "f_set_id").GeneratedBy.Identity();
Map(x => x.Name, "name").Not.Nullable();
HasOne<SetQueryBuilder>(x => x.QueryBuilder);
}
}
SetQueryBuilderMapping : ClassMap<SetQueryBuilder>
{
public SetQueryBuilderMapping()
{
References(x => x.Set, "set_id");
HasMany(x => x.Category1Keys).Table("f_set_cat1").Element("cat1_key").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat2").Element("cat2_id").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat3").Element("cat3_id").KeyColumn("set_id");
}
}
Any help with the final step of the mapping would be hugely appreciated
Thanks
Saan
FURTHER INVESTIGATION
Ok I have done a bit mroe investigating on this and have foudn that combining the two classes works fine if I do this:
CLASS:
Set
{
public int Id {get; set;}
public string Name {get; set;}
//public SetQueryBuilder QueryBuilder{get; set;}
public IEnumerable<string> Category1Keys {get; set;}
public IEnumerable<int> Category2Ids {get; set;}
public IEnumerable<int> Category3Ids {get; set;}
}
MAPPING
SetMapping : ClassMap<Set>
{
public SetMapping()
{
Schema("cam");
Table("f_set");
Id(x => x.Id, "f_set_id").GeneratedBy.Identity();
Map(x => x.Name, "name").Not.Nullable();
//HasOne<SetQueryBuilder>(x => x.QueryBuilder);
HasMany(x => x.Category1Keys).Table("f_set_cat1").Element("cat1_key").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat2").Element("cat2_id").KeyColumn("set_id");
HasMany(x => x.Category2Ids).Table("f_set_cat3").Element("cat3_id").KeyColumn("set_id");
}
}
When doing nHibernate mappings this makes perfect sense, but I would really like to have all the category mappings in the separate SetQueryBuilder class.
Thanks again for any help
Saan
You are looking for subclassing strategy. Take a look at this http://nhibernate.info/doc/nh/en/index.html#inheritance
You probably want to follow a table per class with table per subclass strategy imo.