5

Я занимаюсь разработкой службы с использованием WebApi2 и EntityFramework6. У меня есть устаревшая база данных SQLServer, с которой моя служба должна работать.Как вызвать хранимую процедуру из EntityFramework 6 с параметром 'hierarchyid'

Эта БД сильно использует тип данных «hierarchyid», и этот тип используется внутри хранимых процедур БД.

Кажется, что EF6 не поддерживает тип данных «hierarchyid», поэтому я использовал this fork, который добавляет поддержку «hierarchyid».

Хотя извлечение из БД отлично работает с типом «иерархии», моя проблема связана с хранимыми процедурами, для которых в качестве параметра требуется «hierarchyid».

Хранимая процедура выглядит следующим образом:

CREATE PROCEDURE [dbo].[GetSomethingByNodeId] 
    (
     @startingRoot HIERARCHYID 
     ,@return HIERARCHYID OUTPUT 
    ) 

Мой клиент код для вызова этой хранимой процедуры выглядит следующим образом:

var param1 = new SqlParameter("@startingRoot", new HierarchyId("/")); 
var param2 = new SqlParameter{ ParameterName = "@return", Value = 0, Direction = ParameterDirection.Output }; 

var obj = context.Database.SqlQuery<HierarchyId>("GetSomethingByNodeId" @startingRoot, @return out", param1, param2).ToList(); 

Но, к сожалению, называя этот запрос генерирует исключение, которое говорит:

An unhandled exception of type 'System.ArgumentException' occurred in EntityFramework.SqlServer.dll 

Additional information: No mapping exists from object type System.Data.Entity.Hierarchy.HierarchyId to a known managed provider native type. 

Любые идеи о том, как я могу сделать эту работу?

+0

Выстрел в темноте здесь ... Можете ли вы изменить Sproc, за исключением Nvarchar, а затем перевести значение в hirachyId внутри Sproc? – SimonGates

ответ

3

К сожалению, MetaType.GetMetaTypeFromValue не позволяет добавлять типы (все поддерживаемые типы жестко запрограммированы). Я думаю, что вы можете достичь своей цели с помощью параметров и преобразований nvarchar.

В вашем C# код:

var param1 = new SqlParameter("@startingRoot", "/1/"); 
var param2 = new SqlParameter { ParameterName = "@return", Value = "", Size = 1000, Direction = ParameterDirection.Output }; 

var ids = context.Database.SqlQuery<HierarchyId>("GetSomethingByNodeId @startingRoot, @return out", param1, param2).ToList(); 
var returnedId = new HierarchyId(param2.Value.ToString()); 

В вашей процедуре (я написал некоторый тестовый код внутри):

CREATE PROCEDURE [dbo].[GetSomethingByNodeId] 
    (
     @startingRoot nvarchar(max), @return nvarchar(max) OUTPUT 
    ) 
as 
declare @hid hierarchyid = hierarchyid::Parse('/1/') 
select @return = @hid.ToString() 

declare @root hierarchyid = hierarchyid::Parse(@startingRoot) 
select @root as field 

Кроме того, вы можете попробовать использовать Microsoft.SqlServer.Types и `SqlHierarchyId` так:

var sqlHierarchyId = SqlHierarchyId.Parse("/"); 
var param1 = new SqlParameter("@startingRoot", sqlHierarchyId) { UdtTypeName = "HierarchyId" }; 

Но, я думаю, это неправильное направление.

1

Ответ Олега правильный, иерархия до сих пор не очень хорошо интегрирована в EF, и вы должны работать со строками в .net. Вот еще один подход, который был использован с первых дней hierarchyid` в тип данных:

хранимых процедур:

CREATE PROCEDURE GetSomethingByNodeId 
    @startingRoot hierarchyid, -- you don't need to use nvarchar here. String which will come from the application will be converted to hierarchyId implicitly 
    @return nvarchar(500) OUTPUT 
AS 
BEGIN 
SELECT @return = @startingRoot.GetAncestor(1).ToString(); 

END

В приложении вы добавляете частичный класс для контекста данных EF с вызов SP с использованием простого старого ADO.NET.Возможно, вы будете писать этот другой путь или использовать щеголеватый вместо этого, но основная идея здесь проходит параметр в виде строки в SQL Server, и он будет конвертировать в hierarchyid` в неявном:

public partial class TestEntities 
{ 
    public string GetSomethingByNodeId(string startingRoot) 
    { 
     using (var connection = new SqlConnection(this.Database.Connection.ConnectionString)) 
     { 
      var command = new SqlCommand("GetSomethingByNodeId", connection); 
      command.CommandType = CommandType.StoredProcedure; 
      command.Parameters.AddWithValue("@startingRoot", startingRoot); 
      var outParameter = new SqlParameter("@return", SqlDbType.NVarChar, 500); 
      outParameter.Direction = ParameterDirection.Output; 
      command.Parameters.Add(outParameter); 
      connection.Open(); 
      command.ExecuteNonQuery(); 

      return outParameter.Value.ToString(); 
     } 
    } 
} 

Затем этот метод вызывается как любой другой хранится процедура с помощью контекста EF:

using (var context = new TestEntities()) 
{ 
    var s = context.GetSomethingByNodeId("/1/1.3/"); 
} 

UPD: вот как метод расширения для устаревших hierarchyid` в вызове процедуры будет выглядеть с Dapper (как для меня это выглядит гораздо лучше, чем простой ADO.NET):

public string GetSomethingByNodeId(string startingRoot) 
     { 
      using (var connection = new SqlConnection(this.Database.Connection.ConnectionString)) 
      { 
       var parameters = new DynamicParameters(); 
       parameters.Add("startingRoot", startingRoot); 
       parameters.Add("return", null, DbType.String, ParameterDirection.Output, 500); 
       connection.Open(); 
       connection.Execute("GetSomethingByNodeId", parameters, commandType: CommandType.StoredProcedure); 

       return parameters.Get<string>("return"); 
      } 
     }