Bagaimana cara memanggil Stored Procedure di Entity Framework 6 (Code-First)?

259

Saya sangat baru di Entity Framework 6 dan saya ingin menerapkan prosedur tersimpan di proyek saya. Saya memiliki prosedur tersimpan sebagai berikut:

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 kelas:

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
}

Masalah saya adalah: bagaimana saya bisa memanggil prosedur tersimpan dan mengirimkan parameter ke dalamnya?

Jaan
sumber
Saya tertarik mengetahui hal itu juga. Idealnya saya melewatkan EF sama sekali dan menjalankan SEMUA melalui prosedur yang tersimpan. Saya seorang ahli dalam SQL tetapi telah menemukan EF sangat frustasi untuk diterapkan.
David Britz

Jawaban:

247

Anda dapat memanggil prosedur tersimpan di DbContextkelas Anda sebagai berikut.

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

Tetapi jika prosedur tersimpan Anda mengembalikan beberapa set hasil sebagai kode sampel Anda, maka Anda dapat melihat artikel bermanfaat ini di MSDN

Prosedur Tersimpan dengan Kumpulan Hasil Berganda

Alborz
sumber
2
Terima kasih @Alborz. dapatkah Anda memberikan saya beberapa tautan mengenai berbagai implementasi Prosedur Tersimpan di Entity Framework 6 Code First. Saya mencari di mana-mana di web tetapi tidak mendapatkan artikel di mana saya bisa langsung memanggil prosedur tersimpan untuk parameter IN dan OUT. Terima kasih atas waktu Anda yang berharga.
Jaan
2
Artikel ini mungkin membantu blogs.msdn.com/b/diego/archive/2012/01/10/…
Alborz
8
Tampaknya ini tidak berfungsi dengan parameter. Tampaknya perlu mencantumkan parameter secara eksplisit sebagai bagian dari kueri.
Tandai
6
Ya, Anda perlu menentukan params sebagai bagian dari kueri - "storedProcedureName @param1, @param2". Juga jenis paramsini System.Data.SqlClient.SqlParameter[].
Oppa Gingham Style
6
this.Database.SqlQuery<YourEntityType>("storedProcedureName @param1", new System.Data.SqlClient.SqlParameter("@param1", YourParam));
Ppp
152

Yang harus Anda lakukan adalah membuat objek yang memiliki nama properti yang sama dengan hasil yang dikembalikan oleh prosedur tersimpan. Untuk prosedur tersimpan berikut:

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

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

buat kelas yang terlihat seperti:

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

        public string Gender { get; set; }

        public decimal Payout { get; set; }
    }

dan kemudian panggil prosedur dengan melakukan hal berikut:

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

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

Hasilnya akan berisi daftar ResultForCampaignobjek. Anda dapat menelepon SqlQuerymenggunakan parameter sebanyak yang diperlukan.

Filipe Leite
sumber
2
Untuk satu situasi, ini akan bekerja dengan baik. Saya menemukan bahwa definisi SProc harus benar-benar digabungkan dengan kelas yang mewarisi dari DBContext, alih-alih di "ladang gandum" produk.
GoldBishop
50

Saya menyelesaikannya dengan ExecuteSqlCommand

Masukkan metode Anda sendiri seperti milik saya di DbContext sebagai contoh Anda sendiri:

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);
}

sehingga Anda dapat memiliki metode di belakang kode Anda seperti ini:

[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;
    }
}

ini SP saya:

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

semoga membantu anda

Mahdi ghafoorian
sumber
2
Anda perlu menentukan panjang pada parameter nchar untuk prosedur tersimpan Anda - jika tidak, panjangnya hanya satu karakter, seperti yang Anda temukan.
Dave W
@Mahdighafoorian Ini adalah jawaban yang sangat berguna, terima kasih banyak! :)
Komengem
Sintaks ini tidak memerlukan modifikasi pada urutan Parameter SProc, dengan kata lain Posisi Posisi.
GoldBishop
21

Menggunakan contoh Anda, berikut adalah dua cara untuk mencapai ini:

1 - Gunakan pemetaan prosedur Tersimpan

Perhatikan bahwa kode ini akan berfungsi dengan atau tanpa pemetaan. Jika Anda mematikan pemetaan pada entitas, EF akan menghasilkan pernyataan 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 - Hubungi prosedur yang tersimpan secara langsung

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;
     }
}

Saya sarankan menggunakan pendekatan pertama, karena Anda dapat bekerja dengan objek departemen secara langsung dan tidak harus membuat banyak objek SqlParameter.

Brian Vander Plaats
sumber
3
Hati-hati, adalah contoh kedua perubahan tidak dilacak oleh dbContext
edtruant
EDIT. Gunakan System.Data.Entity.DbSet <TEntity> .SqlQuery (String, Object []) sebagai gantinya.
edtruant
@edtruant DbContext muncul untuk melacak perubahan. Untuk menguji, saya melihat db. <DbSet> .Count () sebelum dan sesudah pernyataan penyisipan. Dalam kedua metode, jumlah bertambah satu. Untuk kelengkapan saya menambahkan metode alternatif ke contoh.
Brian Vander Plaats
1
Saya tidak melihat referensi ke prosedur tersimpan pada contoh pertama.
xr280xr
2
@ xr280xr insert_department direferensikan dalam ekspresi modelBuilder dalam pertanyaan OP. Itulah keuntungan memetakan hal-hal seperti ini karena berfungsi secara efektif dengan cara yang sama seperti jika Anda membiarkan EF menghasilkan pernyataan insert / update / delete
Brian Vander Plaats
15

Anda menggunakan MapToStoredProcedures()yang menunjukkan bahwa Anda memetakan entitas Anda ke prosedur tersimpan, saat melakukan ini Anda harus melepaskan fakta bahwa ada prosedur tersimpan dan menggunakan contextseperti biasa. Sesuatu seperti ini ( ditulis ke dalam browser jadi tidak diuji )

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

Jika semua yang Anda benar-benar coba lakukan adalah memanggil prosedur tersimpan secara langsung kemudian gunakan SqlQuery

qujck
sumber
2
Terima kasih qujck. Tetapi saya ingin menggunakan prosedur tersimpan. Saya hanya memberikan kode contoh agar mudah dipahami.
Jaan
4
@ Jaan - Kode di atas akan menggunakan prosedur tersimpan. Maksud Anda, Anda ingin langsung memanggil prosedur yang tersimpan?
qujck
Iya. Bisakah Anda memberi tahu saya jalan mana yang lebih baik. Memanggil langsung prosedur tersimpan atau kode di atas yang telah Anda berikan?
Jaan
6
@Jaan menggunakan kode yang saya tunjukkan - ORM dimaksudkan untuk menyembunyikan implementasi yang mendasarinya - menggunakan kode di atas memastikan bahwa tidak masalah bagi sisa kode Anda apakah ada prosedur tersimpan atau tidak. Anda bahkan dapat mengubah pemetaan model ke prosedur tersimpan lain atau menjadi prosedur tersimpan tanpa mengubah apa pun.
qujck
4
@ Chazt3n Pertanyaan menunjukkan prosedur tersimpan yang dikonfigurasi dari saluran .MapToStoredProcedures(s => . Panggilan ke Addharus diselesaikan ke.Insert(i => i.HasName("insert_department")
qujck
12

Sekarang Anda juga dapat menggunakan konvensi yang saya buat yang memungkinkan menjalankan prosedur tersimpan (termasuk prosedur tersimpan yang mengembalikan banyak hasil), TVF dan UDF skalar yang berasal dari EF.

Hingga Entity Framework 6.1 dirilis, fungsi-fungsi store (yaitu Tabel Valued Functions and Stored Procedures) dapat digunakan di EF hanya ketika melakukan Database Pertama. Ada beberapa solusi yang memungkinkan untuk menjalankan fungsi toko di aplikasi Code First tetapi Anda masih tidak bisa menggunakan TVF di kueri Linq yang merupakan salah satu batasan terbesar. Di EF 6.1 API pemetaan diumumkan kepada publik yang (bersama dengan beberapa penyesuaian tambahan) memungkinkan untuk menggunakan fungsi toko di aplikasi Code First Anda.

Baca lebih lajut

Saya mendorong cukup keras selama dua minggu terakhir dan ini dia - versi beta dari konvensi yang memungkinkan menggunakan fungsi toko (yaitu prosedur tersimpan, fungsi nilai tabel dll.) Dalam aplikasi yang menggunakan pendekatan Code First dan Entity Framework 6.1.1 ( atau lebih baru). Saya sangat senang dengan perbaikan dan fitur baru yang termasuk dalam rilis ini.

Baca lebih lanjut .

Pawel
sumber
Sebenarnya sejak 4.0, Anda dapat menjalankan SProcs tanpa Model. Anda perlu menjalankan pernyataan SQL Raw alih-alih properti objek. Bahkan dengan 6.1.x, Anda harus menggunakan SqlQuery <T> atau ExecuteSqlCommand untuk mendapatkan efek yang serupa.
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
sumber
1
params adalah pengidentifikasi menggunakan nama yang berbeda.
yogihosting
2
The SaveChanges () di sini tidak perlu. Perubahan dilakukan pada panggilan ExecuteSqlCommand ().
Xavier Poinas
10

Ini bekerja untuk saya dengan menarik kembali data dari prosedur yang tersimpan sambil mengirimkan parameter.

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

_db adalah dbContext

Tom Stickel
sumber
9

Lihatlah tautan ini yang menunjukkan cara kerja pemetaan EF 6 dengan Prosedur Tersimpan untuk membuat Sisipan, Perbarui, dan Hapus: http://msdn.microsoft.com/en-us/data/dn468673

Tambahan

Berikut adalah contoh yang bagus untuk memanggil prosedur tersimpan dari Code First:

Katakanlah Anda harus menjalankan Prosedur Tersimpan dengan satu parameter, dan Prosedur Tersimpan mengembalikan satu set data yang cocok dengan Entity States, jadi kami akan memiliki ini:

var countryIso = "AR"; //Argentina

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

Sekarang katakanlah kita ingin menjalankan prosedur tersimpan lainnya dengan dua parameter:

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

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

Perhatikan bahwa kami menggunakan penamaan berbasis indeks untuk parameter. Ini karena Entity Framework akan membungkus parameter ini sebagai objek DbParameter dari Anda untuk menghindari masalah injeksi SQL.

Semoga contoh ini membantu!

Gabriel Andrés Brancolini
sumber
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
sumber
4

Ini bekerja untuk saya pada kode terlebih dahulu. Ini mengembalikan daftar dengan properti model tampilan yang cocok (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();

Diperbarui untuk Konteks

Konteks adalah turunan dari kelas yang mewarisi DbContext seperti di bawah ini.

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

var Context = new  ApplicationDbContext();
reza.cse08
sumber
Hai, saya tidak dapat menemukan Context.Database.SqlQuery <Model> ini, di mana saya dapat melakukan Context.TableName.SqlQuery (ProcName) ini. yang memberi saya masalah
Marshall
@ Marshall, mungkin Anda menggunakan desain database pertama. silakan periksa tautan ini stackoverflow.com/questions/11792018/…
reza.cse08
1

Penumpang yang tidak memiliki pikiran memiliki proyek yang memungkinkan beberapa set hasil dikembalikan dari proc yang disimpan menggunakan kerangka entitas. Salah satu contohnya di bawah ini ....

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
sumber
1

Anda dapat meneruskan parameter ke sp_GetByIddan mengambil hasilnya di ToList()atauFirstOrDefault();

var param  = new SqlParameter("@id", 106);
var result = dbContext
               .Database
               .SqlQuery<Category>("dbo.sp_GetById @id", param)
               .FirstOrDefault();
menunggu
sumber
0

jika Anda ingin meneruskan params tabel ke dalam prosedur tersimpan, Anda harus mengatur properti TypeName untuk params tabel Anda.

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
sumber
0

Inilah yang dihasilkan EF (DB terlebih dahulu) di kelas DbContext:

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

    return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<int>("insert_department", departmentParameter);
}
IngoB
sumber
0

Ketika EDMX membuat waktu ini jika Anda memilih tersimpan diproses dalam opsi tabel pilih kemudian panggil toko diproses dengan menggunakan nama prosedur ...

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.
Syafiq Rabi
sumber
0

Saya menemukan bahwa memanggil Prosedur Tersimpan dalam pendekatan Code First tidak nyaman. Saya lebih suka menggunakan Dappersebagai gantinya

Kode berikut ditulis dengan Entity Framework:

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

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

Kode berikut ditulis dengan Dapper:

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

Saya percaya potongan kode kedua lebih mudah dimengerti.

Vladislav Furdak
sumber
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
sumber
0

Tidak ada hubungannya ... ketika Anda membuat dbcontext untuk pendekatan kode pertama inisialisasi namespace di bawah area API yang lancar membuat daftar sp dan menggunakannya tempat lain di mana Anda inginkan.

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
sumber
0

Menggunakan pendekatan kerangka kerja MySql dan Entity pertama:

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
sumber
0

Buat Prosedur di 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;
//

Buat kelas yang berisi prosedur yang disimpan mengembalikan nilai yang ditetapkan nilai

[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; }

}

Tambahkan Kelas di Dbcontext

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

    }

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

Panggil entitas dalam repositori

   var counts = _Dbcontext.SP_reslutclass.FromSql("call SP_Dasboarddata 
                    ('2019-12-03','2019-12-31')").ToList();
Hari Lakkakula
sumber