Skip to content

The Sample

Vadim Loboda edited this page Feb 11, 2018 · 1 revision

Let's say we have database tables Users and UserRoles (TSQL):

create table dbo.Users
(
    Id       int           not null	  identity primary key clustered,
    [Login]  varchar(20)   not null	  ,
    Name     nvarchar(50)  not null	  ,
    Email    varchar(50)   not null
);

create table dbo.UserRoles
(
    UserId   in       not null   foreign key (UserId) references dbo.Users (Id),	
    RoleId   tinyint  not null   foreign key (RoleId) references dbo.Roles (Id),

    primary key clustered (UserId, RoleId)
);

// The Roles table is just a dictionary or lookup table and users don't edit it.

And we have a User class (C#):

public class User
{
    public Int32   Id       { get; set; }
    public String  Login    { get; set; }
    public String  Name     { get; set; }
    public String  Email    { get; set; }
    public Byte[]  RoleIds  { get; set; }
}

In order to read and save the User data in Artisan.Orm way it is required to create:

  • mapper static vlass (C#)
  • user-defined table type (TSQL)
  • stored procedures (TSQL)

Mapper static class (C#) is decorated with MapperFor attribute and consists of four static methods with reserved names:

  • CreateObject
  • CreateObjectRow
  • CreateDataTable
  • CreateDataRow
[MapperFor(typeof(User)]
public static class UserMapper 
{
    public static User CreateObject(SqlDataReader dr)
    {
        var i = 0;

        return new User 
        {
            Id     =  dr.GetInt32   (i)   ,
            Login  =  dr.GetString  (++i) ,
            Name   =  dr.GetString  (++i) ,
            Email  =  dr.GetString  (++i)
        };
    }

    public static ObjectRow CreateObjectRow(SqlDataReader dr)
    {
        var i = 0;

        return new ObjectRow(4)
        {
            /* 0 - Id      =  */  dr.GetInt32   (i)   ,
            /* 1 - Login   =  */  dr.GetString  (++i) ,
            /* 2 - Name    =  */  dr.GetString  (++i) ,
            /* 3 - Email   =  */  dr.GetString  (++i)
        };
    }

    public static DataTable CreateDataTable()
    {
        return new DataTable("UserTableType")

            .AddColumn< Int32  >( "Id"    )
            .AddColumn< String >( "Login" )
            .AddColumn< String >( "Name"  )
            .AddColumn< String >( "Email" );
    }

    public static Object[] CreateDataRow(User obj)
    {
        if (obj.Id == 0) 
            obj.Id = Int32NegativeIdentity.Next;

        return new object[]
        {
            obj.Id     ,
            obj.Login  ,
            obj.Name   ,
            obj.Email
        };
    }
}

User-defined table type (TSQL):

create type dbo.UserTableType as table
(
    Id       int           not null   primary key clustered,
    [Login]  varchar(20)   not null   ,
    Name     nvarchar(50)  not null   ,
    Email    varchar(50)   not null
);

Stored procedures (TSQL):

create procedure dbo.GetUserById
    @Id    int
as
begin
    set nocount on;

    -- read User

    select
        Id       ,
        [Login]  ,
        Name     ,
        Email
    from
        dbo.Users
    where
        Id = @Id;
        
    -- read User RoleIds
        
    select
        RoleId
    from
        dbo.UserRoles
    where
        UserId = @Id;
        
end;
create procedure dbo.SaveUser
    @User     dbo.UserTableType       readonly,
    @RoleIds  dbo.TinyIntIdTableType  readonly
as
begin
    set nocount on;
    
    declare @UserIds table ( InsertedId int primary key, ParamId int unique);

    begin -- save User

        merge into dbo.Users as target
            using 
            (
                select
                    Id       ,
                    [Login]  ,
                    Name     ,
                    Email
                from
                    @User
            ) 
            as source on source.Id = target.Id

        when matched then
            update set
                [Login]  =  source.[Login]  ,
                Name     =  source.Name     ,
                Email    =  source.Email    

        when not matched by target then                                                         
            insert (    
                [Login]  , 
                Name     ,
                Email    )
            values (
                source.[Login]  , 
                source.Name     , 
                source.Email    )
    
        output          inserted.Id , source.Id
        into @UserIds ( InsertedId  , ParamId   );

    end; 

    begin -- save UserRoles
            
        merge into dbo.UserRoles as target
            using 
            (
                select
                    UserId  =  ids.InsertedId,
                    RoleId  =  r.Id
                from 
                    @User u
                    inner join @UserIds ids on ids.ParamId = u.Id
                    cross join @RoleIds r
            )
            as source on source.UserId = target.UserId and source.RoleId = target.RoleId

        when not matched by target then                                                         
            insert (    
                UserId  ,
                RoleId  )
            values (
                source.UserId  ,
                source.RoleId  )

        when not matched by source and target.UserId in (select InsertedId from @UserIds) then    
            delete;
    end;

end;

Having prepared mapper, user-defined table type and stored procedures we can write the UserRepository (C#):

public class UserRepository: Artisan.Orm.RepositoryBase
{
    public User GetById(int id)
    {
        return GetByCommand(cmd =>
        {
            cmd.UseProcedure("dbo.GetUserById");

            cmd.AddIntParam("@Id", id);

            return cmd.GetByReader(reader =>
            {
                var user     = reader.ReadTo<User>();
                user.RoleIds = reader.ReadToArray<byte>();            
                
                return user;
            });

        });
    }

    public void Save(User user)
    {
        ExecuteCommand(cmd =>
        {
            cmd.UseProcedure("dbo.SaveUser");

            cmd.AddTableParam( "@User"    , user         );
            cmd.AddTableParam( "@RoleIds" , user.RoleIds );
        });
    }
}

More examples of the Artisan.Orm usage are available in the Tests and Database projects.

Clone this wiki locally