I currently have a legacy system that uses SPs exclusively for access to the DB. My domain object looks something like this:
public class User : EntityBase
{
public virtual string Name {get;set;}
public virtual string CreatedBy {get;set;}
public virtual DateTime CreatedDate {get;set;}
}
The SP I have that mapped this looked like this:
CREATE PROCEDURE getUser
{
#ID int
}
select
Name
,(SELECT TOP 1 UserName FROM AuditTrail WHERE EntityID = [User].[ID] AND EntityName = 'User' AND AuditActionID = 1 ORDER BY DateStamp) AS CreatedBy
,(SELECT TOP 1 DateStamp FROM AuditTrail WHERE EntityID = [User].[ID] AND EntityName = 'User' AND AuditActionID = 1 ORDER BY DateStamp) AS CreatedDate
FROM [User]
WHERE [User].ID = #ID
So, as you can see, the audit information is separated from the entity itself on the database and the CreatedBy/CreatedOn (and ModifiedBy/ModifiedOn) are stored in a separate table called AuditTrail. the AuditActionID field on the table specifies if it was a create/update.
How can I setup this mapping with NHibernate? I looked into JOIN but it doesn't give me the option to restrict by the additional values (and a join isn't what I want).
Also, if this is possible in Fluent NHibernate, that's a bonus but I'm fine with trying just standard NHibernate mapping config if it gets me there.
UPDATE:
I have found one way to do this, but I'm not a fan. I have setup a SQLQuery that reads the data and maps it back to an object. It works, but I'd love to do this via mapping. Is it even possible since the "values" from the database I'm mapping to is a subselect and not editable?
Solution:
Thanks to the tip from Diego, this was the final solution I found (using Fluent NHibernate, in my ClassMap file):
Map(x => x.CreatedBy).Formula("(SELECT TOP 1 AuditTrail.UserName FROM AuditTrail WHERE AuditTrail.EntityID = [ID] AND AuditTrail.EntityName = 'User' AND AuditTrail.AuditActionID = 1 ORDER BY AuditTrail.DateStamp)");
Map(x => x.CreatedDate).Formula("(SELECT TOP 1 AuditTrail.DateStamp FROM AuditTrail WHERE AuditTrail.EntityID = [ID] AND AuditTrail.EntityName = 'User' AND AuditTrail.AuditActionID = 1 ORDER BY AuditTrail.DateStamp)");
Thanks,
M
You can specify the select clause as the formula for your property.
Related
I have this mapping:
#Entity
#Table(name="CUSTOMER")
class Customer {
#Id
#Column(name="CUSTOMER_ID")
Long id;
...
#ElementCollection
#CollectionTable(name="CUSTOMER_ADDRESS", joinColumns=#JoinColumn(name="CUSTOMER_ID"))
List<CustomerAddress> addresses;
}
#Embeddable
class CustomerAddress {
String street;
String zip;
String ...
String ...
...
}
I have two saved addresses for customer ID : 1. If i update this customer, having a single address on, Eclipselink tries deletes the missing address row using all CustomerAddress fields on DELETE where statement:
DELETE FROM CUSTOMER_ADDRESS WHERE STREET = ?, ..., ZIP = ?, ..., CUSTOMER_ID = ?
The issue is i may have accents and other data that prevents the WHERE statement to match the row using all fields. Is there anyway to force Eclipselink to delete all user addresses and insert again?
DELETE FROM CUSTOMER_ADDRES WHERE CUSTOMER_ID = ?
INSERT ALL FROM COLLECTION
I could not find a way to accomplish what i wanted. It seems Hibernate has the behavior i expect.
Changed ElementCollection to OneToMany and changed the model to have an ID under CustomerAddress.
I have a database that has a parent-child look up for my drop down values, there is a domain table which represents what type of drop down it is ie "TITLE", "MARITAL_STATUS", "COUNTRY" and there is look up for the values associated with each of these drop downs that we call domain values
EG
TITLE (Domain table)
MR (Domain Value table)
MRS (Domain Value table)
MISS (Domain Value table)
DOCTOR (Domain Value table)
MARITAL_STATUS (Domain table)
SINGLE (Domain Value table)
MARRIED (Domain Value table)
DIVORCED (Domain Value table)
I find myself writing ugly code which exposes the under lying implementation similar to below
using (var db = new OrderContext())
{
...
var maritalStatusId = (
from sp in db.DomainValues
where sp.ShortCode == "MARRIED"
where sp.Domain.ShortCode == "MARITAL_STATUS"
select sp.Id
).FirstOrDefault();
if maritalStatusId != 0)
{
orderStore.maritalStatus = maritalStatusId;
}
...
db.OrderStores.Add(orderStore);
db.SaveChanges();
}
Is it possible to write code that looks similar to this
using (var db = new OrderContext())
{
...
orderStore.maritalStatus = new MaritalStatus { ShortCode = "MARRIED" }
...
db.OrderStores.Add(orderStore);
db.SaveChanges();
}
, where a navigation property handles the insert?
If so how do I set this up?
Any links to articles on this would be good too.
You can keep a cache with the different values of your dropdowns, that seems reasonnable, or you can use enumeration if the values are not dynamic.
Another solution would be to have the shortcode field to be the primary key of the table, all your problems would be resolved.
I am using EF5 Code First and I have a standard Tree type structure which is a bunch of nested Folders. Some folders have Devices inside them. What I want to do is be able to select a folder and find all devices within that folder and its child folders.
After some digging it seems I need use Common Table Expressions in order to generate the data I need. Unfortunately I am really struggling with how to do this.
public class Folder
{
public virtual ICollection<Folder> Children { get; set; }
public virtual ICollection<Device> Devices { get; set; }
}
public class Device
{
public virtual ICollection<Folder> PresentInFolders { get; set; }
}
I have attempted to add a GetDevicesForFolderRecursively function based on the work on this MSDN article. The query is incomplete as folder.FolderId equals device.FolderID wont work in my situation because a device can be in more than one folder so it has a list of folders it's in.
public IQueryable<Device> GetDevicesForFolderRecursively(int folderID)
{
return from folder in this.FlatFolders
join device in this.Devices on folder.FolderId equals device.FolderID
where folder.FolderId == folderID
select device;
}
Once this is out the way, I also dont know how accurately construct the CTE. The CTE that the MSDN article creates looks like so but I dont know how to modify it for my needs and how this would work recursively?
context.Database.ExecuteSqlCommand(#"CREATE VIEW [dbo].[ManagerEmployees]
AS
WITH cte ( ManagerEmployeeID, EmployeeID )
AS ( SELECT EmployeeID ,
EmployeeID
FROM dbo.Employees
UNION ALL
SELECT e.EmployeeID ,
cte.EmployeeID
FROM cte
INNER JOIN dbo.Employees AS e ON e.ReportsToEmployeeID = cte.ManagerEmployeeID
)
SELECT ISNULL(EmployeeID, 0) AS ManagerEmployeeID ,
ISNULL(ManagerEmployeeID, 0) AS EmployeeID
FROM cte");
Update
I have managed to get the CTE query to this state but still doesnt quite work. It doesnt understand AS ( SELECT FolderId , [EstateManagerService].dbo.Devices.DeviceSerial and im not sure what values are meant to be in there?
CREATE VIEW [dbo].[ManagerEmployees]
AS
WITH cte ( FolderID, DeviceID )
AS ( SELECT FolderId ,
[EstateManagerService].dbo.Devices.DeviceSerial
FROM [EstateManagerService].dbo.Folders
UNION ALL
SELECT e.Folder_FolderId,
cte.FolderID
FROM cte
INNER JOIN [EstateManagerService].dbo.FolderDevices AS e ON e.Folder_FolderId = cte.FolderID
)
SELECT ISNULL(DeviceID, 0) AS FolderID ,
ISNULL(FolderID, 0) AS DeviceID
FROM cte
Have a legacy database with mapping table structures like the following. I am trying to figure out how to fluently map this type of relationship.
There are multiple parent tables that use a mapping table to store notes.
The parent tables look like the following:
P1 Table
ID iSomething
P2 Table
ID iSomethingElse
There is a mapping table that will take a parent table and map it to a note table.
Mapping Table
ID i_RecordUniqueID
ID i_NoteID
ID i_RecordID
The column i_RecordID contains a numeric value indicating which parent table the i_RecordUniqueID value came from. The mapping table only has those three columns and is a ternary primary key.
Here is the note table:
Note Table
ID i_NoteID
The query to find table P1's notes is as follows:
Select n.*
from P1 p
inner join Mapping m on p.iSomething = m.i_RecordUniqueID and m.i_RecordID = 1
inner join Note n on m.i_NoteID = n.i_NoteID
The query to find table P2's notes is as follows:
Select n.*
from P2 p
inner join Mapping m on p.iSomething = m.i_RecordUniqueID and m.i_RecordID = 2
inner join Note n on m.i_NoteID = n.i_NoteID
In my Parent tables mapping file, I have an association like the below. I don't know how to add the i_RecordID constraint.
HasManyToMany<Note>(x => x.Notes)
.Table("Mapping")
.ParentKeyColumn("i_RecordUniqueID")
.ChildKeyColumn("i_NoteID")
.Cascade.All();
FluentNHibernatew does not yet support ManyToAny mapping. you could map it for readonly access
// P1Map()
HasManyToMany(x => x.Notes)
.Table("Mapping")
.ParentKeyColumn("i_RecordUniqueID")
.Where("i_RecordID == 1")
.ChildKeyColumn("i_NoteID")
.Cascade.All();
// P2Map()
HasManyToMany(x => x.Notes)
.Table("Mapping")
.ParentKeyColumn("i_RecordUniqueID")
.Where("i_RecordID == 2")
.ChildKeyColumn("i_NoteID")
.Cascade.All();
or you have to create a component
ICollection<TableToNote> Notes;
public TableToNoteMap()
{
ReferencesAny(x => x.Parent).IdentityColumn("i_RecordUniqueID").MetaTypeColumn("i_RecordID")...;
References(x => x.Note);
}
SQL 2008 | .NET 4.0 | NHibernate 3.1 | NHibernate.Castle 3.1 | Castle.Core 2.5.2
So I have a linking table with metadata, like the author of this question NHibernate Mapping a Many to Many with Data on Join Table
Initially, I mapped just like the answer to this question as it seemed the most parsimonious way to handle it. However, after turning on show_sql and observing what was going on, the ID lookups ended up yielding N+1 queries where N is the number of associations.
Observe this example database that is analogous to my actual data, defined in sql-like syntax
CREATE TABLE [User]
(
Id int PRIMARY KEY
)
CREATE TABLE UserPref
(
Id int PRIMARY KEY,
Name varchar(32)
)
CREATE TABLE UserPrefAssociation
(
UserId int,
PrefId int,
Value varchar(32)
)
I hacked the following code together with this User one-to-many object mapping IList<UserPrefAssociation> Preferences { get; set; }
public IDictionary<string, string> GeneratePrefDict()
{
return Preferences
.ToDictionary(i => i.UserPref.Name, i => i.Value);
}
Sure, this works great, but as mentioned before, each i.UserPref.Name, is an additional query to SQL.
After playing in SQL, I have found the query that accomplishes what I want. My question then becomes how can I do this with NHibernate?
SELECT UserPref.Name, UserPrefAssociation.Value
FROM [User]
INNER JOIN UserPrefAssociation ON [User].Id = UserPrefAssociation.UserId
INNER JOIN UserPref ON UserPrefAssociation.UserPrefId = UserPref.Id
WHERE [User].Id = 1
~~~~SOLVED~~~~~
using NHibernate.Linq;
...
public IDictionary<string, string> GeneratePrefDict(ISession s)
{
return
(from entry in s.Query<User_UserPref>()
where entry.User == this
select new
{
key = entry.UserPref.Name,
value = entry.Value
})
.ToDictionary(i => i.key, i => i.value);
}
Generates this SQL
NHibernate: select userpref1_.Name as col_0_0_, user_userp0_.Value as col_1_0_ f
rom User_UserPref user_userp0_ left outer join UserPref userpref1_ on user_userp
0_.UserPrefId=userpref1_.Id where user_userp0_.UserId=#p0;#p0 = 1 [Type: Int32 (
0)]
Which is better than N+1 queries, and solves my issue.
I think you can achieve what you are wanting with Futures and QueryOver. Take a look at the following article:
Fighting cartesian product (x-join) when using NHibernate 3.0.0
If you can't visualize how to accomplish what you need from the above I can tailor that example more to your needs.