Sau khi chúng tôi quyết định chuyển DataAccessLayer của mình sang các thủ tục được lưu trữ và các thủ tục này thường trả về nhiều kết quả được liên kết (ví dụ bên dưới).
Chà, cách tiếp cận của tôi gần như giống nhau, nhưng có thể thoải mái hơn một chút.
Đây là cách mã của bạn có thể trông như thế nào:
using ( var conn = GetConn() )
{
var res = await conn
.StoredProc<Person>( procName, procParams )
.Include<Book>( ( p, b ) => p.Books = b.Where( x => x.PersonId == p.Id ).ToList() )
.Include<Course>( ( p, c ) => p.Courses = c.Where( x => x.PersonId == p.Id ).ToList() )
.Include<Course, Mark>( ( c, m ) => c.Marks = m.Where( x => x.CourseId == c.Id ).ToList() )
.Execute();
}
Hãy phá vỡ nó ...
Sự mở rộng:
public static class SqlExtensions
{
public static StoredProcMapper<T> StoredProc<T>( this SqlConnection conn, string procName, object procParams )
{
return StoredProcMapper<T>
.Create( conn )
.Call( procName, procParams );
}
}
Người vẽ bản đồ:
public class StoredProcMapper<T>
{
public static StoredProcMapper<T> Create( SqlConnection conn )
{
return new StoredProcMapper<T>( conn );
}
private List<MergeInfo> _merges = new List<MergeInfo>();
public SqlConnection Connection { get; }
public string ProcName { get; private set; }
public object Parameters { get; private set; }
private StoredProcMapper( SqlConnection conn )
{
Connection = conn;
_merges.Add( new MergeInfo( typeof( T ) ) );
}
public StoredProcMapper<T> Call( object procName, object parameters )
{
ProcName = procName.ToString();
Parameters = parameters;
return this;
}
public StoredProcMapper<T> Include<TChild>( MergeDelegate<T, TChild> mapper )
{
return Include<T, TChild>( mapper );
}
public StoredProcMapper<T> Include<TParent, TChild>( MergeDelegate<TParent, TChild> mapper )
{
_merges.Add( new MergeInfo<TParent, TChild>( mapper ) );
return this;
}
public async Task<List<T>> Execute()
{
if ( string.IsNullOrEmpty( ProcName ) )
throw new Exception( $"Procedure name not specified! Please use '{nameof(Call)}' method before '{nameof( Execute )}'" );
var gridReader = await Connection.QueryMultipleAsync(
ProcName, Parameters, commandType: CommandType.StoredProcedure );
foreach ( var merge in _merges )
{
merge.Result = gridReader
.Read( merge.Type )
.ToList();
}
foreach ( var merge in _merges )
{
if ( merge.ParentType == null )
continue;
var parentMerge = _merges.FirstOrDefault( x => x.Type == merge.ParentType );
if ( parentMerge == null )
throw new Exception( $"Wrong parent type '{merge.ParentType.FullName}' for type '{merge.Type.FullName}'." );
foreach ( var parent in parentMerge.Result )
{
merge.Merge( parent, merge.Result );
}
}
return _merges
.First()
.Result
.Cast<T>()
.ToList();
}
private class MergeInfo
{
public Type Type { get; }
public Type ParentType { get; }
public IEnumerable Result { get; set; }
public MergeInfo( Type type, Type parentType = null )
{
Type = type;
ParentType = parentType;
}
public void Merge( object parent, IEnumerable children )
{
MergeInternal( parent, children );
}
public virtual void MergeInternal( object parent, IEnumerable children )
{
}
}
private class MergeInfo<TParent, TChild> : MergeInfo
{
public MergeDelegate<TParent, TChild> Action { get; }
public MergeInfo( MergeDelegate<TParent, TChild> mergeAction )
: base( typeof( TChild ), typeof( TParent ) )
{
Action = mergeAction;
}
public override void MergeInternal( object parent, IEnumerable children )
{
Action( (TParent)parent, children.Cast<TChild>() );
}
}
public delegate void MergeDelegate<TParent, TChild>( TParent parent, IEnumerable<TChild> children );
}
Đó là tất cả, nhưng nếu bạn muốn kiểm tra nhanh, đây là mô hình và quy trình dành cho bạn:
Mô hình:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Course> Courses { get; set; }
public List<Book> Books { get; set; }
public override string ToString() => Name;
}
public class Book
{
public Guid Id { get; set; }
public Guid PersonId { get; set; }
public string Name { get; set; }
public override string ToString() => Name;
}
public class Course
{
public Guid Id { get; set; }
public Guid PersonId { get; set; }
public string Name { get; set; }
public List<Mark> Marks { get; set; }
public override string ToString() => Name;
}
public class Mark
{
public Guid Id { get; set; }
public Guid CourseId { get; set; }
public int Value { get; set; }
public override string ToString() => Value.ToString();
}
SP:
if exists (
select *
from sysobjects
where
id = object_id(N'dbo.MultiTest')
and ObjectProperty( id, N'IsProcedure' ) = 1 )
begin
drop procedure dbo.MultiTest
end
go
create procedure dbo.MultiTest
@PersonId UniqueIdentifier
as
begin
declare @tmpPersons table
(
Id UniqueIdentifier,
Name nvarchar(50)
);
declare @tmpBooks table
(
Id UniqueIdentifier,
PersonId UniqueIdentifier,
Name nvarchar(50)
)
declare @tmpCourses table
(
Id UniqueIdentifier,
PersonId UniqueIdentifier,
Name nvarchar(50)
)
declare @tmpMarks table
(
Id UniqueIdentifier,
CourseId UniqueIdentifier,
Value int
)
insert into @tmpPersons
values
( '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Иван' ),
( '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Василий' ),
( '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Алефтина' )
insert into @tmpBooks
values
( NewId(), '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Книга Математика' ),
( NewId(), '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Книга Физика' ),
( NewId(), '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Книга Геометрия' ),
( NewId(), '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Книга Биология' ),
( NewId(), '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Книга Химия' ),
( NewId(), '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Книга История' ),
( NewId(), '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Книга Литература' ),
( NewId(), '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Книга Древне-шумерский диалект иврита' )
insert into @tmpCourses
values
( '30945b68-a6ef-4da8-9a35-d3b2845e7de3', '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Математика' ),
( '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Физика' ),
( '92bbefd1-9fec-4dc7-bb58-986eadb105c8', '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Геометрия' ),
( '923a2f0c-c5c7-4394-847c-c5028fe14711', '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Биология' ),
( 'ace50388-eb05-4c46-82a9-5836cf0c988c', '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Химия' ),
( '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'История' ),
( '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Литература' ),
( '73ac366d-c7c2-4480-9513-28c17967db1a', '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Древне-шумерский диалект иврита' )
insert into @tmpMarks
values
( NewId(), '30945b68-a6ef-4da8-9a35-d3b2845e7de3', 98 ),
( NewId(), '30945b68-a6ef-4da8-9a35-d3b2845e7de3', 87 ),
( NewId(), '30945b68-a6ef-4da8-9a35-d3b2845e7de3', 76 ),
( NewId(), '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', 89 ),
( NewId(), '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', 78 ),
( NewId(), '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', 67 ),
( NewId(), '92bbefd1-9fec-4dc7-bb58-986eadb105c8', 79 ),
( NewId(), '92bbefd1-9fec-4dc7-bb58-986eadb105c8', 68 ),
( NewId(), '92bbefd1-9fec-4dc7-bb58-986eadb105c8', 75 ),
( NewId(), '923a2f0c-c5c7-4394-847c-c5028fe14711', 198 ),
( NewId(), '923a2f0c-c5c7-4394-847c-c5028fe14711', 187 ),
( NewId(), '923a2f0c-c5c7-4394-847c-c5028fe14711', 176 ),
( NewId(), 'ace50388-eb05-4c46-82a9-5836cf0c988c', 189 ),
( NewId(), 'ace50388-eb05-4c46-82a9-5836cf0c988c', 178 ),
( NewId(), 'ace50388-eb05-4c46-82a9-5836cf0c988c', 167 ),
( NewId(), '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', 8 ),
( NewId(), '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', 7 ),
( NewId(), '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', 6 ),
( NewId(), '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', 9 ),
( NewId(), '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', 8 ),
( NewId(), '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', 7 ),
( NewId(), '73ac366d-c7c2-4480-9513-28c17967db1a', 9 ),
( NewId(), '73ac366d-c7c2-4480-9513-28c17967db1a', 8 ),
( NewId(), '73ac366d-c7c2-4480-9513-28c17967db1a', 5 )
select * from @tmpPersons
select * from @tmpBooks
select * from @tmpCourses
select * from @tmpMarks
end
go