Если вам не нужен код, который я написал, и вы просто хотите обсудить тему абстрагирования существующих абстракций ... перейдите к последним 3 абзацам.Я только что реализовал шаблон Unit Of Work поверх платформы Entity Framework, используя общие хранилища. Что я на самом деле выполнил?
Я познакомился с идеей репозиториев и подразделения работы над платформой Entity Framework через урок Pluralsight, который я наблюдал. Я также замалчивался на собственную страницу Microsoft, подробно описывающую этот процесс: http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application
Так что я решил попробовать. Я намеревался написать свой собственный класс Unit Of Work с родовыми репозиториями поверх Entity Framework, создавая абсолютно все на этом пути, используя интерфейсы, чтобы я мог вводить свои собственные макеты для тестирования.
Прежде всего, для этого упражнения я выбрал простой блог-приложение.
Итак, я начал с DbContext. Должен убедиться, что я использую интерфейс!
public interface ISimpleBlogContext : IDisposable
{
IDbSet<Blog> Blogs { get; }
IDbSet<Post> Posts { get; }
void SaveChanges();
IDbSet<T> Set<T>() where T : class;
DbEntityEntry Entry<T>(T entity) where T : class;
}
Я уверен, что каждый знает, что IDbSets предназначены для, но SaveChanges, методы Set, и запись может выглядеть немного неуместны. Не волнуйтесь, мы доберемся до них.
Так что теперь я подключил свой интерфейс в реальной конкретной DbContext:
public class SimpleBlogContext : DbContext, ISimpleBlogContext
{
public SimpleBlogContext() {
Database.SetInitializer<SimpleBlogContext>(new DropCreateDatabaseAlways<SimpleBlogContext>());
}
public IDbSet<Blog> Blogs { get; set; }
public IDbSet<Post> Posts { get; set; }
public DbEntityEntry Entry<T>(T entity) where T : class
{
return Entry<T>(entity);
}
void ISimpleBlogContext.SaveChanges()
{
SaveChanges();
}
IDbSet<T> ISimpleBlogContext.Set<T>()
{
return Set<T>();
}
инициализатор База данных только обеспечение того, чтобы для этого тестового приложения базы данных отбрасывается и воссозданы каждый раз, когда я запустить приложение. В конце концов, это было просто упражнение, а не реальное приложение. Вы можете увидеть методы SaveChanges, Set и Entry, реализованные здесь, и все они не что иное, как обертки для методов DbContext с тем же именем.
Так что теперь на репозитории ...
Я не собираюсь переписывать практически тот же самый код хранилища для каждого объекта я мог бы добавить к моему заявлению (хотя в данном случае только я ветер используя один репозиторий), поэтому я создал общий репозиторий. Не пропустите интерфейс!
public interface IGenericRepository<T>
where T : class
{
IEnumerable<T> GetAll();
T GetById(object id);
IEnumerable<T> GetByExpression(Expression<Func<T, bool>> expression);
void Add(T entity);
void Delete(T entity);
void Update(T entity);
}
и конкретная версия ... (заметьте, я использую мой ISimpleBlogContext здесь вместо конкретного класса DbContext, потому что я хочу, чтобы быть в состоянии проверить все вещи. Кроме того, теперь вы знаете, почему я должен был писать те, которые, запись и методы SaveChanges в моем ISimpleBlogContext интерфейсе)
public class GenericRepository<T> : IGenericRepository<T>
where T : class
{
public GenericRepository(ISimpleBlogContext context)
{
this.context = context;
}
private ISimpleBlogContext context;
public void Add(T entity)
{
context.Set<T>().Add(entity);
}
public void Delete(T entity)
{
context.Set<T>().Remove(entity);
}
public IEnumerable<T> GetAll()
{
return context.Set<T>().ToList<T>();
}
public IEnumerable<T> GetByExpression(Expression<Func<T, bool>> expression)
{
return context.Set<T>().Where<T>(expression).ToList<T>();
}
public T GetById(object id)
{
return context.Set<T>().Find(id);
}
public void Update(T entity)
{
context.Entry(entity).State = EntityState.Modified;
}
}
и теперь, наконец, единица работы класса
public class UnitOfWork : IDisposable
{
public void Dispose()
{
if (context != null)
{
context.Dispose();
context = null;
}
}
public UnitOfWork()
{
context = new SimpleBlogContext();
}
public UnitOfWork(ISimpleBlogContext context)
{
this.context = context;
}
private ISimpleBlogContext context;
public GenericRepository<TEntity> GetRepository<TEntity>() where TEntity : class
{
return new GenericRepository<TEntity>(context);
}
public void Save()
{
context.SaveChanges();
}
}
Я все еще позволяя ISimpleBlogContext который должен быть передан через перегруженный конструктор, но конструктор по умолчанию - это то, где мы, наконец, получаем наш конкретный объект SimpleBlogContext DbContext.
Так что теперь мне просто нужно проверить все это. Поэтому я написал простое консольное приложение, которое не более чем генерирует пару поддельных блогов с несколькими фальшивыми сообщениями и сохраняет их с помощью класса Unit Of Work. Затем он просматривает блоги и сообщения и распечатывает их, чтобы я мог убедиться, что они были фактически сохранены в базе данных.
P.S. Джейк - моя собака, если тебе интересно, как лаять ...
class Program
{
static void Main(string[] args)
{
UnitOfWork unitOfWork = new UnitOfWork();
GenericRepository<Blog> blogRepository = unitOfWork.GetRepository<Blog>();
Blog paulsBlog = new Blog()
{
Author = "Paul",
Posts = new List<Post>()
{
new Post()
{
Title = "My First Post",
Body = "This is my first post"
},
new Post()
{
Title = "My Second Post",
Body = "This is my second post"
}
}
};
Blog jakesBlog = new Blog()
{
Author = "Jake",
Posts = new List<Post>()
{
new Post()
{
Title = "Dog thoughts",
Body = "Bark bark bark"
},
new Post()
{
Title = "I like barking",
Body = "Bark bark bark"
}
}
};
blogRepository.Add(paulsBlog);
blogRepository.Add(jakesBlog);
unitOfWork.Save();
List<Blog> blogs = blogRepository.GetAll() as List<Blog>;
foreach (Blog blog in blogs)
{
System.Console.WriteLine("ID: {0}, Author: {1}\n", blog.Id, blog.Author);
if (blog.Posts != null && blog.Posts.Count > 0)
{
foreach (Post post in blog.Posts)
{
System.Console.WriteLine("Posted at: {0}, Title: {1}, Body: {2}", post.PostTime, post.Title, post.Body);
}
}
else
{
System.Console.WriteLine("No posts");
}
System.Console.WriteLine("\n");
}
}
}
Это работает. Ура!
Мой вопрос, однако, просто ... что я получил именно этим, выполнив все это?
Разве DbContext не является частью работы, а DbSet уже является хранилищем? Кажется, все, что я сделал, это написать действительно сложную оболочку для обеих вещей без каких-либо дополнительных функций. Вы можете сказать, что это более удобно для тестирования, поскольку все использует интерфейсы, но с издевательскими фреймворками, такими как Moq, уже можно высмеивать DbSets и DbContexts. Мои хранилища носят общий характер, поэтому существует буквально нулевая функциональность, характерная для бизнес-логики. Класс Unit Of Work - это всего лишь оболочка для DbContext, а общие хранилища - это просто оболочки для DbSet.
Может кто-нибудь объяснить мне, почему кто-нибудь это сделает? Я просмотрел урок 4-х Pluralsight по этому поводу, а также пережил все проблемы, связанные с этим, и я все еще не понимаю.
Вы получили ничего, кроме кода bloat – ErikEJ
a DbContext MEANT как единица работы - конечно, это не единица работы сама по себе, так как это в основном ваш DAL. DbSet также не являются репозиториями самостоятельно - это IQueryable, со всеми преимуществами и проблемами, которые это подразумевает. Помимо этого, это в значительной степени оболочка, которая инкапсулирует функциональность. Хотя я никогда не использовал Moq, я не знаю, будет ли это работать, чтобы издеваться над DbContexts, поскольку вы не можете реализовать там перехватчики. Таким образом, в основном это обертка с общим шаблоном и перераспределение фактической задачи выполнения. –
DevilSuichiro
Это [старая дискуссия] (https://ayende.com/blog/3955/repository-is-the-new-singleton). Вы ничего не получаете, вы теряете гибкость. И, кстати, репозиторий не должен распоряжаться контекстом. –