1) Создадим пустой .net core console проект. Добавим поддержку docker-compose, В docker-compose файл добавим БД и pgAdmin
postgres:
image: postgres:11.2-alpine
shm_size: 256M
ports:
- 5432:5432
volumes:
- pg_auth_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin}
volumes:
- pgadmin:/root/.pgadmin
ports:
- "${PGADMIN_PORT:-5050}:80"
volumes:
pg_auth_data:
pgadmin:
2) Установка nuget пакетов
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
3) Добавим контекст
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql("Host=postgres;Database=testdb;Username=postgres;Password=postges");
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
4) Добавим миграции
Убедимся что контекст виден для EF
dotnet ef dbcontext list
PS C:\Users\radiofisik\Desktop\EFTest\EFTest> dotnet ef dbcontext list
EFTest.BloggingContext
Добавим миграцию
dotnet ef migrations add InitialMigration -c BloggingContext -o Migrations/
Убедимся что добавилось корректно
dotnet ef migrations list --context EFTest.BloggingContext
PS C:\Users\radiofisik\Desktop\EFTest\EFTest> dotnet ef migrations list --context EFTest.BloggingContext
20190509224711_InitialMigration
Применим миграции
dotnet ef database update --context EFTest.BloggingContext
PS C:\Users\radiofisik\Desktop\EFTest\EFTest> dotnet ef database update --context EFTest.BloggingContext
Applying migration '20190509224711_InitialMigration'.
Done.
В большинстве случаем контекст в проекте один и команды можно упростить
Как видно в контексте не было упоминаний о том как связаны сущности Blog и Post, хотя обычно это указывается через атрибуты или FluentApi. Тем не менее EF core все понял и создал таблички со связью один ко многим.
Добавим данные
private static void AddData()
{
var blog = new Blog()
{
Url = "https://radiofisik.ru/", Posts = new List<Post>()
{
new Post() {Title = "About this blog", Content = "Github pages is great"},
new Post() {Title = "EF", Content = "ef content"}S
}
};
var context = new BloggingContext();
context.Blogs.Add(blog);
context.SaveChanges();
}
Получить блоги с постами можно
var blogs = context.Blogs.Include(_=>_.Posts).ToList();
Альтернативно можно получить связанные сущности когда они будут нужны через явную загрузку
var firstBlog = context.Blogs.First();
context.Entry(firstBlog).Collection(_=>_.Posts).Load();
var posts = firstBlog.Posts;
Есть несколько распространенных настроек для оптимизации EF Core, особенно полезных в случае если предпологается что запросы будут только на чтение. По умолчанию EF Core наблюдает за изменениями в запрошенных сущностях, и для записи изменений в БД достаточно выполнить SaveChanges.
[Fact]
public void TrackTest()
{
var context = new BloggingContext();
var firstBlog = context.Blogs.First();
Assert.Equal(EntityState.Unchanged,context.Entry(firstBlog).State);
firstBlog.Url = "changedUrl";
Assert.Equal(EntityState.Modified, context.Entry(firstBlog).State);
}
Данное поведение можно изменить так настройка ` context.ChangeTracker.AutoDetectChangesEnabled = false;` позволяет отключить автодетект изменений, при попытке изменений состояние остается Unchanged. Чтобы сохранить измененя надо явно вызывать Update и потом SaveChanges
[Fact]
public void OffAutoDetectChangesEnabledTest()
{
var context = new BloggingContext();
context.ChangeTracker.AutoDetectChangesEnabled = false;
var firstBlog = context.Blogs.First();
Assert.Equal(EntityState.Unchanged, context.Entry(firstBlog).State);
firstBlog.Url = "changedUrl";
Assert.Equal(EntityState.Unchanged, context.Entry(firstBlog).State);
//will not change anything so Don't do it that way
context.SaveChanges();
Assert.Equal(EntityState.Unchanged, context.Entry(firstBlog).State);
context.Blogs.Update(firstBlog);
Assert.Equal(EntityState.Modified, context.Entry(firstBlog).State);
//will save changes
context.SaveChanges();
}
Следующая настройка оптимизации context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
при ее применении после запроса получаем сущность со статусом Detached что значит что EF вообще не в курсе о ее существовании, как будто мы создали ее через new.
[Fact]
public void OffTrackTest()
{
var context = new BloggingContext();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var firstBlog = context.Blogs.First();
Assert.Equal(EntityState.Detached, context.Entry(firstBlog).State);
firstBlog.Url = "changedUrl";
context.Blogs.Update(firstBlog);
Assert.Equal(EntityState.Modified, context.Entry(firstBlog).State);
//will save changes
context.SaveChanges();
}
Получившийся репозиторий https://github.com/Radiofisik/EFTest
Создадим сущность и контекст
class Entity
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
class EntityContext : DbContext
{
public EntityContext(DbContextOptions<EntityContext> options) : base(options)
{
}
public DbSet<Entity> Entities { get; set; }
}
сконфигурируем хранение в памяти и заполним его
var options = new DbContextOptionsBuilder<EntityContext>()
.UseInMemoryDatabase(databaseName: "entityDB")
.Options;
// Insert seed data into the database using one instance of the context
using (var context = new EntityContext(options))
{
context.Entities.Add(new Entity() {Name = "test6"});
context.Entities.Add(new Entity() {Name = "test5"});
context.Entities.Add(new Entity() {Name = "test4"});
context.Entities.Add(new Entity() {Name = "Test3"});
context.Entities.Add(new Entity() {Name = "test1"});
context.Entities.Add(new Entity() {Name = "test2"});
context.SaveChanges();
}
выполним запрос с динамически сформированный с помощью деревьев выражений
using (var context = new EntityContext(options))
{
var sorted = context.Entities.OrderBy(x=>x.Name).ToList();
var parameter = Expression.Parameter(typeof(Entity), "x");
var prop = Expression.Property(parameter, nameof(Entity.Name));
var toLower = typeof(string).GetMethod("ToLower", System.Type.EmptyTypes);
var body = Expression.Call(prop, toLower);
LambdaExpression expression = Expression.Lambda(body, parameter);
var genericOrderBy = _orderBy.MakeGenericMethod(typeof(Entity), typeof(string));
var query = context.Entities.AsQueryable();
var sortedQuery = query.Provider.CreateQuery<Entity>(
Expression.Call(
(Expression) null,
genericOrderBy,
query.Expression,
(Expression) Expression.Quote((Expression) expression))
);
var result = sortedQuery.ToList();
}
Репозиторий https://github.com/Radiofisik/ExpressionTest