I have two tables in a legacy database that I need to map using NHibernate. Unfortunately, one of these tables are using a composite key and I am running into a problem which I describe below.
Firstly, here is the schema of the two tables:
CREATE TABLE [dbo].[tBenchmarkFxHedgeHistory](
[BenchmarkFxHedgeHistoryId] [int] IDENTITY(1,1) NOT NULL,
[BenchmarkFxHedgeId] [int] NOT NULL,
[ModelId] [int] NOT NULL,
[BaseCurrencyCode] [nvarchar](5) NOT NULL,
[BenchmarkFxHedgeTypeId] [int] NOT NULL,
[DateFrom] [smalldatetime] NOT NULL,
[DateTo] [smalldatetime] NULL,
[PctHedgeBackToBase] [decimal](13, 10) NULL,
[Enabled] [bit] NOT NULL,
[BenchmarkHedgeStatusId] [int] NOT NULL,
[AuditActionId] [int] NOT NULL,
[Timestamp] [timestamp] NOT NULL,
[HistoryUser] [nvarchar](50) NOT NULL CONSTRAINT [DF_tBenchmarkFxHedgeHistory_HistoryUser] DEFAULT (suser_sname()),
[HistoryDate] [datetime] NOT NULL CONSTRAINT [DF_tBenchmarkFxHedgeHistory_HistoryDate] DEFAULT (getdate()),
CONSTRAINT [PK_tBenchmarkFxHedgeHistory] PRIMARY KEY CLUSTERED
(
[BenchmarkFxHedgeHistoryId] ASC
)
CREATE TABLE [dbo].[tBenchmarkFxHedgeRatio](
[BenchMarkFxHedgeId] [int] NOT NULL,
[NonBaseCurrencyCode] [nvarchar](5) NOT NULL,
[PctHedgeBackToBase] [decimal](13, 10) NOT NULL,
CONSTRAINT [PK_tBenchmarkFxHedgeRatio] PRIMARY KEY CLUSTERED
(
[BenchMarkFxHedgeId] ASC,
[NonBaseCurrencyCode] ASC
)
And here are the domain classes:
public class BenchmarkFxHedgeRuleHistory
{
private IList<BenchmarkFxRuleRatioHistory> _percentages = new List<BenchmarkFxRuleRatioHistory>();
public virtual int Id { get; set; }
public virtual string BaseCurrencyCode { get; set; }
public virtual DateTime DateFrom { get; set; }
public virtual DateTime? DateTo { get; set; }
public virtual decimal? Percentage { get; set; }
public virtual bool Enabled { get; set; }
public virtual byte[] Timestamp { get; set; }
public virtual BenchmarkFxHedgeStatus Status { get; set; }
public virtual BenchmarkFxHedgeType Strategy { get; set; }
public virtual Model Model { get; set; }
public virtual BenchmarkFxHedgeRule Rule { get; set; }
public virtual AuditAction AuditAction { get; set; }
public virtual IList<BenchmarkFxRuleRatioHistory> Percentages
{
get { return _percentages; }
}
}
[Serializable]
public class BenchmarkFxRuleRatioHistory
{
public virtual string NonBaseCurrencyCode { get; set; }
public virtual decimal Percentage { get; set; }
public virtual BenchmarkFxHedgeRuleHistory HistoryEntry { get; set; }
public override bool Equals(object obj)
{
var rule = obj as BenchmarkFxRuleRatioHistory;
if (rule == null) return false;
return rule.HistoryEntry.Id == HistoryEntry.Id && NonBaseCurrencyCode == rule.NonBaseCurrencyCode;
}
public override int GetHashCode()
{
return NonBaseCurrencyCode.GetHashCode() ^ HistoryEntry.GetHashCode();
}
}
And finally, here are the NHibernate mapping files:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Tests.DomainModel" assembly="Tests.DomainModel" xmlns="urn:nhibernate-mapping-2.2">
<class name="BenchmarkFxHedgeRuleHistory" table="`tBenchmarkFxHedgeHistory`" schema="`dbo`">
<id name="Id" access="property" column="`BenchmarkFxHedgeHistoryId`">
<generator class="native" />
</id>
<many-to-one name="Rule" class="BenchmarkFxHedgeRule" column="`BenchmarkFxHedgeId`" not-null="true" fetch="select" />
<property name="BaseCurrencyCode" type="String" column="`BaseCurrencyCode`" length="5" />
<property name="DateFrom" type="DateTime" column="`DateFrom`" />
<property name="DateTo" type="DateTime" column="`DateTo`" />
<property name="Enabled" type="Boolean" column="`Enabled`" />
<property name="Percentage" type="Decimal" column="`PctHedgeBackToBase`" />
<property name="Timestamp" type="BinaryBlob" column="`Timestamp`" />
<many-to-one name="Status" class="BenchmarkFxHedgeStatus" column="`BenchmarkHedgeStatusId`" not-null="true" fetch="join" />
<many-to-one name="Strategy" class="BenchmarkFxHedgeType" column="`BenchmarkFxHedgeTypeId`" not-null="true" fetch="join" />
<many-to-one name="Model" class="Model" column="`ModelId`" not-null="true" fetch="select" />
<many-to-one name="AuditAction" class="AuditAction" column="`AuditActionId`" not-null="true" fetch="join" />
<bag name="Percentages" fetch="join" access="readonly" inverse="true" lazy="false" table="tBenchmarkFxHedgeRatioHistory" cascade="all-delete-orphan" subselect="">
<key column="`BenchmarkFxHedgeHistoryId`" />
<one-to-many class="BenchmarkFxRuleRatioHistory" />
</bag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Tests.DomainModel" assembly="Tests.DomainModel" xmlns="urn:nhibernate-mapping-2.2">
<class name="BenchmarkFxRuleRatioHistory" table="`tBenchmarkFxHedgeRatioHistory`" schema="`dbo`">
<composite-id>
<key-property name="NonBaseCurrencyCode" type="String" column="`NonBaseCurrencyCode`" />
<key-many-to-one name="HistoryEntry" class="BenchmarkFxHedgeRuleHistory" column="`BenchmarkFxHedgeHistoryId`" />
</composite-id>
<property name="Percentage" type="decimal" column="`PctHedgeBackToBase`" />
</class>
</hibernate-mapping>
Now I use these in the following code:
using(var session = DataMapperConfiguration.SessionFactory.OpenSession())
{
var sessionRule = session.Get<BenchmarkFxHedgeRule>(id);
var historyList = session.Query<BenchmarkFxHedgeRuleHistory>()
.Where(x => x.Rule == sessionRule).ToList();
Assert.AreEqual(2, historyList[0].Percentages.Count);
}
What happens is that sessionRule is correctly hydrated, but historyList does not have its Percentages property correctly hydrated. It keeps coming back as an empty list, but I expect it to have members in the list since there are matching rows in the database.
Any advice? What could I be doing wrong?
It turns out that the problem was that I declared the Percentages collection as an IList instead of an ICollection. Changing the declaration to ICollection<BenchmarkFxRuleRatioHistory> solves the problem.
Related
I've spent a couple of days researching this on Google, StackOverflow and reading various blogs on this but to no avail. My question is if a collection is updated within an entity then would this cause NHibernate to update all properties in the entity of the modified collections? In this case I've added a user to a role and once I call session.SaveOrUpdate then 2 updates occur (NHibernate updates user and role) then the INSERT occurs. Is this the default behavior? I've tried to do the following to see if I can get NHibernate to just issue the INSERT statement:
Ran a Ghostbuster test on these entities based on code by Jason Dentler and Fabio Maulo but everything comes back ok and there are no dirty properties.
I made properties nullable that are defined as null in the database.
Set Inverse true on one of the entites.
Any help or insight is much appreciated.
Here are the mapping and class files.
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="Custom.NHibernateLib.Model" assembly="Custom.NHibernateLib" xmlns="urn:nhibernate-mapping-2.2">
<class name="Users">
<id name="UserId" type="Int64">
<generator class="hilo" />
</id>
<property name="ApplicationName" type="String" length="50" />
<property name="Username" type="String" length="15" />
<property name="Email" type="String" length="15" />
<property name="Password" type="String" length="50" />
<property name="PasswordSalt" type="String" length="128" />
<property name="PasswordQuestion" type="String" length="15" />
<property name="PasswordAnswer" type="String" length="50" />
<property name="IsApproved" type="YesNo" />
<property name="LastActivityDate" type="DateTime" />
<property name="LastLoginDate" type="DateTime" />
<property name="LastPasswordChangedDate" type="DateTime" />
<property name="CreationDate" type="DateTime" />
<property name="IsOnline" type="YesNo" />
<property name="IsAnonymous" type="YesNo" />
<property name="IsLockedOut" type="YesNo" />
<property name="LastLockedOutDate" type="DateTime" />
<property name="FailedPasswordAttemptCount" type="Int32" />
<property name="FailedPasswordAttemptWindowStart" type="DateTime" />
<property name="FailedPasswordAnswerAttemptCount" type="Int32" />
<property name="FailedPasswordAnswerAttemptWindowStart" type="DateTime" />
<property name="Comment" type="String" length="4001" />
<bag name="RoleList" table="UserRoles" lazy="true" cascade="save-update, persist" batch-size="10">
<key column="UserId" not-null="true" />
<many-to-many class="Roles" foreign-key="FK_Roles_UserRoles_RoleId">
<column name="RoleId" not-null="true" />
</many-to-many>
</bag>
<one-to-one name="Profiles" />
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="Custom.NHibernateLib.Model" assembly="Custom.NHibernateLib" xmlns="urn:nhibernate-mapping-2.2">
<class name="Roles">
<id name="RoleId" type="Int64">
<generator class="hilo" />
</id>
<property name="ApplicationName" type="String" length="50" />
<property name="RoleName" type="String" length="50" />
<bag name="UserList" table="UserRoles" lazy="true" inverse="true" cascade="save-update, persist" batch-size="10">
<key column="RoleId" not-null="true" />
<many-to-many class="Users" foreign-key="FK_User_UserRoles_UserId">
<column name="UserId" not-null="true" />
</many-to-many>
</bag>
</class>
</hibernate-mapping>
Here are the class files:
using System;
using System.Collections.Generic;
namespace Custom.NHibernateLib.Model
{
public class Users : Entity
{
public Users (){}
public virtual long UserId { get; set; }
public virtual string ApplicationName { get; set; }
public virtual string Username { get; set; }
public virtual string Email { get; set; }
public virtual string Password{ get; set; }
public virtual string PasswordSalt { get; set; }
public virtual string PasswordQuestion { get; set; }
public virtual string PasswordAnswer { get; set; }
public virtual bool? IsApproved { get; set; }
public virtual DateTime? LastActivityDate { get; set; }
public virtual DateTime? LastLoginDate { get; set; }
public virtual DateTime? LastPasswordChangedDate { get; set; }
public virtual DateTime? CreationDate { get; set; }
public virtual bool? IsOnline { get; set; }
public virtual bool? IsAnonymous { get; set; }
public virtual bool? IsLockedOut { get; set; }
public virtual DateTime? LastLockedOutDate { get; set; }
public virtual int? FailedPasswordAttemptCount { get; set; }
public virtual DateTime? FailedPasswordAttemptWindowStart { get; set; }
public virtual int? FailedPasswordAnswerAttemptCount { get; set; }
public virtual DateTime? FailedPasswordAnswerAttemptWindowStart { get; set; }
public virtual string Comment { get; set; }
public virtual IList<Roles> RoleList { get; set; }
public virtual Profiles Profiles { get; set; }
}
}
using System;
using System.Collections.Generic;
namespace Custom.NHibernateLib.Model
{
public class Roles : Entity
{
public Roles(){}
public virtual long RoleId { get; set; }
public virtual string ApplicationName { get; set; }
public virtual string RoleName { get; set; }
public virtual IList<Users> UserList { get; set; }
}
}
Adding the user and the role to their respective collections.
usr.RoleList.Add(role);
role.UserList.Add(usr);
When this is called session.SaveOrUpdate(role) then this occurs in NHibernate.
-- statement #1
UPDATE Users...WHERE UserId = 32768
-- statement #2
UPDATE Roles...WHERE RoleId = 65536
-- statement #3
INSERT INTO UserRoles...
Ok well I can conclude the my issue was caused by the way I was handling the session and commit after the save and update. I was closing the session then recreating it within multiple methods in my custom membership library code. For example, each method I was calling I was wrapping it around a using statement for the session and transaction. I should have known better and not go by my assumptions and just spend the time to RTM.
The NHibernate in Action book about session management and using current session context is what guided me. I coded that up to use in my unit test and everything worked fine. Though the book is a bit dated it still had some good basic info.
I have two classes:
namespace fm.web
{
public class User
{
public static string default_username = "guest";
public static string default_password = "guest";
private UserType usertype;
public virtual int? Id { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual DateTime Datecreated { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
public virtual string Email { get; set; }
public virtual UserType Usertype
{
get { return usertype; }
set { usertype = value; }
}
}
}
namespace fm.web
{
public class UserType
{
public virtual int? Id { get; set; }
public virtual string Title { get; set; }
}
}
Here are the mapping files
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="fm.web"
assembly="fm.web">
<class name="User" table="[user]">
<id name="Id">
<column name="id" />
<generator class="native" />
</id>
<property name="Username" />
<property name="Password" />
<property name="Datecreated" />
<many-to-one name="Usertype"
class="UserType"
column="[type]"
cascade="all"
lazy="false"
/>
<property name="Firstname" />
<property name="Lastname" />
<property name="Email" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="fm.web"
assembly="fm.web">
<class name="UserType" table="[user_type]">
<id name="Id">
<column name="id" />
<generator class="native" />
</id>
<property name="Title" />
</class>
</hibernate-mapping>
I'm getting an exception: DuplicateMappingException
Could not compile the mapping document: fm.web.data.User.hbm.xml
Duplicate class/entity mapping User
Is nhibernate always this hard? Maybe I need a different framework.
I really think the mappings are fine which leads me to believe that the configuration setup is not quite right.
Please can you check that BuildSessionFactory is only called once on application start up.
Also please check that you are not including the mapping files twice as this will also throw this type of error.
Please post your configuration code.
You are correct in thinking that NHibernate is difficult to grasp for new comers espically the session management and mappings. Once you have grasped this then things get easier and are well worth the effort.
<class name="CashThreshold" table="CASH_THRESHOLD_COUNTERS" lazy="true" >
<id name="Id" column="ID" >
<generator class="assigned" />
</id>
<bag name="ThresholdNominalsList" cascade="all" inverse="true" lazy="false" table="CASH_THRESHOLD_CAS_COUNTERS">
<key column="CASH_THRESHOLD_ID" />
<one-to-many class="NominalThreshold" />
</bag>
Map second table
<class name="NominalThreshold" table="CASH_THRESHOLD_CAS_COUNTERS" lazy="true" >
<composite-id>
<key-property name="CashTrasholdId" column="CASH_THRESHOLD_ID" type="long"></key-property>
<key-property name="Nominal" column="NOMINAL" type="long"></key-property>
</composite-id>
<property name="MinNoteCount" column="MIN_NOTE_COUNT" />
<property name="MaxNoteCount" column="MAX_NOTE_COUNT" />
Table classes
public class CashThreshold : ICashThreshold
{
public virtual long Id { set; get; }
/// !!!!!!! IS ALWAYS AMPTY, but not null !!!!!
public virtual IList<INominalThreshold> ThresholdNominalsList { set; get; }
}
public class NominalThreshold : INominalThreshold
{
public virtual long CashTrasholdId { set; get; }
public virtual long Nominal { set; get; }
public virtual long MinNoteCount { set; get; }
public virtual long MaxNoteCount { set; get; }
public override bool Equals(Object obj)
{
var tmp = (INominalThreshold)obj;
return (tmp.CashTrasholdId == CashTrasholdId && tmp.Nominal == Nominal);
}
public override int GetHashCode()
{
return (int)CashTrasholdId ^ (int)Nominal;
}
}
Function for getting list of ICashThreshold
ICriteria selectAll = currentSession.CreateCriteria<ICashThreshold>();
IList<ICashThreshold> list = selectAll.List<ICashThreshold>();
Query executed whith no errors. Bag-query executed successfully in sql-client and returned 4 result, but IList< INominalThreshold > ThresholdNominalsList has no elements.
Thanks.
Problem solved. NHibernate mapped bag successfully, but list was empty, because the data in DB was NOT COMMITTED. I inserted test data in the table, but did not commit it. When I execute query in sql-client, it executed successfully(because do it in session, where table rows inserted), but hibernate had another session. Thats why NHibernate could not see the table data.
I have a table called person_skills like so:
person_id, skill_type_id, base_score, misc_score
There is a lookup table that contains id, name for skill_types.
Now the tricky thing is that I have a composite key for person_id, skill_type_id. There will be many entries within this table as a person may have 5 skills.
Currently I have got a class like so:
public class skill
{
int BaseScore {get;set;}
int MiscScore {get;set;}
}
Then I have a class to contain all this like below:
public class person_skills
{
int person_id {get;set;}
IDictionary<skill_type, skill> skills {get;set;}
}
Now im not sure if this is the best way to handle this relationship, ultimately I need to be able to give people a link to skills, there is one person to many skills.
I was thinking about just putting in an auto incrememnt id column and use that as the PK, but it doesn't seem ideal. I can change the models and the DB if required, but as this is used within an ajax part of a page I need to be able to change the skills and then update them into the database.
I did not find an actual question but I'll answer anyway. :)
You do not need a surrogate key for the person_skills table. Your composite key, consisting of person_id and skill_type_id, should be sufficient. I believe the following classes and mappings reflect what you are trying to accomplish here.
Classes:
public class Person
{
public virtual int PersonId { get; set; }
public virtual String Name { get; set; }
public virtual IList<PersonSkills> Skills { get; set; }
}
public class SkillType
{
public virtual int SkillTypeId { get; set; }
public virtual String SkillName { get; set; }
public virtual IList<PersonSkills> Persons { get; set; }
}
public class PersonSkills
{
public virtual int PersonId { get; set; }
public virtual int SkillTypeId { get; set; }
public virtual int BaseScore { get; set; }
public virtual int MiscScore { get; set; }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj == null || !(obj is PersonSkills))
{
return false;
}
PersonSkills o = obj as PersonSkills;
return (this.PersonId == o.PersonId
&& this.SkillTypeId == o.SkillTypeId);
}
public override int GetHashCode()
{
int hash = 13;
hash = hash + this.PersonId.GetHashCode();
hash = hash + this.SkillTypeId.GetHashCode();
return hash;
}
}
Mappings: (FluentNhibernate)
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.PersonId);
Map(x => x.Name);
HasMany(x => x.Skills)
.KeyColumn("PersonId")
.Cascade.All();
}
}
public class SkillTypeMap : ClassMap<SkillType>
{
public SkillTypeMap()
{
Id(x => x.SkillTypeId);
Map(x => x.SkillName);
HasMany(x => x.Persons)
.KeyColumn("SkillTypeId")
.Cascade.All();
}
}
public class PersonSkillsMap : ClassMap<PersonSkills>
{
public PersonSkillsMap()
{
CompositeId()
.KeyProperty(x => x.PersonId)
.KeyProperty(x => x.SkillTypeId);
Map(x => x.BaseScore);
Map(x => x.MiscScore);
}
}
Mappings (hbm, generated by FluentNHibernate - I removed output that is not required):
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<class xmlns="urn:nhibernate-mapping-2.2" name="Person" table="Person">
<id name="PersonId" type="int">
<column name="PersonId" />
<generator class="identity" />
</id>
<bag cascade="all" name="Skills" mutable="true">
<key>
<column name="PersonId" />
</key>
<one-to-many class="PersonSkills" />
</bag>
<property name="Name" type="String">
<column name="Name" />
</property>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<class xmlns="urn:nhibernate-mapping-2.2" name="SkillType" table="SkillType">
<id name="SkillTypeId" type="int">
<column name="SkillTypeId" />
<generator class="identity" />
</id>
<bag cascade="all" name="Persons">
<key>
<column name="SkillTypeId" />
</key>
<one-to-many class="PersonSkills" />
</bag>
<property name="SkillName" type="String">
<column name="SkillName" />
</property>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<class xmlns="urn:nhibernate-mapping-2.2" name="PersonSkills" table="PersonSkills">
<composite-id mapped="false" unsaved-value="undefined">
<key-property name="PersonId" type="int">
<column name="PersonId" />
</key-property>
<key-property name="SkillTypeId" type="int">
<column name="SkillTypeId" />
</key-property>
</composite-id>
<property name="BaseScore" type="int">
<column name="BaseScore" />
</property>
<property name="MiscScore" type="int">
<column name="MiscScore" />
</property>
</class>
</hibernate-mapping>
When i map columns from the inspected table, i do this:
<property name="InstanceName" type="MyNameUserType, MyApp.MyNamespace">
<column name="Name"/>
<column name="Name2"/>
</property>
How can I make property mapping initialize a UserType with data retrieved by the formula's sql query?
<property name="InstanceName" type="MyNameUserType, MyApp.MyNamespace" formula="(...)"/>
fails with an exception "wrong number of columns".
Thanks in advance!
MyUserNameType should be a class level mapping so that you can map the result of the SQL function to a class. See these two posts for some possible help:
Class and SQL Function example: http://thoughtspam.spaces.live.com/blog/cns!253515AE06513617!478.entry
NHibernate Mapping with formula mapping example:
http://thoughtspam.spaces.live.com/blog/cns!253515AE06513617!477.entry
I'm the author of the articles referenced by Michael. I had no idea people where still interested and I'm not sure it's applicable with the latest NHibernate.
Here's a fresh link though: http://thoughtspam.wordpress.com/2007/12/19/nhibernate-property-with-formula/
example, using Northwind...
Mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="PropertyFormulaExample.Shipper, PropertyFormulaExample" table="Shippers" lazy="false" >
<id name="ShipperID" column="ShipperID" unsaved-value="0">
<generator class="native" />
</id>
<property name="CompanyName" column="CompanyName" />
<property name="Phone" column="Phone" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="PropertyFormulaExample.Order, PropertyFormulaExample" table="Orders" lazy="false">
<id name="OrderID" column="OrderID" unsaved-value="0">
<generator class="native" />
</id>
<property name="CustomerID" column="CustomerID" />
<property name="ShipVia" type="PropertyFormulaExample.Shipper, PropertyFormulaExample" formula="dbo.GetShipper(shipvia)" />
</class>
</hibernate-mapping>
Entities:
public class Order
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
public Shipper ShipVia { get; set; }
}
public class Shipper : ILifecycle
{
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
#region ILifecycle Members
public LifecycleVeto OnDelete(NHibernate.ISession s)
{
throw new NotImplementedException();
}
public void OnLoad(NHibernate.ISession s, object id)
{
}
public LifecycleVeto OnSave(NHibernate.ISession s)
{
throw new NotImplementedException();
}
public LifecycleVeto OnUpdate(NHibernate.ISession s)
{
throw new NotImplementedException();
}
#endregion
}
And finally the SQL function:
CREATE FUNCTION dbo.GetShipper(#shipperId int)
RETURNS int
AS
BEGIN
RETURN #shipperId
END
Obviously, you’ll want the function to do something meaningful, but the idea is you return the PK for the entity and implement ILifecycle.