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

В качестве фронт-энда будет ASP.NET MVC, так что создаем соответствующий проект. Выбираем No Authentication — в этом проекте нельзя будет логиниться и весь контент доступен для всех.
Я сделал еще 2 проекта — для бизнес-объектов и DAL, но при желании можно просто создать соответствующие папки в web проекте. Не забудте установить Entity Framework в соответствующие проекты через NuGet.

Создаем классы, которые будут отображать сущности (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.

Скачать пример можно тут.
Недостатки
Во-первых, к существующей базе данных подобный подход применить сложно. Так что вообщем-то это для разработки с нуля.
Часто подножки ставит 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)
