For example, when INSERTing rows into both SESSION_MST (for UserSessionDto), and LOGIN_TRN_MST (for LoginDto), the following attribute decoration will result in an exception because Dapper.SimpleSave will try to insert the parent record before the child:
[Table("[user].SESSION_MST")]
public class UserSessionDto
{
[PrimaryKey]
public int? SessionKey { get; set; }
public DateTimeOffset StartDateTime { get; set; }
public DateTimeOffset? EndDateTime { get; set; }
[OneToOne]
[Column("LoginKey")]
public LoginDto Login { get; set; }
}
For reference, LoginDto is defined as follows:
[Table("[user].LOGIN_TRN")]
public class LoginDto
{
[PrimaryKey]
public int? LoginKey { get; set; }
[OneToOne, Column("UserKey")]
[SimpleSaveIgnore]
[JsonIgnore]
[XmlIgnore]
public UserDto User { get; set; }
public int UserKey { get; set; }
[OneToOne, Column("AllowedIPKey")]
public AllowedIpDto AllowedIp { get; set; }
public int? AllowedIPKey { get; set; }
public UserLoginResultEnum LoginResultKey { get; set; }
public string UnknownIp { get; set; }
}
At the moment Dapper.SimpleSave doesn't correctly handle the relationship by inserting the LoginDto row first. Instead, as already stated, it will try to insert the SessionDto row.
However the LoginDto row must be inserted first because, in this case, the foreign key column is defined in SESSION_MST and cannot contain NULLs:
CREATE TABLE [user].[SESSION_MST]
(
SessionKey INT NOT NULL IDENTITY(0,1)
CONSTRAINT [PK_SESSION_KEY] PRIMARY KEY,
[StartDateTime] DATETIMEOFFSET(4) NOT NULL,
[EndDateTime] DATETIMEOFFSET(4) NULL,
[LoginKey] INT NOT NULL INDEX [IX_SESSION_MST_LOGINKEY] NONCLUSTERED,
CONSTRAINT [FK_SESSION_MST_LOGIN_TRN] FOREIGN KEY ([LoginKey]) REFERENCES [user].[LOGIN_TRN]([LoginKey])
)
In any case, inserting in LOGIN_TRN first would be cleaner because it results in only a single INSERT for the SESSION_MST, rather than an INSERT, followed by an UPDATE after the LOGIN_TRN INSERT.
This can be made to work properly now, but it requires a [ForeignKeyReference] attribute on the Login property. This forces Dapper.SimpleSave to correctly handle the relationship, and it will first INSERT into LOGIN_TRN, then into SESSION_MST. Here's the code:
[Table("[user].SESSION_MST")]
public class UserSessionDto
{
[PrimaryKey]
public int? SessionKey { get; set; }
public DateTimeOffset StartDateTime { get; set; }
public DateTimeOffset? EndDateTime { get; set; }
[OneToOne]
[ForeignKeyReference(typeof(LoginDto))] // Note the extra attribute
[Column("LoginKey")]
public LoginDto Login { get; set; }
}
Dapper.SimpleSave should be able to infer any information it needs from the return type of the property without requiring an explicit (re-)statement using the [ForeignKeyReference] attribute.
For example, when
INSERTing rows into bothSESSION_MST(forUserSessionDto), andLOGIN_TRN_MST(forLoginDto), the following attribute decoration will result in an exception because Dapper.SimpleSave will try to insert the parent record before the child:For reference,
LoginDtois defined as follows:At the moment Dapper.SimpleSave doesn't correctly handle the relationship by inserting the
LoginDtorow first. Instead, as already stated, it will try to insert theSessionDtorow.However the
LoginDtorow must be inserted first because, in this case, the foreign key column is defined inSESSION_MSTand cannot containNULLs:CREATE TABLE [user].[SESSION_MST] ( SessionKey INT NOT NULL IDENTITY(0,1) CONSTRAINT [PK_SESSION_KEY] PRIMARY KEY, [StartDateTime] DATETIMEOFFSET(4) NOT NULL, [EndDateTime] DATETIMEOFFSET(4) NULL, [LoginKey] INT NOT NULL INDEX [IX_SESSION_MST_LOGINKEY] NONCLUSTERED, CONSTRAINT [FK_SESSION_MST_LOGIN_TRN] FOREIGN KEY ([LoginKey]) REFERENCES [user].[LOGIN_TRN]([LoginKey]) )In any case, inserting in
LOGIN_TRNfirst would be cleaner because it results in only a singleINSERTfor theSESSION_MST, rather than anINSERT, followed by anUPDATEafter theLOGIN_TRNINSERT.This can be made to work properly now, but it requires a
[ForeignKeyReference]attribute on theLoginproperty. This forces Dapper.SimpleSave to correctly handle the relationship, and it will firstINSERTintoLOGIN_TRN, then intoSESSION_MST. Here's the code:Dapper.SimpleSave should be able to infer any information it needs from the return type of the property without requiring an explicit (re-)statement using the
[ForeignKeyReference]attribute.