Pull to refresh

Разработка баз данных с Code First

Reading time 5 min
Views 55K
image

Повсеместно принято, что в «серьезных» CRUD приложениях база данных становится во главу угла. Ее проектируют самой первой, она обрастает хранимыми процедурами (stored procedures), с ней приходиться возиться больше всего. Но это не единственный путь! Для Entity Framework есть Code First подход, где главным становится код, а не база. Преимущества:

  • Никакого генерированного кода
  • База — это снова просто хранилище. Над ней не надо дрожать, дропается легко и без проблем.
  • Простейшая установка среды разработки. Выкачал код и запустил — никакой возни с бэкапами.


Есть и пара недостатков, но они скорее связаны с Entity Framework, а не с Code First подходом как таковым; о них чуть позже.

Ниже я покажу на примере, насколько просто разрабатывать с Code First подходом.

Пример

Возьмем простую модель:

image

В качестве фронт-энда будет ASP.NET MVC, так что создаем соответствующий проект. Выбираем No Authentication — в этом проекте нельзя будет логиниться и весь контент доступен для всех.

Я сделал еще 2 проекта — для бизнес-объектов и DAL, но при желании можно просто создать соответствующие папки в web проекте. Не забудте установить Entity Framework в соответствующие проекты через NuGet.

image


Создаем классы, которые будут отображать сущности (entities):

    public abstract class BaseEntity
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid ID { get; set; }
    }


    public class Course : BaseEntity
    {
        public string Title { get; set; }

        public int Credits { get; set; }


        public ICollection<Enrollment> Enrollments { get; set; }
    }


    public class Student : BaseEntity
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public DateTime EnrollmentDate { get; set; }


        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }


    public class Enrollment : BaseEntity
    {
        public Guid CourseID { get; set; }

        public Guid StudentID { get; set; }

        public Grade CourseGrade { get; set; }


        public virtual Student Student { get; set; }

        public virtual Course Course { get; set; }
    }


Как видно, все повторяющееся свойства (properties) можно убрать в абстрактный класс и наследоваться от него. В данном случае у каждой таблицы будет Primary Key колонка типа Guid, который будет генерироваться при записи в базу.

Grade — это просто энумератор, ничего особенного:
    public enum Grade
    {
        A,B,C,D,E,F
    }


Создаем контекстный класс:

    public class UniversityContext : DbContext
    {
        public UniversityContext() : base("UniversityContext") { }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Student>()
                .HasMany(s => s.Enrollments)
                .WithRequired(e => e.Student)
                .HasForeignKey(e => e.StudentID);

            modelBuilder.Entity<Course>()
                .HasMany(c => c.Enrollments)
                .WithRequired(e => e.Course)
                .HasForeignKey(e => e.CourseID);

            
        }
    }


Отношения дефинированы через Fluent API, читаются с конца — например, Student — Enrollment относятся как one (Student): many (Enrollment).

Стоит отметить, что конфигурировать модели можно как через Fluent API, так и аннотациями. Для некоторых настроек аннотаций не существует, но их можно создать самим. Я предпочитаю все-же Fluent API.

И, наконец, заполнение базы данными:

    public class UniversityInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<UniversityContext>
    {
        protected override void Seed(UniversityContext context)
        {
            var studentList = new List<Student>() 
            { 
                new Student(){ Age = 20, EnrollmentDate = DateTime.Now, Name = "Basil Ivanov" },
                new Student(){ Age = 18, EnrollmentDate = DateTime.Now, Name = "Boris Ivan" },
                new Student(){ Age = 27, EnrollmentDate = DateTime.Now, Name = "Masha Ivanova" },
                new Student(){ Age = 23, EnrollmentDate = DateTime.Now, Name = "Vytautas" },
                new Student(){ Age = 21, EnrollmentDate = DateTime.Now, Name = "Ivan" }
            };
            studentList.ForEach(s => context.Students.Add(s));
            context.SaveChanges();

            var courseList = new List<Course>()
            {
                new Course(){ Credits = 10, Title = "Maths"},
                new Course(){ Credits = 20, Title = "Advanced maths"},
                new Course(){ Credits = 10, Title = "Physics"}
            };
            courseList.ForEach(c => context.Courses.Add(c));
            context.SaveChanges();

            var enrollments = new List<Enrollment>()
            {
                new Enrollment(){ Course = context.Courses.FirstOrDefault(c => c.Title=="Maths"), Student = context.Students.FirstOrDefault()},
                new Enrollment(){ Course = context.Courses.FirstOrDefault(c => c.Title=="Physics"), Student = context.Students.FirstOrDefault()},
                new Enrollment(){ Course = context.Courses.FirstOrDefault(c => c.Title=="Physics"), 
                    Student = context.Students.FirstOrDefault(s => s.Name == "Boris Ivan")}
            };
            enrollments.ForEach(e => context.Enrollments.Add(e));
            context.SaveChanges();


        }
    }


Примечание: как следует из названия DropCreateDatabaseIfModelChanges, база будет дропаться при изменениях в соответствующих классах моделей. То есть данные — капут.
Как реализовать миграции, чтобы данные не капут, выходит за область этой статьи.

Последнее, что надо сделать — добавить информацию в web.config. Используем LocalDb, которая идет вместе с Visual Studio, которой вполне достаточно для целей этого проекта. Следующий код идет в элемент configuration:

<connectionStrings>  
    <add name="UniversityContext" connectionString="Data Source=(LocalDb)\v11.0;;AttachDbFilename=|DataDirectory|\UnivCRUD.mdf;Initial Catalog=UniversityCRUD;Integrated Security=SSPI;" providerName="System.Data.SqlClient"/>
</connectionStrings>


А следующая разметка — в элемент entityFramework:
<contexts>
    <context type="UniversityCRUD.DA.UniversityContext, UniversityCRUD.DA">
      <databaseInitializer type="UniversityCRUD.DA.UniversityInitializer, UniversityCRUD.DA" />
    </context>
</contexts>  


В атрибуте type элемента context указываются через запятую название класса контекста и assembly, где этот класс находится. То же самое для инициализатора в элементе databaseInitializer.

Это вообщем-то и все, проект готов к запуску.

В Visual Studio 2013 можно по-быстрому сгенерировать Controller и View к выбранной модели через диалог Add -> New Scaffolded Item.

image

Скачать пример можно тут.

Недостатки

Во-первых, к существующей базе данных подобный подход применить сложно. Так что вообщем-то это для разработки с нуля.
Часто подножки ставит Entity Framework, который часто принимает решения за программиста — есть так называемые конвенции, что, допустим, property который называется Id, будет по умолчанию преобразован в Primary Key таблицы. Мне такой подход не нравится.

Продолжение темы

Разработка с помощью Code First подхода в Entity Framework достаточно объемная тема. Я не касался вопроса миграций, проблем с многопоточностью (concurrency) и многого другого. Если сообществу интересно, я могу продолжить эту тему в дальнейших статьях.

Материалы:

1. Getting started with Entity Framework 6 Code First using MVC 5

2. Database initialization in Code-First

3. Lerman J., Miller R. — Programming Entity Framework. Code First (2011)
Tags:
Hubs:
+8
Comments 23
Comments Comments 23

Articles