C# exception when modifying database with EntityFramework (Sqlite) [duplicate] - c#

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.

Related

Error Object Id' is unknown when attempting to save changes. Adding Many to Many to the DB

I have objects with many to many relationship.
public class Executor
{
public long Id { get; set; }
public string Name { get; set; }
public List<Competency> Competency { get; set; }
}
public class Competency
{
public long Id { get; set; }
public string CompetencyName { get; set; }
public List<Executor> Executor { get; set; }
}
I am using EF Core 5 and PostgreSQL DB. I can`t just add new Executor to DB, first I need to find all competencies in the DB because of this problem.
So, my code now is like this:
public async Task<ServiceResponse<ExecutorDto>> AddExecutor(ExecutorDto newExecutor, long userId)
{
var serviceResponse = new ServiceResponse<ExecutorDto>();
try
{
var executor = _mapper.Map<Executor>(newExecutor);
executor.Competency.Clear();
executor.Competency = _context.Competencies.Where(i => newExecutor.Competency.Contains(i)).ToList();
_context.Executors.Add(executor);
await _context.SaveChangesAsync();
...
But on the Save moment I have error.
The value of 'CompetencyExecutor (Dictionary<string, object>).CompetencyId' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
I was trying to resolve this in many ways, but I can`t find the solution.
Well, it was stupid, the problem was because one of the Competency in the List has Id=0. PostreSQL recognises 0 as NULL. Just need to change Id to 1 or another positive number.

How to properly write seed method in Entity Framework Core?

I am learning ASP.NET and I am using EF Core in my project. I would like to seed some test data when I'm running my migrations. Here's my AppDbContext class and my model classes:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Game> Games { get; set; }
public DbSet<Studio> Studios { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var converter = new EnumToNumberConverter<Genre, byte>();
builder.Entity<Game>().Property(item => item.Genre).HasConversion(converter);
}
}
public class Person
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(30)]
public string Name { get; set; }
[Required]
[StringLength(30)]
public string Surname { get; set; }
}
public class Game
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[Range(0.0, 1000.0)]
public double Price { get; set; }
public Genre Genre { get; set; }
[Required]
public Studio Studio { get; set; }
}
public class Studio
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public IEnumerable<Game> Games { get; set; }
[Required]
public decimal Budget { get; set; }
public IEnumerable<Person> Employees { get; set; }
}
As you could notice there's a little bit complicated relation between Game and Studio entities. Not only game has a studio which created the game, but also a studio has a list of games developed by them.
If I had to write OnModelCreating method, I would firstly create some test people and then use them to fill Employees field in Studio class. The problem occurs when I have to create games. Studio does not exist yet and if I wanted to create studios first, games are a missing element.
Do I have to manually create some games, ignoring the Studio reference, then instantiate studios, and then manually link object together, or is there any better solution? Best regards.
Maybe it's because people only read the title, but everybody jumped to how to seed in Entity Framework, period. Next, some came up with AddOrUpdate which doesn't exist in EF core.
Seeding in EF core has changed dramatically compared to EF6. In EF6's Seed method it was possible to save an object graph, i.e. objects containing references and collections. However, the AddOrUpdate method had a couple of issues (I won't spell them out here) that made its behavior very hard to follow or even get right if you weren't aware of them.
As an (over)reaction to that, the EF team decided to no longer let EF determine whether an entity should be added or updated. Seeding should either add an entity if it's non-existing, or do nothing otherwise. Never update. These considerations lead to a greatly (over)simplified seeding mechanism in EF core:
Entities should be identified by their hard-coded primary keys
. If necessary, they're inserted with IDENTITY INSERT ON (in Sql Server).
Foreign keys should also be hard coded.
Navigation properties can't be populated. If they are, EF will throw an exception like
InvalidOperationException: The seed entity for entity type 'Studio' cannot be added because it has the navigation 'Games' set. To seed relationships you need to add the related entity seed to 'Game' and specify the foreign key values {'StudioId'}.
That means that you have to add StudioId to Game. And StudioId and GenreId to Person. Independent associations (a Studio reference without StudioId) aren't supported. (I think this is a far-reaching architectural decision).
Doing that, your seeding code, simplified a bit, could look like:
var games = new[]
{
new Game{ Id = 1, Name = "Game1", StudioId = 1 },
new Game{ Id = 2, Name = "Game2", StudioId = 1 },
};
var studio = new Studio
{
Id = 1,
Name = "Studio1",
};
modelBuilder.Entity<Studio>().HasData(studio);
modelBuilder.Entity<Game>().HasData(games);
The circular reference Studio ⟷ Game doesn't matter here because it represents only one foreign key.
However, circular references over two foreign keys are impossible. Suppose Studio had a Director property of type Person referred to by DirectorId and the director is also an employee:
var director = new Person { Id = 1, Name = "Director1", StudioId = 1 }; // Employee of Studio1
var studio = new Studio
{
Id = 1,
Name = "Studio1",
DirectorId = 1 // Director is "Director1"
};
Now there's a chicken-and-egg problem: both entities can only be inserted if the other one is inserted first.
InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved.
I think that's another far-reaching consequence of this design decision. In EF6, even with its crippled AddOrUpdate, at least it was possible to call SaveChanges twice to accommodate this scenario.
Considering that on top of all this, seeding isn't migration-friendly my stance on data seeding is: either don't support it (I wouldn't mind) or support it well.
You should use ModelBuilder to seed data :
modelBuilder.Entity<Post>().HasData(
new Post() { BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1" });
protected override async void Seed(ApplicationContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data.
context.Test.AddOrUpdate(x => x.Id,
new Test() { Id = 1, Name = "Test" }
);
await context.SaveChangesAsync();
}
in PM insert:
1. Enable-Migrations
2. Add-migration Initial
3. Update-database
if you don't like to use "ef core modelbuilder" or "ef core migrations", you can create default records in application layer. You don't need modelbuilder or migartions. Like this.
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
SeedData.Initialize(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider);
app.UseDeveloperExceptionPage();
}
// your code...
}
SeedData.cs
internal class SeedData
{
static AppDbContext _dataContext;
internal static void Initialize(IServiceProvider serviceProvider)
{
_dataContext = (AppDbContext)serviceProvider.GetService(typeof(AppDbContext));
var ensureCreated = _dataContext.Database.EnsureCreated();
if (ensureCreated)
{
CreatePersonData();
CreateGameData();
CreateStudioData();
try
{
_dataContext.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
}
private static void CreatePersonData() { /* your code... */ }
private static void CreateGameData() { /* your code... */ }
private static void CreateStudioData() { /* your code... */ }
}
I would create an extension method to seed your data:
public static class DataContextExtensions
{
public static void EnsureSeedDataForContext(this AppDbContext context)
{
// first, clear the database. This ensures we can always start fresh. This is optional !
context.Games.RemoveRange(context.Games);
context.SaveChanges();
// init seed data
var games = new List<Games>()
{
new Games()
{
Id = new Guid("25320c5e-f58a-4b1f-b63a-8ee07a840bdf"),
Name = "Stephen King",
Price= 14.99
Genre = new Genre(),
Studio = new Studio(),
}
}
//can seed other context data here
context.Games.AddRange(games);
context.SaveChanges();
}
}
Then in your startup.cs file on the configure method you can call the extension method and then do your migrations commands:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, AppDBContext appDbContext)
{
appDbContext.EnsureSeedDataForContext();
app.UseHttpCacheHeaders();
app.UseMvc();
}
To me its not complicated and easy to work with when it comes to stubbing data. But the above examples are great too ! Hope this helps.

.NET Core 2 EF, object references are null

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.

Why does my ASP.NET MVC 4 application create new entities instead of updating the old ones?

EDIT: The solution I selected probably wasn't the best, but it definitely worked. I'll be going through my code over the next week (once this project is done) and I'll update my question when I understand what went wrong.
I'm using the ASP.NET MVC 4 framework with Entity 5. Here's some code:
The class to be instantiated and saved (fresh) in the database:
public class ClassCancellation
{
[Key]
public int Id { get; set; }
public Faculty Professor { get; set; }
public DateTime CancelledOn { get; set; }
public Course Course { get; set; }
[Required]
public ClassDate ClassCancelled { get; set; }
public Message CancellationMessage { get; set; }
[Required]
public List<Student> Students { get; set; }
}
It's mapped from the viewmodel called CancellationFull (with AutoMapper):
public class CancellationForList
{
[Key]
public int Id { get; set; }
public CourseForList Course { get; set; }
public ClassDateForList ClassCancelled { get; set; }
}
public class CancellationFull : CancellationForList
{
public CancellationFull()
{
this.Students = new List<StudentForList>();
}
public FacultyForList Professor { get; set; }
public MessageForList CancellationMessage { get; set; }
public DateTime CancelledOn { get; set; }
public List<StudentForList> Students { get; set; }
}
This is the repo method that turns a CancellationFull into a ClassCancellation and then saves it to the database:
public CancellationFull createClassCancellation(CancellationFull c)
{
ClassCancellation newCancellation = Mapper.Map<ClassCancellation>(c);
dc.ClassCancellations.Add(newCancellation);
dc.SaveChanges();
return Mapper.Map<CancellationFull>(dc.ClassCancellations.FirstOrDefault(cc => cc.Id == newCancellation.Id));
}
Why, for the love of god why, does the database create new objects for Faculty and Course when the Id (primary key) of each's existing entity counterpart is provided? It might also be doing the same with Student objects but I haven't looked that closely.
Before the ClassCancellation instance is saved to the database the debugger shows that it's attributes Professor of type Faculty and Course of type Course have the correct primary key - that is, the primary key of the already existing entities of those types that I'm trying to update with a reference to the new ClassCancellation object.
Driving me nuts. Feel free to ask for clarification!
EDIT:
Here's the logic where the CancellationFull viewmodel is constructed from form data and viewmodels about existing objects retrieved from their respective repos:
newCancellation = new CancellationFull();
newCancellation.CancelledOn = DateTime.Now;
newCancellation.ClassCancelled = repoClass.getClassDateForListById(Int32.Parse(classIds[i]));
newCancellation.Course = repoCourse.getForList(newCancellation.ClassCancelled.Course.Id);
newCancellation.CancellationMessage = repoMessage.getMessageForList(newMessage.Id);
newCancellation.Professor = repoFac.getFacultyForList((int)Session["facId"]);
var students = repoStudent.getStudentsForListByCourse(newCancellation.Course.Id);
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
repoCancellation.createClassCancellation(newCancellation);
Here's an example of one of those repo methods (the rest are very similar):
public CourseForList getForList(int? id)
{
return Mapper.Map<CourseForList>(dc.Courses.FirstOrDefault(c => c.Id == id));
}
What I find the easiest solution is when updating a model, clear any related entities, then re add them.
ie:
newCancellation.Students.Clear();
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
Try using Attach() instead of Add()
dc.ClassCancellations.Attach(newCancellation);
dc.SaveChanges();
Add() is used for new objects that do not already exist in the database. Attach() is used for creating relationships to entities that already exist in the database.
EDIT
Without seeing your code, the best solution I can recommend to attach is to create a 'stub' instance and then attach that to your newCancellation:
var existingCourse = new Course{ Id = newCancellation.ClassCancelled.Course.Id };
db.Courses.Attach(existingCourse);
newCancellation.Course = existingCourse;
The problem is that you have multiple contexts, or units of work. When you add the newCancellation to the dc context, it also adds any related entity in the object graph that is not tracked in the dc context. I think your best option is:
dc.ClassCancellations.Add(newCancellation);
dc.Entry(newCancellation.Course).State = EntityState.Unchanged;
dc.Entry(newCancellation.Faculty).State = EntityState.Unchanged;
See Julie Lerman's article on this issue for an explanation and other options.
In my opinion, EF should recognize entities that have autonumbered keys and not insert them if the key is assigned.

Pattern required for adding/updating entities in EF5 including relationships

I am using VS 2010 with Entity Framework 5 code first and C# and have a web application (hence disconnected entities). I am used to working with SQL queries directly but am very new to EF and code first.
I have two classes:
public class User
{
public int UserID {get; set;}
public string UserName { get; set; }
public bool IsSuspended { get; set; }
public int UnitID { get; set; }
public virtual MyTrust MyTrusts { get; set; }
}
public class MyTrust
{
public int MyTrustID { get; set; }
public string MyTrustName { get; set; }
public string Region { get; set; }
public bool DoNotUse { get; set; }
}
and my DbContext class contains:
public DbSet<MyTrust> MyTrust { get; set; }
public DbSet<User> Users { get; set; }
modelBuilder.Entity<User>()
.HasRequired(m => m.MyTrust);
The MyTrust entity will not be changed
There are three scenarios I am interested in:
Adding a user with an existing MyTrust
Updating a user with no change to the trust
Updating a user with a change to the trust
When the website returns the data the MyTrust object has only the MyTrustID set. When I update/add the user the MyTrust record is also updated.
CLARIFICATION The relationship in the User object is NOT updated; the actual MyTrust object is updated with the data returned from the website; as most fields are empty this is corrupting the object AND not achieving the required update of the User record.
In fact, the problem seems to boil down to the fact that the wrong end of the relationship is being updated.
I have looked at some many examples I cannot see a simple solution.
Can anyone please suggest a straightforward pattern for this (it was so easy in the SQL days).
UPDATE
I resolved this by adding specific keys to the User and MyTrust classes.
public int NHSTrustID { get; set; }
and a matching key in the MyTrust class.
In retrospect the question was wrong. I wasn't after patterns but the solution to a specific problem.
I've given some examples below - I've done them from memory but hopefully will give you a good starting point:
Adding a user with an existing MyTrust
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Added
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.Entry(myUser.MyTrusts).Property(x => x.MyTrustName).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.Region).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.DoNotUse).IsModified = false;
context.SaveChanges();
}
Updating a user with no change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Unchanged;
context.SaveChanges();
}
Updating a user with a change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.SaveChanges();
}

Categories

Resources