Jak wywołać procedurę składowaną w Entity Framework 6 (najpierw kod)?

259

Jestem bardzo nowy w Entity Framework 6 i chcę wdrożyć procedury składowane w moim projekcie. Mam procedurę składowaną w następujący sposób:

ALTER PROCEDURE [dbo].[insert_department]
    @Name [varchar](100)
AS
BEGIN
    INSERT [dbo].[Departments]([Name])
    VALUES (@Name)

    DECLARE @DeptId int

    SELECT @DeptId = [DeptId]
    FROM [dbo].[Departments]
    WHERE @@ROWCOUNT > 0 AND [DeptId] = SCOPE_IDENTITY()

    SELECT t0.[DeptId]
    FROM [dbo].[Departments] AS t0
    WHERE @@ROWCOUNT > 0 AND t0.[DeptId] = @DeptId
END

Department klasa:

public class Department
{
    public int DepartmentId { get; set; }       
    public string Name { get; set; }
}

modelBuilder 
.Entity<Department>() 
.MapToStoredProcedures(s => 
s.Update(u => u.HasName("modify_department") 
               .Parameter(b => b.Department, "department_id") 
               .Parameter(b => b.Name, "department_name")) 
 .Delete(d => d.HasName("delete_department") 
               .Parameter(b => b.DepartmentId, "department_id")) 
 .Insert(i => i.HasName("insert_department") 
               .Parameter(b => b.Name, "department_name")));

protected void btnSave_Click(object sender, EventArgs e)
{
    string department = txtDepartment.text.trim();

    // here I want to call the stored procedure to insert values
}

Mój problem brzmi: jak mogę wywołać procedurę składowaną i przekazać do niej parametry?

Jaan
źródło
Ja też chcę to wiedzieć. Idealnie byłoby całkowicie pominąć EF i uruchomić WSZYSTKO, używając jedynie procedur przechowywanych. Jestem ekspertem w SQL, ale wdrożenie EF jest bardzo frustrujące.
David Britz

Odpowiedzi:

247

Możesz wywołać procedurę przechowywaną w swojej DbContextklasie w następujący sposób.

this.Database.SqlQuery<YourEntityType>("storedProcedureName",params);

Ale jeśli procedura składowana zwraca wiele zestawów wyników jako przykładowy kod, możesz zobaczyć ten pomocny artykuł na MSDN

Procedury przechowywane z wieloma zestawami wyników

Alborz
źródło
2
Dzięki @Alborz. czy możesz podać mi najpierw linki dotyczące różnych implementacji procedury składowanej w Entity Framework 6 Code First. Szukałem wszędzie w Internecie, ale nie dostałem żadnego artykułu, w którym mogę bezpośrednio wywołać procedurę składowaną dla parametrów IN i OUT. Dzięki za cenny czas.
Jaan
2
Ten artykuł może być pomocny blogs.msdn.com/b/diego/archive/2012/01/10/…
Alborz
8
To nie wydaje się działać z parametrami. Wydaje się, że należy jawnie wymienić parametry jako część zapytania.
Mark
6
Tak, musisz podać parametry jako część zapytania - "storedProcedureName @param1, @param2". Również typ paramsjest System.Data.SqlClient.SqlParameter[].
Styl Oppa Gingham
6
this.Database.SqlQuery<YourEntityType>("storedProcedureName @param1", new System.Data.SqlClient.SqlParameter("@param1", YourParam));
Ppp
152

Wszystko, co musisz zrobić, to utworzyć obiekt, który ma takie same nazwy właściwości jak wyniki zwrócone przez procedurę przechowywaną. Dla następującej procedury składowanej:

    CREATE PROCEDURE [dbo].[GetResultsForCampaign]  
    @ClientId int   
    AS
    BEGIN
    SET NOCOUNT ON;

    SELECT AgeGroup, Gender, Payout
    FROM IntegrationResult
    WHERE ClientId = @ClientId
    END

utwórz klasę, która wygląda następująco:

    public class ResultForCampaign
    {
        public string AgeGroup { get; set; }

        public string Gender { get; set; }

        public decimal Payout { get; set; }
    }

a następnie wywołaj procedurę, wykonując następujące czynności:

    using(var context = new DatabaseContext())
    {
            var clientIdParameter = new SqlParameter("@ClientId", 4);

            var result = context.Database
                .SqlQuery<ResultForCampaign>("GetResultsForCampaign @ClientId", clientIdParameter)
                .ToList();
    }

Wynik będzie zawierał listę ResultForCampaignobiektów. Możesz zadzwonić, SqlQueryużywając dowolnej liczby parametrów.

Filipe Leite
źródło
2
W wyjątkowych sytuacjach działałoby to świetnie. Uważam, że definicja SProc powinna być ściśle powiązana z klasą, która dziedziczy po DBContext, zamiast na „polach pszenicy” produktu.
GoldBishop
50

Rozwiązałem to za pomocą ExecuteSqlCommand

Umieść własną metodę, taką jak moja, w DbContext jako własne wystąpienia:

public void addmessage(<yourEntity> _msg)
{
    var date = new SqlParameter("@date", _msg.MDate);
    var subject = new SqlParameter("@subject", _msg.MSubject);
    var body = new SqlParameter("@body", _msg.MBody);
    var fid = new SqlParameter("@fid", _msg.FID);
    this.Database.ExecuteSqlCommand("exec messageinsert @Date , @Subject , @Body , @Fid", date,subject,body,fid);
}

więc możesz mieć w swoim kodzie taką metodę:

[WebMethod] //this method is static and i use web method because i call this method from client side
public static void AddMessage(string Date, string Subject, string Body, string Follower, string Department)
{
    try
    {
        using (DBContext reposit = new DBContext())
        {
            msge <yourEntity> Newmsg = new msge();
            Newmsg.MDate = Date;
            Newmsg.MSubject = Subject.Trim();
            Newmsg.MBody = Body.Trim();
            Newmsg.FID= 5;
            reposit.addmessage(Newmsg);
        }
    }
    catch (Exception)
    {
        throw;
    }
}

to jest mój SP:

Create PROCEDURE dbo.MessageInsert

    @Date nchar["size"],
    @Subject nchar["size"],
    @Body nchar["size"],
    @Fid int
AS
    insert into Msg (MDate,MSubject,MBody,FID) values (@Date,@Subject,@Body,@Fid)
    RETURN

nadzieja ci pomogła

Mahdi Ghafoorian
źródło
2
Musisz podać długość parametrów nchar do procedury składowanej - w przeciwnym razie, jak zauważyłeś, mają one tylko jeden znak.
Dave W
@Mahdighafoorian To bardzo przydatna odpowiedź, dziękuję bardzo! :)
Komengem
Ta składnia nie wymaga modyfikacji kolejności parametrów SProc, innymi słowy Pozycjonowanie porządkowe.
GoldBishop
21

Korzystając z Twojego przykładu, oto dwa sposoby na osiągnięcie tego:

1 - Użyj mapowania procedur składowanych

Pamiętaj, że ten kod będzie działał z mapowaniem lub bez. Jeśli wyłączysz mapowanie na encji, EF wygeneruje instrukcję insert + select.

protected void btnSave_Click(object sender, EventArgs e)
{
     using (var db = DepartmentContext() )
     {
        var department = new Department();

        department.Name = txtDepartment.text.trim();

        db.Departments.add(department);
        db.SaveChanges();

        // EF will populate department.DepartmentId
        int departmentID = department.DepartmentId;
     }
}

2 - Wywołaj procedurę przechowywaną bezpośrednio

protected void btnSave_Click(object sender, EventArgs e)
{
     using (var db = DepartmentContext() )
     {
        var name = new SqlParameter("@name", txtDepartment.text.trim());

        //to get this to work, you will need to change your select inside dbo.insert_department to include name in the resultset
        var department = db.Database.SqlQuery<Department>("dbo.insert_department @name", name).SingleOrDefault();

       //alternately, you can invoke SqlQuery on the DbSet itself:
       //var department = db.Departments.SqlQuery("dbo.insert_department @name", name).SingleOrDefault();

        int departmentID = department.DepartmentId;
     }
}

Zalecam zastosowanie pierwszego podejścia, ponieważ można bezpośrednio pracować z obiektem departamentu i nie trzeba tworzyć wiązki obiektów SqlParameter.

Brian Vander Plaats
źródło
3
Bądź ostrożny, to drugi przykład, że zmiana nie jest śledzona przez dbContext
edtruant 19.04.2016
EDYCJA Użyj zamiast tego System.Data.Entity.DbSet <TEntity> .SqlQuery (String, Object []).
edtruant
@edtruant Wygląda na to, że dbContext śledzi zmianę. Aby przetestować, spojrzałem na db. <DbSet> .Count () przed i po instrukcji insert. W obu metodach liczba wzrosła o jeden. Dla kompletności dodałem alternatywną metodę do przykładu.
Brian Vander Plaats
1
W pierwszym przykładzie nie widzę żadnego odniesienia do procedury przechowywanej.
xr280xr
2
@ xr280xr odwołanie do insert_department znajduje się w wyrażeniu modelBuilder w pytaniu PO. Jest to zaleta polegająca na odwzorowaniu rzeczy w ten sposób, ponieważ skutecznie działa tak samo, jakbyś pozwalał EF generować instrukcje wstawiania / aktualizacji / usuwania
Brian Vander Plaats
15

Używasz, MapToStoredProcedures()co wskazuje, że mapujesz swoje byty na procedury składowane, robiąc to, musisz uwolnić się od faktu, że istnieje procedura składowana i użyć jej contextjak zwykle. Coś takiego ( zapisane w przeglądarce, więc nie przetestowane )

using(MyContext context = new MyContext())
{
    Department department = new Department()
    {
        Name = txtDepartment.text.trim()
    };
    context.Set<Department>().Add(department);
}

Jeśli wszystko, co naprawdę próbujesz zrobić, to bezpośrednio wywołać procedurę składowaną, użyj SqlQuery

qujck
źródło
2
Dzięki, qujck. Ale chcę użyć procedury składowanej. Podałem tylko przykładowy kod dla wygodnego zrozumienia.
Jaan
4
@Jaan - Kod powyżej będzie użyć procedury. Czy masz na myśli, że chcesz bezpośrednio wywołać procedurę przechowywaną?
qujck
tak. Czy możesz mi powiedzieć, która droga jest lepsza. Bezpośrednie wywołanie procedury składowanej lub podanego powyżej kodu?
Jaan
6
@Jaan użyj kodu, który pokazałem - ORM ma ukryć podstawową implementację - użycie powyższego kodu gwarantuje, że reszta kodu nie będzie mieć znaczenia, czy jest to procedura przechowywana, czy nie. Możesz nawet zmienić mapowanie modelu na inną procedurę przechowywaną lub nie być procedurą przechowywaną bez zmiany czegokolwiek innego.
qujck
4
@ Chazt3n Pytanie pokazuje procedury składowane konfigurowane z linii .MapToStoredProcedures(s => . Wezwanie do Addpowinno zostać rozwiązane.Insert(i => i.HasName("insert_department")
qujck 16.04.15
12

Możesz teraz także użyć utworzonej przeze mnie konwencji, która umożliwia wywoływanie procedur przechowywanych (w tym procedur zwracających wiele zestawów wyników), TVF i skalarnych UDF natywnie z EF.

Do momentu wydania Entity Framework 6.1 funkcje składowania (tj. Funkcje wycenione w tabeli i Procedury przechowywane) mogły być używane w EF tylko podczas wykonywania bazy danych w pierwszej kolejności. Było kilka obejść, które umożliwiły wywoływanie funkcji sklepu w aplikacjach Code First, ale nadal nie można było używać TVF w zapytaniach Linq, co było jednym z największych ograniczeń. W EF 6.1 API mapowania zostało upublicznione, co (wraz z kilkoma dodatkowymi poprawkami) umożliwiło korzystanie z funkcji sklepu w aplikacjach Code First.

Czytaj więcej

Naciskałem dość mocno przez ostatnie dwa tygodnie i oto ona - wersja beta konwencji, która umożliwia korzystanie z funkcji sklepu (tj. Procedur przechowywanych, funkcji wycenianych w tabeli itp.) W aplikacjach, które używają podejścia Code First i Entity Framework 6.1.1 ( lub nowsze). Jestem bardziej niż zadowolony z poprawek i nowych funkcji zawartych w tym wydaniu.

Czytaj dalej .

Paweł
źródło
Właściwie od 4.0 można było wykonywać SProcs bez Modelu. Musisz wykonać instrukcje Raw SQL zamiast właściwości object. Nawet w wersji 6.1.x musisz użyć SqlQuery <T> lub ExecuteSqlCommand, aby uzyskać podobny efekt.
GoldBishop
10
object[] xparams = {
            new SqlParameter("@ParametterWithNummvalue", DBNull.Value),
            new SqlParameter("@In_Parameter", "Value"),
            new SqlParameter("@Out_Parameter", SqlDbType.Int) {Direction = ParameterDirection.Output}};

        YourDbContext.Database.ExecuteSqlCommand("exec StoreProcedure_Name @ParametterWithNummvalue, @In_Parameter, @Out_Parameter", xparams);
        var ReturnValue = ((SqlParameter)params[2]).Value;  
Shiraj Momin
źródło
1
params to identyfikator używający innej nazwy.
yogihosting
2
Funkcja SaveChanges () tutaj nie jest konieczna. Zmiany są zatwierdzane podczas wywołania ExecuteSqlCommand ().
Xavier Poinas,
10

Działa to dla mnie, wyciągając dane z procedury składowanej podczas przekazywania parametru.

var param = new SqlParameter("@datetime", combinedTime);
var result = 
        _db.Database.SqlQuery<QAList>("dbo.GetQAListByDateTime @datetime", param).ToList();

_db to dbContext

Tom Stickel
źródło
9

Spójrz na ten link, który pokazuje, jak działa mapowanie EF 6 z procedurami przechowywanymi, aby wstawić, zaktualizować i usunąć: http://msdn.microsoft.com/en-us/data/dn468673

Dodanie

Oto świetny przykład wywołania procedury składowanej z Code First:

Powiedzmy, że musisz wykonać procedurę składowaną z jednym parametrem, a procedura składowana zwraca zestaw danych pasujących do stanów encji, więc otrzymamy:

var countryIso = "AR"; //Argentina

var statesFromArgentina = context.Countries.SqlQuery(
                                      "dbo.GetStatesFromCountry @p0", countryIso
                                                    );

Powiedzmy teraz, że chcemy wykonać inną procedurę składowaną z dwoma parametrami:

var countryIso = "AR"; //Argentina
var stateIso = "RN"; //Río Negro

var citiesFromRioNegro = context.States.SqlQuery(
                            "dbo.GetCitiesFromState @p0, @p1", countryIso, stateIso
                          );

Zauważ, że używamy nazewnictwa opartego na indeksie dla parametrów. Wynika to z faktu, że Entity Framework zawinie te parametry jako obiekty DbParameter, aby uniknąć problemów z wstrzykiwaniem SQL.

Mam nadzieję, że ten przykład pomoże!

Gabriel Andrés Brancolini
źródło
6
public IList<Models.StandardRecipeDetail> GetRequisitionDetailBySearchCriteria(Guid subGroupItemId, Guid groupItemId)
{
    var query = this.UnitOfWork.Context.Database.SqlQuery<Models.StandardRecipeDetail>("SP_GetRequisitionDetailBySearchCriteria @SubGroupItemId,@GroupItemId",
    new System.Data.SqlClient.SqlParameter("@SubGroupItemId", subGroupItemId),
    new System.Data.SqlClient.SqlParameter("@GroupItemId", groupItemId));
    return query.ToList();
}
Md. Delower Hossain
źródło
4

Najpierw działa dla mnie w kodzie. Zwraca listę z pasującą właściwością modelu widoku (StudentChapterCompletionViewModel)

var studentIdParameter = new SqlParameter
{
     ParameterName = "studentId",
     Direction = ParameterDirection.Input,
     SqlDbType = SqlDbType.BigInt,
     Value = studentId
 };

 var results = Context.Database.SqlQuery<StudentChapterCompletionViewModel>(
                "exec dbo.sp_StudentComplettion @studentId",
                 studentIdParameter
                ).ToList();

Zaktualizowano dla kontekstu

Kontekst jest instancją klasy, która dziedziczy DbContext jak poniżej.

public class ApplicationDbContext : DbContext
{
    public DbSet<City> City { get; set; }
}

var Context = new  ApplicationDbContext();
reza.cse08
źródło
Cześć, nie mogę znaleźć tego Context.Database.SqlQuery <Model>, gdzie mogę to zrobić Context.TableName.SqlQuery (ProcName). co daje mi problemy
Marshall
@Marshall, być może używasz pierwszego projektu bazy danych. sprawdź ten link stackoverflow.com/questions/11792018/…
reza.cse08
1

Bezmyślny pasażer ma projekt, który umożliwia zwrócenie wielu zestawów wyników z przechowywanego proc przy użyciu frameworku encji. Jeden z jego przykładów poniżej ....

using (testentities te = new testentities())
{
    //-------------------------------------------------------------
    // Simple stored proc
    //-------------------------------------------------------------
    var parms1 = new testone() { inparm = "abcd" };
    var results1 = te.CallStoredProc<testone>(te.testoneproc, parms1);
    var r1 = results1.ToList<TestOneResultSet>();
}
Dib
źródło
1

Możesz przekazać parametry sp_GetByIdi pobrać wyniki w ToList()lubFirstOrDefault();

var param  = new SqlParameter("@id", 106);
var result = dbContext
               .Database
               .SqlQuery<Category>("dbo.sp_GetById @id", param)
               .FirstOrDefault();
oczekuje
źródło
0

jeśli chcesz przekazać parametry tabeli do procedury składowanej, musisz ustawić właściwość TypeName dla swoich parametrów tabeli.

SqlParameter codesParam = new SqlParameter(CODES_PARAM, SqlDbType.Structured);
            SqlParameter factoriesParam = new SqlParameter(FACTORIES_PARAM, SqlDbType.Structured);

            codesParam.Value = tbCodes;
            codesParam.TypeName = "[dbo].[MES_CodesType]";
            factoriesParam.Value = tbfactories;
            factoriesParam.TypeName = "[dbo].[MES_FactoriesType]";


            var list = _context.Database.SqlQuery<MESGoodsRemain>($"{SP_NAME} {CODES_PARAM}, {FACTORIES_PARAM}"
                , new SqlParameter[] {
                   codesParam,
                   factoriesParam
                }
                ).ToList();
trueboroda
źródło
0

Oto, co EF (najpierw DB) generuje w klasie DbContext:

public ObjectResult<int> Insert_Department(string department)
{
    var departmentParameter = new ObjectParameter("department", department);

    return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<int>("insert_department", departmentParameter);
}
IngoB
źródło
0

Kiedy EDMX utworzy ten czas, jeśli wybierzesz opcję przechowywaną w procedurze wyboru tabeli, to po prostu zadzwoń do sklepu przetworzonego przy użyciu nazwy procedury ...

var num1 = 1; 
var num2 = 2; 

var result = context.proc_name(num1,num2).tolist();// list or single you get here.. using same thing you can call insert,update or delete procedured.
Shafiq Rabbi
źródło
0

Odkryłem, że wywoływanie procedur przechowywanych w podejściu Code First nie jest wygodne. Wolę używać Dapperzamiast tego

Poniższy kod został napisany za pomocą Entity Framework:

var clientIdParameter = new SqlParameter("@ClientId", 4);

var result = context.Database
.SqlQuery<ResultForCampaign>("GetResultsForCampaign @ClientId", clientIdParameter)
.ToList();

Poniższy kod został napisany za pomocą Dapper:

return Database.Connection.Query<ResultForCampaign>(
            "GetResultsForCampaign ",
            new
            {
                ClientId = 4
            },
            commandType: CommandType.StoredProcedure);

Uważam, że drugi fragment kodu jest łatwiejszy do zrozumienia.

Vladislav Furdak
źródło
0
public static string ToSqlParamsString(this IDictionary<string, string> dict)
        {
            string result = string.Empty;
            foreach (var kvp in dict)
            {
                result += $"@{kvp.Key}='{kvp.Value}',";
            }
            return result.Trim(',', ' ');
        }

public static List<T> RunSproc<T>(string sprocName, IDictionary<string, string> parameters)
        {
            string command = $"exec {sprocName} {parameters.ToSqlParamsString()}";
            return Context.Database.SqlQuery<T>(command).ToList();
        }
mattylantz
źródło
0

Nic nie trzeba robić ... podczas tworzenia kontekstu db dla kodu pierwsze podejście zainicjuj przestrzeń nazw poniżej obszaru płynnego interfejsu API stwórz listę sp i użyj jej w innym miejscu, w którym chcesz.

public partial class JobScheduleSmsEntities : DbContext
{
    public JobScheduleSmsEntities()
        : base("name=JobScheduleSmsEntities")
    {
        Database.SetInitializer<JobScheduleSmsEntities>(new CreateDatabaseIfNotExists<JobScheduleSmsEntities>());
    }

    public virtual DbSet<Customer> Customers { get; set; }
    public virtual DbSet<ReachargeDetail> ReachargeDetails { get; set; }
    public virtual DbSet<RoleMaster> RoleMasters { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //modelBuilder.Types().Configure(t => t.MapToStoredProcedures());

        //modelBuilder.Entity<RoleMaster>()
        //     .HasMany(e => e.Customers)
        //     .WithRequired(e => e.RoleMaster)
        //     .HasForeignKey(e => e.RoleID)
        //     .WillCascadeOnDelete(false);
    }
    public virtual List<Sp_CustomerDetails02> Sp_CustomerDetails()
    {
        //return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<Sp_CustomerDetails02>("Sp_CustomerDetails");
        //  this.Database.SqlQuery<Sp_CustomerDetails02>("Sp_CustomerDetails");
        using (JobScheduleSmsEntities db = new JobScheduleSmsEntities())
        {
           return db.Database.SqlQuery<Sp_CustomerDetails02>("Sp_CustomerDetails").ToList();

        }

    }

}

}

public partial class Sp_CustomerDetails02
{
    public long? ID { get; set; }
    public string Name { get; set; }
    public string CustomerID { get; set; }
    public long? CustID { get; set; }
    public long? Customer_ID { get; set; }
    public decimal? Amount { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public int? CountDay { get; set; }
    public int? EndDateCountDay { get; set; }
    public DateTime? RenewDate { get; set; }
    public bool? IsSMS { get; set; }
    public bool? IsActive { get; set; }
    public string Contact { get; set; }
}
SHUBHASIS MAHATA
źródło
0

Pierwsze użycie kodu frameworku MySql i Entity Podejście:

public class Vw_EMIcount
{
    public int EmiCount { get; set; }
    public string Satus { get; set; }
}

var result = context.Database.SqlQuery<Vw_EMIcount>("call EMIStatus('2018-3-01' ,'2019-05-30')").ToList();
Hari Lakkakula
źródło
0

Utwórz procedurę w MYsql.

delimiter //
create procedure SP_Dasboarddata(fromdate date, todate date)
begin
select count(Id) as count,date,status,sum(amount) as amount from 
details
where (Emidate between fromdate and todate)
group by date ,status;
END;
//

Utwórz klasę zawierającą procedury składowane zwracające wartości zestawu wyników

[Table("SP_reslutclass")]
public  class SP_reslutclass
{
    [Key]
    public int emicount { get; set; }
    public DateTime Emidate { get; set; }
    public int ? Emistatus { get; set; }
    public int emiamount { get; set; }

}

Dodaj klasę w Dbcontext

  public  class ABCDbContext:DbContext
{
    public ABCDbContext(DbContextOptions<ABCDbContext> options)
       : base(options)
    {

    }

 public DbSet<SP_reslutclass> SP_reslutclass { get; set; }
}

Wywołaj encję w repozytorium

   var counts = _Dbcontext.SP_reslutclass.FromSql("call SP_Dasboarddata 
                    ('2019-12-03','2019-12-31')").ToList();
Hari Lakkakula
źródło