Повсеместно принято, что в «серьезных» 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();
}
}
Последнее, что надо сделать — добавить информацию в 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)
Автор: