Aracılığıyla paylaş


ASP.NET MVC Uygulamasında Depo ve İş Birimi Desenlerini Uygulama (9/10)

tarafından Tom Dykstra

Contoso University örnek web uygulaması, Entity Framework 5 Code First ve Visual Studio 2012 kullanarak ASP.NET MVC 4 uygulamalarının nasıl oluşturulacağını gösterir. Öğretici serisi hakkında bilgi için serideki ilk öğreticiye bakın.

Not

Çözemediğiniz bir sorunla karşılaşırsanız tamamlanmış bölümü indirin ve sorununuzu yeniden oluşturmayı deneyin. Genellikle kodunuzu tamamlanmış kodla karşılaştırarak sorunun çözümünü bulabilirsiniz. Bazı yaygın hatalar ve bunların nasıl çözüldüğü için bkz . Hatalar ve Geçici Çözümler.

Önceki öğreticide ve Instructor varlık sınıflarındaki Student yedekli kodu azaltmak için devralmayı kullandınız. Bu öğreticide CRUD işlemleri için depoyu ve iş desenleri birimini kullanmanın bazı yollarını göreceksiniz. Önceki öğreticide olduğu gibi, bu öğreticide yeni sayfalar oluşturmak yerine kodunuzun zaten oluşturduğunuz sayfalarla çalışma biçimini değiştireceksiniz.

Depo ve İş Birimi Desenleri

Depo ve iş birimi desenleri, veri erişim katmanı ile bir uygulamanın iş mantığı katmanı arasında bir soyutlama katmanı oluşturmaya yöneliktir. Bu desenleri uygulamak, uygulamanızı veri deposundaki değişikliklerden yalıtmanıza yardımcı olabilir ve otomatik birim testini veya test temelli geliştirmeyi (TDD) kolaylaştırabilir.

Bu öğreticide her varlık türü için bir depo sınıfı uygulayacaksınız. Varlık türü için Student bir depo arabirimi ve bir depo sınıfı oluşturacaksınız. Denetleyicinizde deponun örneğini oluştururken, denetleyicinin depo arabirimini uygulayan herhangi bir nesneye başvuru kabul edebilmesi için arabirimini kullanacaksınız. Denetleyici bir web sunucusu altında çalıştığında, Entity Framework ile çalışan bir depo alır. Denetleyici bir birim test sınıfı altında çalıştığında, bellek içi koleksiyon gibi test için kolayca işleyebileceğiniz bir şekilde depolanan verilerle çalışan bir depo alır.

Öğreticinin ilerleyen bölümlerinde denetleyicideki ve varlık türleri Course için Course birden çok depo ve Department bir iş sınıfı birimi kullanacaksınız. İş sınıfı birimi, tümü tarafından paylaşılan tek bir veritabanı bağlam sınıfı oluşturarak birden çok deponun çalışmasını koordine eder. Otomatik birim testi yapabilmek istiyorsanız, depoda yaptığınız gibi bu sınıflar için Student arabirimler oluşturup kullanırsınız. Ancak öğreticiyi basit tutmak için bu sınıfları arabirimler olmadan oluşturacak ve kullanacaksınız.

Aşağıdaki çizimde, depoyu veya iş birimi düzenini hiç kullanmamaya kıyasla denetleyici ve bağlam sınıfları arasındaki ilişkileri kavramsallaştırmanın bir yolu gösterilmektedir.

Repository_pattern_diagram

Bu öğretici serisinde birim testleri oluşturmazsınız. Depo desenini kullanan bir MVC uygulamasıyla TDD'ye giriş için bkz. İzlenecek yol: ASP.NET MVC ile TDD kullanma. Depo düzeni hakkında daha fazla bilgi için aşağıdaki kaynaklara bakın:

Not

Depo ve iş birimi desenlerini uygulamanın birçok yolu vardır. Depo sınıflarını bir iş sınıfı birimiyle veya iş sınıfı olmadan kullanabilirsiniz. Tüm varlık türleri için tek bir depo veya her tür için bir depo uygulayabilirsiniz. Her tür için bir tane uygularsanız, ayrı sınıflar, genel bir temel sınıf ve türetilmiş sınıflar ya da soyut bir temel sınıf ve türetilmiş sınıflar kullanabilirsiniz. Deponuza iş mantığı ekleyebilir veya bunu veri erişim mantığıyla kısıtlayabilirsiniz. Ayrıca, varlık kümelerinizin DbSet türleri yerine IDbSet arabirimlerini kullanarak veritabanı bağlam sınıfınıza bir soyutlama katmanı oluşturabilirsiniz. Bu öğreticide gösterilen bir soyutlama katmanını uygulama yaklaşımı, tüm senaryolar ve ortamlar için öneri değil, göz önünde bulundurmanız gereken seçeneklerden biridir.

Öğrenci Deposu Sınıfını Oluşturma

DAL klasöründe IStudentRepository.cs adlı bir sınıf dosyası oluşturun ve mevcut kodu aşağıdaki kodla değiştirin:

using System;
using System.Collections.Generic;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public interface IStudentRepository : IDisposable
    {
        IEnumerable<Student> GetStudents();
        Student GetStudentByID(int studentId);
        void InsertStudent(Student student);
        void DeleteStudent(int studentID);
        void UpdateStudent(Student student);
        void Save();
    }
}

Bu kod, biri tüm Student varlıkları döndüren, diğeri de kimliğe göre tek Student bir varlık bulan iki okuma yöntemi de dahil olmak üzere tipik bir CRUD yöntemleri kümesini bildirir.

DAL klasöründe StudentRepository.cs dosyası adlı bir sınıf dosyası oluşturun. Mevcut kodu arabirimini uygulayan IStudentRepository aşağıdaki kodla değiştirin:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class StudentRepository : IStudentRepository, IDisposable
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public IEnumerable<Student> GetStudents()
        {
            return context.Students.ToList();
        }

        public Student GetStudentByID(int id)
        {
            return context.Students.Find(id);
        }

        public void InsertStudent(Student student)
        {
            context.Students.Add(student);
        }

        public void DeleteStudent(int studentID)
        {
            Student student = context.Students.Find(studentID);
            context.Students.Remove(student);
        }

        public void UpdateStudent(Student student)
        {
            context.Entry(student).State = EntityState.Modified;
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Veritabanı bağlamı bir sınıf değişkeninde tanımlanır ve oluşturucu çağıran nesnesinin bağlamın bir örneğine geçmesini bekler:

private SchoolContext context;

public StudentRepository(SchoolContext context)
{
    this.context = context;
}

Depoda yeni bir bağlam örneği oluşturabilirsiniz, ancak bir denetleyicide birden çok depo kullandıysanız her biri ayrı bir bağlamla sonuçlanabilir. Daha sonra denetleyicide Course birden çok depo kullanacaksınız ve bir iş sınıfı biriminin tüm depoların aynı bağlamı kullanmasını nasıl sağlayabileceğinizi göreceksiniz.

Depo, daha önce denetleyicide gördüğünüz gibi IDisposable uygular ve veritabanı bağlamını atıp CRUD yöntemleri veritabanı bağlamını daha önce gördüğünüz gibi çağırır.

Öğrenci Denetleyicisini Depoyu Kullanacak Şekilde Değiştirme

StudentController.cs dosyasında şu anda sınıfında olan kodu aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

using System;
using System.Data;
using System.Linq;
using System.Web.Mvc;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;
using PagedList;

namespace ContosoUniversity.Controllers
{
   public class StudentController : Controller
   {
      private IStudentRepository studentRepository;

      public StudentController()
      {
         this.studentRepository = new StudentRepository(new SchoolContext());
      }

      public StudentController(IStudentRepository studentRepository)
      {
         this.studentRepository = studentRepository;
      }

      //
      // GET: /Student/

      public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
      {
         ViewBag.CurrentSort = sortOrder;
         ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
         ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

         if (searchString != null)
         {
            page = 1;
         }
         else
         {
            searchString = currentFilter;
         }
         ViewBag.CurrentFilter = searchString;

         var students = from s in studentRepository.GetStudents()
                        select s;
         if (!String.IsNullOrEmpty(searchString))
         {
            students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                                   || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
         }
         switch (sortOrder)
         {
            case "name_desc":
               students = students.OrderByDescending(s => s.LastName);
               break;
            case "Date":
               students = students.OrderBy(s => s.EnrollmentDate);
               break;
            case "date_desc":
               students = students.OrderByDescending(s => s.EnrollmentDate);
               break;
            default:  // Name ascending 
               students = students.OrderBy(s => s.LastName);
               break;
         }

         int pageSize = 3;
         int pageNumber = (page ?? 1);
         return View(students.ToPagedList(pageNumber, pageSize));
      }

      //
      // GET: /Student/Details/5

      public ViewResult Details(int id)
      {
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // GET: /Student/Create

      public ActionResult Create()
      {
         return View();
      }

      //
      // POST: /Student/Create

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
           Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.InsertStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

      //
      // GET: /Student/Edit/5

      public ActionResult Edit(int id)
      {
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // POST: /Student/Edit/5

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
         Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.UpdateStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

      //
      // GET: /Student/Delete/5

      public ActionResult Delete(bool? saveChangesError = false, int id = 0)
      {
         if (saveChangesError.GetValueOrDefault())
         {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
         }
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // POST: /Student/Delete/5

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Delete(int id)
      {
         try
         {
            Student student = studentRepository.GetStudentByID(id);
            studentRepository.DeleteStudent(id);
            studentRepository.Save();
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
         }
         return RedirectToAction("Index");
      }

      protected override void Dispose(bool disposing)
      {
         studentRepository.Dispose();
         base.Dispose(disposing);
      }
   }
}

Denetleyici artık bağlam sınıfı yerine arabirimini uygulayan IStudentRepository bir nesne için sınıf değişkeni bildirir:

private IStudentRepository studentRepository;

Varsayılan (parametresiz) oluşturucu yeni bir bağlam örneği oluşturur ve isteğe bağlı bir oluşturucu, çağıranın bağlam örneğini geçirmesine olanak tanır.

public StudentController()
{
    this.studentRepository = new StudentRepository(new SchoolContext());
}

public StudentController(IStudentRepository studentRepository)
{
    this.studentRepository = studentRepository;
}

( Bağımlılık ekleme veya DI kullanıyorsanız, DI yazılımı her zaman doğru depo nesnesinin sağlanmasını sağlayacağından varsayılan oluşturucuya ihtiyacınız olmazdı.)

CRUD yöntemlerinde artık bağlam yerine depo çağrılır:

var students = from s in studentRepository.GetStudents()
               select s;
Student student = studentRepository.GetStudentByID(id);
studentRepository.InsertStudent(student);
studentRepository.Save();
studentRepository.UpdateStudent(student);
studentRepository.Save();
studentRepository.DeleteStudent(id);
studentRepository.Save();

Dispose Yöntemi şimdi bağlam yerine depoyu atıyor:

studentRepository.Dispose();

Siteyi çalıştırın ve Öğrenciler sekmesine tıklayın.

Students_Index_page

Sayfa, depoyu kullanmak üzere kodu değiştirmeden önceki gibi görünür ve çalışır ve diğer Öğrenci sayfaları da aynı şekilde çalışır. Ancak, denetleyici yönteminin Index filtreleme ve sıralama yönteminde önemli bir fark vardır. Bu yöntemin özgün sürümü aşağıdaki kodu içeriyor:

var students = from s in context.Students
               select s;
if (!String.IsNullOrEmpty(searchString))
{
    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                           || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

Güncelleştirilmiş Index yöntem aşağıdaki kodu içerir:

var students = from s in studentRepository.GetStudents()
                select s;
if (!String.IsNullOrEmpty(searchString))
{
    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                        || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

Yalnızca vurgulanan kod değişti.

Kodun özgün sürümünde nesne studentsIQueryable olarak yazılır. Sorgu, dizin görünümü öğrenci modeline erişene kadar gerçekleşmeyen gibi ToListbir yöntem kullanılarak bir koleksiyona dönüştürülene kadar veritabanına gönderilmez. Where Yukarıdaki özgün koddaki yöntem, veritabanına gönderilen SQL sorgusunda bir WHERE yan tümceye dönüşür. Bu da veritabanı tarafından yalnızca seçili varlıkların döndürüldüğünü gösterir. Ancak, olarak değiştirilmesinin context.StudentsstudentRepository.GetStudents()students bir sonucu olarak, bu deyimden sonraki değişken veritabanındaki tüm öğrencileri içeren bir IEnumerable koleksiyondur. Yöntemi uygulamanın Where sonucu aynıdır, ancak artık iş veritabanı tarafından değil web sunucusundaki bellekte yapılır. Büyük miktarlarda veri döndüren sorgular için bu verimsiz olabilir.

İpucu

IQueryable ve IEnumerable karşılaştırması

Depoyu burada gösterildiği gibi uyguladıktan sonra, Arama kutusuna bir şey girseniz bile, SQL Server gönderilen sorgu arama ölçütlerinizi içermediğinden tüm Öğrenci satırlarını döndürür:

Uygulanan ve vurgulanan yeni öğrenci deposunu gösteren kodun ekran görüntüsü.

SELECT 
'0X0X' AS [C1], 
[Extent1].[PersonID] AS [PersonID], 
[Extent1].[LastName] AS [LastName], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[EnrollmentDate] AS [EnrollmentDate]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[Discriminator] = N'Student'

Depo, arama ölçütlerini bilmeden sorguyu yürüttüğünden bu sorgu tüm öğrenci verilerini döndürür. Sıralama, arama ölçütleri uygulama ve disk belleği için verilerin bir alt kümesini seçme işlemi (bu örnekte yalnızca 3 satır gösterilir) daha sonra yöntem koleksiyonda çağrıldığında ToPagedList bellekte IEnumerable gerçekleştirilir.

Kodun önceki sürümünde (depoyu uygulamadan önce), siz arama ölçütlerini uygulayana kadar sorgu veritabanına gönderilmez ve nesnede IQueryable çağrılırToPagedList.

Öğrenci Denetleyicisi kodunu gösteren ekran görüntüsü. Kod arama dizesi satırı ve Sayfalandırılmış Listeye kod satırı vurgulanır.

Bir IQueryable nesnede ToPagedList çağrıldığında, SQL Server'ye gönderilen sorgu arama dizesini belirtir ve sonuç olarak yalnızca arama ölçütlerine uyan satırlar döndürülür ve bellekte filtreleme yapılması gerekmez.

exec sp_executesql N'SELECT TOP (3) 
[Project1].[StudentID] AS [StudentID], 
[Project1].[LastName] AS [LastName], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[EnrollmentDate] AS [EnrollmentDate]
FROM ( SELECT [Project1].[StudentID] AS [StudentID], [Project1].[LastName] AS [LastName], [Project1].[FirstName] AS [FirstName], [Project1].[EnrollmentDate] AS [EnrollmentDate], row_number() OVER (ORDER BY [Project1].[LastName] ASC) AS [row_number]
FROM ( SELECT 
    [Extent1].[StudentID] AS [StudentID], 
    [Extent1].[LastName] AS [LastName], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[EnrollmentDate] AS [EnrollmentDate]
    FROM [dbo].[Student] AS [Extent1]
    WHERE (( CAST(CHARINDEX(UPPER(@p__linq__0), UPPER([Extent1].[LastName])) AS int)) > 0) OR (( CAST(CHARINDEX(UPPER(@p__linq__1), UPPER([Extent1].[FirstName])) AS int)) > 0)
)  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[LastName] ASC',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'Alex',@p__linq__1=N'Alex'

(Aşağıdaki öğreticide, SQL Server gönderilen sorguların nasıl incelendiği açıklanmaktadır.)

Aşağıdaki bölümde, bu işin veritabanı tarafından yapılması gerektiğini belirtmenize olanak tanıyan depo yöntemlerinin nasıl uygulanacağı gösterilmektedir.

Şimdi denetleyici ile Entity Framework veritabanı bağlamı arasında bir soyutlama katmanı oluşturdunuz. Bu uygulamayla otomatik birim testi gerçekleştirecekseniz, uygulayan IStudentRepository bir birim testi projesinde alternatif bir depo sınıfı oluşturabilirsiniz. Bu sahte depo sınıfı, verileri okumak ve yazmak için bağlamı çağırmak yerine, denetleyici işlevlerini test etmek için bellek içi koleksiyonları işleyebilir.

Genel Depo ve İş Sınıfı Birimi Uygulama

Her varlık türü için bir depo sınıfı oluşturmak çok fazla yedekli koda neden olabilir ve kısmi güncelleştirmelere neden olabilir. Örneğin, aynı işlemin parçası olarak iki farklı varlık türünü güncelleştirmeniz gerekir. Her biri ayrı bir veritabanı bağlam örneği kullanıyorsa, biri başarılı, diğeri başarısız olabilir. Yedekli kodu en aza indirmenin bir yolu genel bir depo kullanmaktır ve tüm depoların aynı veritabanı bağlamını kullanmasını (ve dolayısıyla tüm güncelleştirmeleri koordine etmesini) sağlamanın bir yolu da bir iş sınıfı birimi kullanmaktır.

Öğreticinin bu bölümünde bir sınıf ve UnitOfWork sınıf oluşturacak ve hem hem de CourseDepartment varlık kümelerine erişmek için bunları denetleyicide Course kullanacaksınız.GenericRepository Daha önce açıklandığı gibi, öğreticinin bu bölümünü basit tutmak için bu sınıflar için arabirimler oluşturmazsınız. Ancak bunları TDD'yi kolaylaştırmak için kullanacaksanız bunları genellikle depodaki gibi arabirimlerle uygularsınız Student .

Genel Depo Oluşturma

DAL klasöründe GenericRepository.cs dosyasını oluşturun ve mevcut kodu aşağıdaki kodla değiştirin:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.Entity;
using ContosoUniversity.Models;
using System.Linq.Expressions;

namespace ContosoUniversity.DAL
{
    public class GenericRepository<TEntity> where TEntity : class
    {
        internal SchoolContext context;
        internal DbSet<TEntity> dbSet;

        public GenericRepository(SchoolContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    }
}

Sınıf değişkenleri veritabanı bağlamı ve deponun örneği oluşturulan varlık kümesi için bildirilir:

internal SchoolContext context;
internal DbSet dbSet;

Oluşturucu bir veritabanı bağlam örneği kabul eder ve varlık kümesi değişkenini başlatır:

public GenericRepository(SchoolContext context)
{
    this.context = context;
    this.dbSet = context.Set<TEntity>();
}

yöntemi, Get çağıran kodun sonuçları sıralamak için bir filtre koşulu ve sütun belirtmesine izin vermek için lambda ifadelerini kullanır ve dize parametresi çağıranın hızlı yükleme için gezinti özelliklerinin virgülle ayrılmış bir listesini sağlamasına olanak tanır:

public virtual IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "")

Kod Expression<Func<TEntity, bool>> filter , çağıranın türüne göre bir lambda ifadesi sağlayacağı ve bu ifadenin TEntity bir Boole değeri döndüreceği anlamına gelir. Örneğin, varlık türü için Student depo örneği oluşturulursa, çağırma yöntemindeki kod parametresi için filter " belirtebilirstudent => student.LastName == "Smith.

Kod Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy , çağıranın bir lambda ifadesi sağlayacağı anlamına da gelir. Ancak bu durumda, ifadeye giriş türü için TEntity bir IQueryable nesnedir. İfade, bu IQueryable nesnenin sıralı bir sürümünü döndürür. Örneğin, varlık türü için Student depo örneği oluşturulursa, çağırma yöntemindeki kod parametresi için orderBy belirtebilirq => q.OrderBy(s => s.LastName).

yöntemindeki Get kod bir IQueryable nesnesi oluşturur ve varsa filtre ifadesini uygular:

IQueryable<TEntity> query = dbSet;

if (filter != null)
{
    query = query.Where(filter);
}

Ardından, virgülle ayrılmış listeyi ayrıştırdıktan sonra istekli yükleme ifadelerini uygular:

foreach (var includeProperty in includeProperties.Split
    (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) 
{ 
    query = query.Include(includeProperty); 
}

Son olarak, varsa ifadeyi orderBy uygular ve sonuçları döndürür; aksi takdirde sıralanmamış sorgunun sonuçlarını döndürür:

if (orderBy != null)
{
    return orderBy(query).ToList();
}
else
{
    return query.ToList();
}

yöntemini çağırdığınızda Get , bu işlevler için parametreler sağlamak yerine yöntemi tarafından döndürülen koleksiyonda IEnumerable filtreleme ve sıralama yapabilirsiniz. Ancak sıralama ve filtreleme çalışması daha sonra web sunucusundaki bellekte yapılır. Bu parametreleri kullanarak, işin web sunucusu yerine veritabanı tarafından yapıldığından emin olun. Alternatif olarak, belirli varlık türleri için türetilmiş sınıflar oluşturabilir ve veya GetStudentsByNamegibi GetStudentsInNameOrder özelleştirilmiş Get yöntemler ekleyebilirsiniz. Ancak, karmaşık bir uygulamada, bu türetilmiş sınıfların ve özelleştirilmiş yöntemlerin çok sayıda neden olabilir, bu bakım için daha fazla çalışma olabilir.

, Insertve Update yöntemlerindeki GetByIDkod, genel olmayan depoda gördüklerinize benzer. (yöntemiyle hevesle Find yükleme gerçekleştiremediğiniz için imzada GetByID istekli bir yükleme parametresi sağlamazsınız.)

yöntemi için Delete iki aşırı yükleme sağlanır:

public virtual void Delete(object id)
{
    TEntity entityToDelete = dbSet.Find(id);
    dbSet.Remove(entityToDelete);
}

public virtual void Delete(TEntity entityToDelete)
{
    if (context.Entry(entityToDelete).State == EntityState.Detached)
    {
        dbSet.Attach(entityToDelete);
    }
    dbSet.Remove(entityToDelete);
}

Bunlardan biri silinecek varlığın yalnızca kimliğini geçirmenize olanak tanır ve bir varlık örneği alır. Eşzamanlılığı İşleme öğreticisinde gördüğünüz gibi, eşzamanlılık işleme için bir Delete izleme özelliğinin özgün değerini içeren bir varlık örneği alan bir yönteme ihtiyacınız vardır.

Bu genel depo tipik CRUD gereksinimlerini işler. Belirli bir varlık türünün daha karmaşık filtreleme veya sıralama gibi özel gereksinimleri olduğunda, bu tür için ek yöntemleri olan türetilmiş bir sınıf oluşturabilirsiniz.

İş Sınıfı Birimi Oluşturma

İş sınıfı birimi tek bir amaca hizmet eder: birden çok depo kullandığınızda bunların tek bir veritabanı bağlamını paylaştığından emin olmaktır. Bu şekilde, bir çalışma birimi tamamlandığında bağlamın SaveChanges söz konusu örneğinde yöntemini çağırabilir ve tüm ilgili değişikliklerin eşgüdümlü olduğundan emin olabilirsiniz. Sınıfın tek ihtiyacı bir yöntem ve her depo için bir özelliktir Save . Her depo özelliği, diğer depo örnekleriyle aynı veritabanı bağlam örneği kullanılarak örneklenmiş bir depo örneği döndürür.

DAL klasöründe UnitOfWork.cs adlı bir sınıf dosyası oluşturun ve şablon kodunu aşağıdaki kodla değiştirin:

using System;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class UnitOfWork : IDisposable
    {
        private SchoolContext context = new SchoolContext();
        private GenericRepository<Department> departmentRepository;
        private GenericRepository<Course> courseRepository;

        public GenericRepository<Department> DepartmentRepository
        {
            get
            {

                if (this.departmentRepository == null)
                {
                    this.departmentRepository = new GenericRepository<Department>(context);
                }
                return departmentRepository;
            }
        }

        public GenericRepository<Course> CourseRepository
        {
            get
            {

                if (this.courseRepository == null)
                {
                    this.courseRepository = new GenericRepository<Course>(context);
                }
                return courseRepository;
            }
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Kod, veritabanı bağlamı ve her depo için sınıf değişkenleri oluşturur. değişkeni için context yeni bir bağlam örneği oluşturulur:

private SchoolContext context = new SchoolContext();
private GenericRepository<Department> departmentRepository;
private GenericRepository<Course> courseRepository;

Her depo özelliği, deponun zaten var olup olmadığını denetler. Aksi takdirde, bağlam örneğini geçirerek deponun örneğini oluşturur. Sonuç olarak, tüm depolar aynı bağlam örneğini paylaşır.

public GenericRepository<Department> DepartmentRepository
{
    get
    {

        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

yöntemi, Save veritabanı bağlamını çağırır SaveChanges .

Sınıf değişkeninde veritabanı bağlamı örneği oluşturan tüm sınıflar IDisposable gibi, UnitOfWork sınıfı da bağlamı uygular ve yok eder.

Kurs Denetleyicisini UnitOfWork Sınıfını ve Depolarını kullanacak şekilde değiştirme

CourseController.cs dosyasında şu anda sahip olduğunuz kodu aşağıdaki kodla değiştirin:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;

namespace ContosoUniversity.Controllers
{
   public class CourseController : Controller
   {
      private UnitOfWork unitOfWork = new UnitOfWork();

      //
      // GET: /Course/

      public ViewResult Index()
      {
         var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
         return View(courses.ToList());
      }

      //
      // GET: /Course/Details/5

      public ViewResult Details(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         return View(course);
      }

      //
      // GET: /Course/Create

      public ActionResult Create()
      {
         PopulateDepartmentsDropDownList();
         return View();
      }

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
          [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Insert(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
         }
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      public ActionResult Edit(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
           [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Update(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
         }
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
      {
         var departmentsQuery = unitOfWork.DepartmentRepository.Get(
             orderBy: q => q.OrderBy(d => d.Name));
         ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
      }

      //
      // GET: /Course/Delete/5

      public ActionResult Delete(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         return View(course);
      }

      //
      // POST: /Course/Delete/5

      [HttpPost, ActionName("Delete")]
      [ValidateAntiForgeryToken]
      public ActionResult DeleteConfirmed(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         unitOfWork.CourseRepository.Delete(id);
         unitOfWork.Save();
         return RedirectToAction("Index");
      }

      protected override void Dispose(bool disposing)
      {
         unitOfWork.Dispose();
         base.Dispose(disposing);
      }
   }
}

Bu kod, sınıfı için UnitOfWork bir sınıf değişkeni ekler. (Burada arabirimler kullanıyorsanız değişkeni burada başlatmazsınız; bunun yerine depo için Student yaptığınız gibi iki oluşturucudan oluşan bir desen uygularsınız.)

private UnitOfWork unitOfWork = new UnitOfWork();

Sınıfının geri kalanında, veritabanı bağlamı için yapılan tüm başvurular, depoya erişmek için özellikler kullanılarak UnitOfWork uygun depoya yapılan başvurularla değiştirilir. Dispose yöntemi örneği atılırUnitOfWork.

var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Insert(course);
unitOfWork.Save();
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Update(course);
unitOfWork.Save();
// ...
var departmentsQuery = unitOfWork.DepartmentRepository.Get(
    orderBy: q => q.OrderBy(d => d.Name));
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Delete(id);
unitOfWork.Save();
// ...
unitOfWork.Dispose();

Siteyi çalıştırın ve Kurslar sekmesine tıklayın.

Courses_Index_page

Sayfa, değişikliklerinizden önceki gibi görünür ve çalışır ve diğer Kurs sayfaları da aynı şekilde çalışır.

Özet

Şimdi hem depoyu hem de iş desenleri birimini uyguladınız. Genel depoda yöntem parametreleri olarak lambda ifadelerini kullandınız. Bu ifadeleri bir IQueryable nesneyle kullanma hakkında daha fazla bilgi için MSDN Kitaplığı'ndaki IQueryable(T) Arabirimi (System.Linq) konusuna bakın. Sonraki öğreticide bazı gelişmiş senaryoları işlemeyi öğreneceksiniz.

Diğer Entity Framework kaynaklarına bağlantılar ASP.NET Veri Erişimi İçerik Eşlemesi'nde bulunabilir.