I've started to use .NET Core 2 and databases for the first time and looked at examples like: Getting Started with EF Core on .NET Core Console App with a New database.
I've got a few models, like
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
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; }
}
But if I load an object, like Post post = context.Post.SingleOrDefault(s => s.PostId == id) then post.Blog is null. But if I before reading from the database, expand context.Blogs in the debugger, then post.Blog is a valid object after reading from the database with the above command.
So it feels like I need to read the blogs from the database to load those so the reference from Post will be correct. What is the correct way of doing this?
Second question: how to get the database context from anywhere? Should I have a default constructor with a connection string set in the constructor and create a new context all the time?
Take a look here for detailed explanation: https://learn.microsoft.com/en-us/ef/core/querying/related-data
A few examples from the link above:
Eager loading
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
}
Explicit loading
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
}
Lazy loading is not yet supported. It should be coming in v2.1.
Just to keep this updated for future reference, EF Core now supports lazy loading.
https://learn.microsoft.com/en-us/ef/core/querying/related-data
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString);
This should solve the problem.
EF Core does not support lazy loading. You will need to eager load the objects using .Include
The reason it worked when you called the Blogs first, is that the object was available in the context cache and hence it was able to populate the Blog object successfully.
Related
This question already has answers here:
Creating table Entity Framework Core and SQLite
(2 answers)
Closed 12 days ago.
developers. I'm a begginer so please excuse me if I don't know how to explain. So I have this code sample into another class:
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public string DbPath { get; }
public BloggingContext()
{
var folder = Environment.SpecialFolder.LocalApplicationData;
var path = Environment.GetFolderPath(folder);
DbPath = System.IO.Path.Join(path, "blogging.db");
}
// The following configures EF to create a Sqlite database file in the
// special "local" folder for your platform.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={DbPath}");
}
public class Blog
{
public int BlogId { get; set; }
public string? Url { get; set; }
public List<Post> Posts { get; } = new();
}
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; }
}
All i want is to modify the database using Sqlite, this is my Main method:
using var db = new BloggingContext();
Console.WriteLine($"Database Path: {db.DbPath}");
//Create
Console.WriteLine("Insert a new blog");
db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
db.SaveChanges();
//Read
Console.WriteLine("Querying for a blog");
var blog = db.Blogs
.OrderBy(b => b.BlogId)
.First();
//Update
Console.WriteLine("Updating the blog");
blog.Url = "www.blablabla.com";
blog.Posts.Add(new Post { Title = "My first post", Content = "Playing with EntityFramework" });
db.SaveChanges();
//Delete
Console.WriteLine("Deleting what we created");
db.Remove(blog);
db.SaveChanges();
However, the app crashes and it's giving me the following exception:
Microsoft.EntityFrameworkCore.DbUpdateException
Message=An error occurred while saving the entity changes. See the inner exception for details.
Source=Microsoft.EntityFrameworkCore.Relational
Inner Exception 1:
SqliteException: SQLite Error 1: 'no such table: Blogs'.
I would like to know how to fix the issue, I've been looking everywhere but I couldn't find anything wrong. Any advice is helpful. Thanks a lot!
Tried installing all types of packages, nothing worked.
The inner exception says what's the real problem: "SqliteException: SQLite Error 1: 'no such table: Blogs'.". So it says that the table "blogs" doesn't exist in your database. A key concept of entity framework are migrations.
When a data model change is introduced, the developer uses EF Core
tools to add a corresponding migration describing the updates
necessary to keep the database schema in sync. (source)
So you need to create a new migration (if you haven't done that already) and update the database.
I have a simple problem - I would like one of the RESTful endpoints serve a resource DTO (auto-mapped) with its related resources as their IDs only. However there does not seem to be any way to implement it without loading the whole(and heavy) related entities. Consider following (DB first) example model:
public partial class Blog
{
public int Id { get; set; }
public string Url { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public partial class Post // some heavy entity
{
public int Id { get; set; }
public string Content { get; set; }
// other properties
}
and its corresponding DTO
// api/v1/blogs serves collection of following type
public class BlogSlimDto
{
public int Id { get; set; }
public string Url { get; set; }
public int[] PostIds { get; set; }
}
a straightforward soltion would be to fetch all the related Posts from database and discard all data except for the IDs, but that can be inefficient or even unfeasible depending on related Post entity size:
var result = ctx.Blogs.Include(blog => blog.Posts) //fecth everything and discard it on next line
.Select(blog => _mapper.Map<BlogSlimDto>(blog));
// simply use a profile that discards Posts but keeps their Ids, e.g.
// .forMember(dto => dto.PostIds, opt => opt.MapFrom(db.Posts.Select(p => p.Id)))
there is similar question which offers a solution using anonymous types, however this does not play well with Automapper at all:
var result = ctx.Blogs.Select(blog => new {
blog.Id,
blog.Url,
PostIds = blog.Posts.Select(b => b.Id),
}).Select(ablog => _mapper.Map<BlogSlimDto>(ablog)); //throws, no mapping and such mapping cannot be defined
The code above will throw during runtime because there no Automapper mapping defined. Even worse, it cannnot be defined because there is no support for anonymous types in Automapper. Moreover, solutions with one-by-one 'manual' property assignment tend to be difficult to maintain.
Is there an alternative solution that would allow EF query without fetching whole related entities while allowing the result to be auto-mapped to the BlogSlimDto?
You can use the queryable extensions:
https://docs.automapper.org/en/stable/Queryable-Extensions.html
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<OrderLine, OrderLineDTO>()
.ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name)));
public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
using (var context = new orderEntities())
{
return context.OrderLines.Where(ol => ol.OrderId == orderId)
.ProjectTo<OrderLineDTO>().ToList();
}
}
Replacing the Item and OrderLine with your Post and Blogs
I am using Lazy Loading for EF Core 2.2.3 with proxies which works well with DbSets. Now I have to load data from a SQL View and am using DbQuery for this. When trying to load related data for the entity used in the query, I'm getting a DetachedLazyLoadingWarning:
Error generated for warning
'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning:
An attempt was made to lazy-load navigation property 'ProjectStatus'
on detached entity of type 'ProjectProxy'. Lazy-loading is not
supported for detached entities or entities that are loaded with
'AsNoTracking()'.'. This exception can be suppressed or logged by
passing event ID 'CoreEventId.DetachedLazyLoadingWarning' to the
'ConfigureWarnings' method in 'DbContext.OnConfiguring' or
'AddDbContext'.
I don't use AsNoTracking() anywhere in my code.
The DbQuery is defined in OnModelCreating of the context. Excerpt:
modelBuilder.Query<ProjectView>()
.ToQuery(() => Projects
.Select(p => new ProjectView()
{
Id = p.Id,
ProjectCategory = p.ProjectCategory,
ProjectPhase = p.ProjectStatus.ProjectPhase,
}));
Projects is a DbSet of the context.
Project.ProjectCategory is a notMapped-Readonly-Property that uses the relation Project.ProjectStatus.
The Properties of the context:
public virtual DbSet<Project> Projects { get; set; }
public virtual DbSet<ProjectStatus> ProjectStatus { get; set; }
public virtual DbQuery<ProjectView> ProjectViews { get; set; }
Excerpt of the classes:
public partial class Project
{
[NotMapped]
public string ProjectCategory
{
get
{
if (this.ProjectStatus == null)
return string.Empty;
var foo = "someweiredcalculations";
return foo
}
}
public virtual ProjectStatus ProjectStatus { get; set; }
public int ProjectStatusId { get; set; }
public int Id { get; set; }
}
public class ProjectView
{
public int Id { get; set; }
public string ProjectCategory { get; set; }
public string ProjectPhase { get; set; }
}
public partial class ProjectStatus : BaseEntity, IIdEntity<int>
{
public int Id { get; set; }
public string ProjectPhase { get; set; }
public virtual ICollection<Project> Projects { get; set; } = new HashSet<Project>();
}
How can I make Lazy Loading work for this DbQuery?
Thank you very much.
This is just a bug of sorts (haven't found work around and issue is still open). Also read this. As advised and I’m quoting
Note that the warning can be configured to not throw using ConfigureWarnings in the DbContextOptionsBuilder.
This seems to be by design, regardless whether .AsNoTracking() is used or not. I did not find any documentation on this.
If you are stuck on .net core 2 the only workaround I found was to load related entities in a separate query. Effectively breaking lazy loading.
As of .net core 3+ it is possible to work around this by eager loading related properties (.Include(x => x.ProjectStatus)) when Lazy Loading is Used. This was fixed by: https://github.com/aspnet/EntityFrameworkCore/issues/12780. And the error message when using lazy loading was changed to: 'Unable to track an instance of type 'FooQuery' because it does not have a primary key. Only entity types with primary keys may be tracked.'
I am building sort of multi tenant application with shared tables using .NET Core 2.0 and EF Core.
I am also using generic repository together with Unit of Work if it matters.
I want to make it properly secured and also avoid repeating the logic, so I think if it's possible to somehow modify the DbContext which I am using to for every find operation add something like: entity => entity.tenantId == userContext.tenantId.
I also have to ensure that while creating the correct tenantId is applied and do not authorize update of other tenant property, but so far this logic is included in Service Layer - correct me if I am wrong with this approach?
The IUserContext is defined in Domain abstractions and the application layer implements it differently (API or Web App), but I am not sure if it is not code smell/anti pattern when data layer is doing this kind of logic? ( I am afraid it is).
Should this logic go to the Services (it will then have to be repeated many times which is not good idea I think), DbContext or should I adjust the repository in some way?
So what you want is that if someone would write some Linq statement like this
var result = myDbcontext.myDbSet.SomeLinq(...)
It would internally be like
var result = myDbContext.myDbSet
.Where(entity => entity.tenantId == userContext.tenantId)
.SomeLinq(...)
So what you should do, is that when users think that they access myDbContext.myDbSet, they actually get the subset where tenantId == userContext.tenantId
I think the neat solution would be to create a class that exposes an IQueryable for every DbSet in your DbContext and hides the actual DbContext.
Something like this:
class MyOriginalDbContext : DbContext
{
public DbSet<Student> Students {get; set;}
public DbSet<Teacher> Teachers {get; set;}
public DbSet<ClassRoom> ClassRooms {get; set;}
...
}
public MyLimitedContext : IDisposable
{
// to be filled in constructor
private readonly MyOriginalDbcontext dbContext = ...
private readonly int tenantId = ...
IQueryable<Student> Students
{
get
{
return this.dbContext.Students
.Where(student => student.tenantId == tenantId);
}
}
IQueryable<Student> Teachers
{
get
{
return this.dbContext.Teachers
.Where(teacher => teacher.tenantId == tenantId);
}
}
...
Users won't notice the difference:
using (var dbContext = new MyLimitedContext(...))
{
var TeachersWithTheirStudents = dbContext.Teachers
.Join(dbContext.Student)
.GroupBy(teacher => teacher.Id,
...
}
You can use Global Query Filters. Read more here.
Such filters are automatically applied to any LINQ queries involving
those Entity Types, including Entity Types referenced indirectly, such
as through the use of Include or direct navigation property references
An example:
public class Blog
{
private string _tenantId;
public int BlogId { get; set; }
public string Name { 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 bool IsDeleted { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
// Configure entity filters
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
You can even disable filter for individual LINQ query if you need:
blogs = db.Blogs
.Include(b => b.Posts)
.IgnoreQueryFilters()
.ToList();
I developed and uploaded a web service to Azure using Entity Framework 6.1.3 with MVC design pattern.
So let's imagine I have a Workshop that can have many Clients and a Client that can have many Workshops.
So far my results have been null, empty values and some times correct values but without the relationship (no clients inside my workshop, and the other way around).
This is what I have at this point:
public class Workshop
{
public Workshop()
{
this.Clients = new HashSet<Client>();
this.ModuleSets = new HashSet<ModuleSet>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Client> Clients { get; set; }
public virtual ICollection<ModuleSet> ModuleSets { get; set; }
}
public class Client
{
public Client()
{
this.Workshops = new HashSet<Workshop>();
this.Vehicles = new HashSet<Vehicle>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Workshop> Workshops { get; set; }
public virtual ICollection<Vehicle> Vehicles { get; set; }
}
Yes I have more relations going on at the same time.
Since that alone was not giving me anything, I added some Fluent Api, like this:
modelBuilder.Entity<Workshop>().
HasMany(c => c.Clients).
WithMany(p => p.Workshops).
Map(
m =>
{
m.MapLeftKey("Workshop_Id");
m.MapRightKey("Client_Id");
m.ToTable("WorkshopClients");
});
The names that are shown are the ones that are in the table WorkshopClients (auto generated by entity framework).
I also read this article to make sure I was doing the right thing when it came to Fluent API.
How to define Many-to-Many relationship through Fluent API Entity Framework?
And this is my simple request on the client:
var request = new RestRequest("api/Workshops") { Method = Method.GET };
var workshopList = await api.ExecuteAsync<List<Workshop>>(request);
API/Workshops method:
// GET: api/Workshops
public IQueryable<Workshop> GetWorkshops()
{
return db.Workshops;
}
It looks like you are not using lazy loading or that part is lost when you pass the data over the API. Make sure you tell your API to include the child objects:
public IQueryable<Workshop> GetWorkshops()
{
return db.Workshops
.Include(w => w.Clients);
}
Note: You may need to add using System.Data.Entity; to use the lambda version of Include, otherwise you can use the string version.
I would recommend keeping your mappings in a separate mapping file, but if you are going to do it in the OnModelCreating method then this should do what you need.
modelBuilder.Entity<Workshop>()
.HasRequired(c => c.Clients) //or HasOptional depending on your setup
.WithMany(d => d.Workshop)
.HasForeignKey(d => new { d.ID });
modelBuilder.Entity<Clients>()
.HasRequired(c => c.Workshop) //same
.WithMany(d => d.Clients)
.HasForeignKey(d => new { d.ID });
Also in both entities add this to your ID properties:
[Key]
public int Id { get; set; }