-
Notifications
You must be signed in to change notification settings - Fork 0
HowTo Call
Contents
Action contracts (no expected resultset) are invoked with the Call
and CallAsync
methods.
All examples below use the CallAsync
syntax but are equally valid for Call
.
There are two ways to invoke an action contract:
- Ad hoc
- Interface
This is not the preferred Pho/rm way, but it's possible to invoke an action contract by name without creating any additional code objects.
int result = await session.CallAsync("MyContract");
// SQL: EXEC [dbo].[usp_MyContract]
The preferred way to invoke an action contract is through a well-defined contract interface:
interface IMyContract : IPhormContract
{
// No parameters
}
The result is essentially the same:
int result = await session.CallAsync<IMyContract>();
// SQL: EXEC [dbo].[usp_MyContract]
There are two ways to pass parameters to action and data contracts:
- Anonymous
- Entity
The below assumes an action contract like:
CREATE PROCEDURE [dbo].[MyContract] (
@Arg1 INT,
@Arg2 VARCHAR(50) = NULL -- Optional
) AS
SET NOCOUNT ON
-- TODO: logic
RETURN 1
Using contract interface:
interface IMyContract : IPhormContract
{
int Arg1 { get; }
string? Arg2 { get; }
}
Any object that completes the interface, including an anonymous one:
int result = await session.CallAsync<IMyContract>(new { Arg1 = 99 });
// SQL: EXEC [dbo].[usp_MyContract] @Arg1 = 99
A concrete implementation of the interface:
class MyContractImpl : IMyContract
{
public int Arg1 { get; set; }
public string? Arg2 { get; set; }
}
int result = await session.CallAsync<IMyContract>(new MyContractImpl { Arg1 = 99 });
// SQL: EXEC [dbo].[usp_MyContract] @Arg1 = 99
This could be the main entity DTO or a class specifically for use as a contract argument.
For data contracts, this is the same style but using the From<?>(...).Get
format.
The main factor for deciding which to use (apart from personal preference) will likely be any output or result information.
Note that omitting a required parameter or sending the wrong datatype will cause an exception to be thrown.
Combining both of the above sections, a truly ad hoc action contract call could look like:
int result = await session.CallAsync("MyContract", new { Arg1 = 99 });
// SQL: EXEC [dbo].[usp_MyContract] @Arg1 = 99
Action contracts can receive "output" data from the datasource.
CREATE PROCEDURE [dbo].[usp_Action]
@Value INT OUTPUT = NULL
AS
SET NOCOUNT ON
SET @Value = 999
RETURN 1
interface IMyAction : IPhormContract
{
int Value { set; } // "set" means we expect an output value
}
class ActionEntity : IMyAction
{
public int Value { get; set; }
}
var arg = new ActionEntity();
int result = await session.CallAsync<IMyAction>(arg);
// SQL: EXEC [dbo].[usp_Action] @Value OUTPUT
// arg.Value will now be 99
Contract members with both get
and set
will send the existing value and can be updated by the procedure:
interface IMyAction : IPhormContract
{
int Value { get; set; }
}
var arg = new ActionEntity { Value = 50 };
int result = await session.CallAsync<IMyAction>(arg);
// SQL: EXEC [dbo].[usp_Action] @Value OUTPUT = 50
// arg.Value.Value may no longer be 50
Alternatively, an anonymous object can achieve the same using a ContractMember
instance:
var arg = new
{
Value = ContractMember.Out<int>() // Use InOut to also send a value on CallAsync
};
int result = await session.CallAsync<IMyAction>(arg);
// SQL: EXEC [dbo].[usp_Action] @Value OUTPUT
// arg.Value.Value will now be 99
Outgoing contract members can be skipped using IgnoreDataMemberAttribute
:
interface IMyContract : IPhormContract
{
int Id { get; }
[IgnoreDataMember] string Value { get; }
}
int result = await session.CallAsync<IMyContract>(new { Id = 123, Value = "test" });
// SQL: EXEC [dbo].[usp_MyContract] @Id = 123
Members can be renamed using the Name
property of DataMemberAttribute
:
interface IMyContract : IPhormContract
{
[DataMember(Name = "NewId")]
int Id { get; }
}
int result = await session.CallAsync<IMyContract>(new { Id = 123 });
// SQL: EXEC [dbo].[usp_MyContract] @NewId = 123
The EmitDefaultValue
property (default false
) can be set to force default values to be sent on the contract:
interface IMyContract : IPhormContract
{
int Id { get; }
[DataMember(EmitDefaultValue = false)] string? Value1 { get; }
[DataMember(EmitDefaultValue = true)] string? Value2 { get; }
}
int result = await session.CallAsync<IMyContract>(new { Id = 123 });
// SQL: EXEC [dbo].[usp_MyContract] @Id = 123, @Value2 = NULL
Finally, the IsRequired
property (default false
) can be set to cause an exception if the contract is sent incomplete:
interface IMyContract : IPhormContract
{
int Id { get; }
[DataMember(IsRequired = true)] string? Value { get; }
}
int result = await session.CallAsync<IMyContract>(new { Id = 123 });
// MissingMemberException is thrown
Note: Due to the limitations described here, implemented interface members are not recommended and support may be removed in future versions of Pho/rm.
Implemented interface properties will be sent like other contract members, if applied to an implementing class:
interface IMyContract : IPhormContract
{
int Id { get; }
public string Value => "test";
}
class MyEntity : IMyContract
{
public int Id => 123;
}
int result = await session.CallAsync<IMyContract>(new MyEntity());
// SQL: EXEC [dbo].[usp_MyContract] @Id = 123, @Value = 'test'
Unfortunately, because anonymous objects do not implement the interface, the implementation is ignored and the property must be supplied like a normal property:
int result = await session.CallAsync<IMyContract>(new { Id = 123, Value = "new test" });
// SQL: EXEC [dbo].[usp_MyContract] @Id = 123, @Value = 'new test'
For more complex logic, method results can be included to provide additional values using ContractMemberAttribute
, with the same limitation:
interface IMyContract : IPhormContract
{
int Id { get; }
[ContractMember]
public string Value()
{
return "test2";
}
public string OtherValue() => "test3"; // Methods are ignored by default
}
class MyEntity : IMyContract
{
public int Id => 123;
}
int result = await session.CallAsync<IMyContract>(new MyEntity());
// SQL: EXEC [dbo].[usp_MyContract] @Id = 123, @Value = 'test2'
When using anonymous objects, the value can be supplied like a normal property:
int result = await session.CallAsync<IMyContract>(new { Id = 123, Value = "new test" });
// SQL: EXEC [dbo].[usp_MyContract] @Id = 123, @Value = 'new test'
Pho/rm will attempt to send the following types based on property type:
C# Type | MS SQL Type |
---|---|
byte[x] |
BINARY(x) |
DateOnly |
DATE |
DataTime |
DATETIME2 |
Guid |
UNIQUEIDENTIFIER |
enum* |
INT / VARCHAR(x)
|
All other types automatically handled by provider.
Note that most RDBMS implementations will gracefully handle type differences where possible.