EF Migration - Unable to create an object of [DbContext] - c#

I have a project which I started but can not seem to create the initial migration. Might just be missing something really stupid.
My Project is layout out like this:
MovieFinder.Core - Contains all the entities
MovieFinder.Infrastructure - Contains the DBContext
Moviefinder.Web - the front end application
This is my DBContext which is in the MovieFinder.Infrastructure project:
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MovieFinder.Core.Entities;
namespace MovieFinder.Infrastructure.Data;
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options) : base(options)
{
}
public DbSet<Director> Directors { get; set; }
public DbSet<Episode> Episodes { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Name> Names { get; set; }
public DbSet<NameProfession> NameProfessions { get; set; }
public DbSet<Principal> Principals { get; set; }
public DbSet<Profession> Professions { get; set; }
public DbSet<Rating> Ratings { get; set; }
public DbSet<Title> Titles { get; set; }
public DbSet<TitleGenre> TitleGenres { get; set; }
public DbSet<TitleName> TitleNames { get; set; }
public DbSet<Writer> Writers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties()
.Where(p => p.PropertyType == typeof(decimal));
var dateTimeProperties = entityType.ClrType.GetProperties()
.Where(p => p.PropertyType == typeof(DateTimeOffset));
foreach (var property in properties)
modelBuilder.Entity(entityType.Name).Property(property.Name)
.HasConversion<double>();
foreach (var property in dateTimeProperties)
modelBuilder.Entity(entityType.Name).Property(property.Name)
.HasConversion(new DateTimeOffsetToBinaryConverter());
}
}
}
I have added the AddDbContext to the ConfigureService method in the Startup.cs in the MovieFinder.Web project.
services.AddDbContext<MovieContext>(opt =>
{
opt.UseSqlite(_config.GetConnectionString("DefaultConnection"),
x => x.MigrationsAssembly("MovieFinder.Infrastructure"));
});
The Connectionstring is stored in the appsettings.development.json file:
{
"ConnectionStrings": {
"DefaultConnection": "Data source=MovieFinder.db"
}
}
So unless I have missed something, I believe I have everything set up. When I run the command dotnet ef migrations add InitialDb -o Data/Migrations --startup-project MovieFinder.Web --project MovieFinder.Infrastructure I get the following error.
Unable to create an object of type 'MovieContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
Another really helpful Microsoft message. lol

Related

Unable to create database

When running my program I get this exception: 'Microsoft.Data.Sqlite.SqliteException: 'SQLite Error 1: 'no such table: Tasks'.'
I followed the steps on the Microsoft docs to install EFcore, create the model and create the database. This did the trick on one of my other Maui projects but not on this one since I can't get it to to create the table 'Tasks'.
using Microsoft.EntityFrameworkCore;
using Task = Todo.Models.Task;
namespace Todo.Data
{
class TaskContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
public string DbPath { get; }
public TaskContext()
{
var folder = Environment.SpecialFolder.LocalApplicationData;
var path = Environment.GetFolderPath(folder);
DbPath = System.IO.Path.Join(path, "ToDo.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}");
}
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Todo.Models
{
public class Task
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public bool IsCompleted { get; set; }
}
}
Edit: Adding Database.EnsureCreated() in the TaskContext constructor fixed it

How to store JSON in an entity field with EF Core?

I am creating a reusable library using .NET Core (targeting .NETStandard 1.4) and I am using Entity Framework Core (and new to both). I have an entity class that looks like:
public class Campaign
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public JObject ExtendedData { get; set; }
}
and I have a DbContext class that defines the DbSet:
public DbSet<Campaign> Campaigns { get; set; }
(I am also using the Repository pattern with DI, but I don't think that is relevant.)
My unit tests give me this error:
System.InvalidOperationException: Unable to determine the relationship
represented by navigation property 'JToken.Parent' of type
'JContainer'. Either manually configure the relationship, or ignore
this property from the model..
Is there a way to indicate that this is not a relationship but should be stored as a big string?
Going to answer this one differently.
Ideally the domain model should have no idea how data is stored. Adding backing fields and extra [NotMapped] properties is actually coupling your domain model to your infrastructure.
Remember - your domain is king, and not the database. The database is just being used to store parts of your domain.
Instead you can use EF Core's HasConversion() method on the EntityTypeBuilder object to convert between your type and JSON.
Given these 2 domain models:
public class Person
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[Required]
public DateTime DateOfBirth { get; set; }
public IList<Address> Addresses { get; set; }
}
public class Address
{
public string Type { get; set; }
public string Company { get; set; }
public string Number { get; set; }
public string Street { get; set; }
public string City { get; set; }
}
I have only added attributes that the domain is interested in - and not details that the DB would be interested in; I.E there is no [Key].
My DbContext has the following IEntityTypeConfiguration for the Person:
public class PersonsConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
// This Converter will perform the conversion to and from Json to the desired type
builder.Property(e => e.Addresses).HasConversion(
v => JsonConvert.SerializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
v => JsonConvert.DeserializeObject<IList<Address>>(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));
}
}
With this method you can completely decouple your domain from your infrastructure. No need for all the backing field & extra properties.
The key to making the the Change Tracker function correctly is to implement a ValueComparer as well as a ValueConverter. Below is an extension to implement such:
public static class ValueConversionExtensions
{
public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder) where T : class, new()
{
ValueConverter<T, string> converter = new ValueConverter<T, string>
(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<T>(v) ?? new T()
);
ValueComparer<T> comparer = new ValueComparer<T>
(
(l, r) => JsonConvert.SerializeObject(l) == JsonConvert.SerializeObject(r),
v => v == null ? 0 : JsonConvert.SerializeObject(v).GetHashCode(),
v => JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(v))
);
propertyBuilder.HasConversion(converter);
propertyBuilder.Metadata.SetValueConverter(converter);
propertyBuilder.Metadata.SetValueComparer(comparer);
propertyBuilder.HasColumnType("jsonb");
return propertyBuilder;
}
}
Example of how this works.
public class Person
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[Required]
public DateTime DateOfBirth { get; set; }
public List<Address> Addresses { get; set; }
}
public class Address
{
public string Type { get; set; }
public string Company { get; set; }
public string Number { get; set; }
public string Street { get; set; }
public string City { get; set; }
}
public class PersonsConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
// This Converter will perform the conversion to and from Json to the desired type
builder.Property(e => e.Addresses).HasJsonConversion<IList<Address>>();
}
}
This will make the ChangeTracker function correctly.
#Michael's answer got me on track but I implemented it a little differently. I ended up storing the value as a string in a private property and using it as a "Backing Field". The ExtendedData property then converted JObject to a string on set and vice versa on get:
public class Campaign
{
// https://learn.microsoft.com/en-us/ef/core/modeling/backing-field
private string _extendedData;
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[NotMapped]
public JObject ExtendedData
{
get
{
return JsonConvert.DeserializeObject<JObject>(string.IsNullOrEmpty(_extendedData) ? "{}" : _extendedData);
}
set
{
_extendedData = value.ToString();
}
}
}
To set _extendedData as a backing field, I added this to my context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Campaign>()
.Property<string>("ExtendedDataStr")
.HasField("_extendedData");
}
Update: Darren's answer to use EF Core Value Conversions (new to EF Core 2.1 - which didn't exist at the time of this answer) seems to be the best way to go at this point.
For those using EF 2.1 there is a nice little NuGet package EfCoreJsonValueConverter that makes it pretty simple.
using Innofactor.EfCoreJsonValueConverter;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class Campaign
{
[Key]
public Guid Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public JObject ExtendedData { get; set; }
}
public class CampaignConfiguration : IEntityTypeConfiguration<Campaign>
{
public void Configure(EntityTypeBuilder<Campaign> builder)
{
builder
.Property(application => application.ExtendedData)
.HasJsonValueConversion();
}
}
I have made a solution based on Robert Raboud's contribution. The change made by me is that my implementation uses a HasJsonConversion method that depends on the System.Text.Json package rather than Newtonsofts library:
public static PropertyBuilder<T> HasJsonConversion<T>(this PropertyBuilder<T> propertyBuilder) where T : class, new()
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
AllowTrailingCommas = true,
PropertyNameCaseInsensitive = true
};
ValueConverter<T, string> converter = new ValueConverter<T, string>
(
v => JsonSerializer.Serialize(v, options),
v => JsonSerializer.Deserialize<T>(v, options) ?? new T()
);
ValueComparer<T> comparer = new ValueComparer<T>
(
(l, r) => JsonSerializer.Serialize(l, options) == JsonSerializer.Serialize(r, options),
v => v == null ? 0 : JsonSerializer.Serialize(v, options).GetHashCode(),
v => JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(v, options), options)
);
propertyBuilder.HasConversion(converter);
propertyBuilder.Metadata.SetValueConverter(converter);
propertyBuilder.Metadata.SetValueComparer(comparer);
propertyBuilder.HasColumnType("LONGTEXT");
return propertyBuilder;
}
Note also that this implementation expects for the column to be LONGTEXT since I am using a MySQL setup.
Here's something I used
Model
public class FacilityModel
{
public string Name { get; set; }
public JObject Values { get; set; }
}
Entity
[Table("facility", Schema = "public")]
public class Facility
{
public string Name { get; set; }
public Dictionary<string, string> Values { get; set; } = new Dictionary<string, string>();
}
Mapping
this.CreateMap<Facility, FacilityModel>().ReverseMap();
DBContext
base.OnModelCreating(builder);
builder.Entity<Facility>()
.Property(b => b.Values)
.HasColumnType("jsonb")
.HasConversion(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<Dictionary<string, string>>(v));
Could you try something like this?
[NotMapped]
private JObject extraData;
[NotMapped]
public JObject ExtraData
{
get { return extraData; }
set { extraData = value; }
}
[Column("ExtraData")]
public string ExtraDataStr
{
get
{
return this.extraData.ToString();
}
set
{
this.extraData = JsonConvert.DeserializeObject<JObject>(value);
}
}
here is the migration output:
ExtraData = table.Column<string>(nullable: true),
For developers, who work with EF Core 3.1 and meet such error ("The entity type 'XXX' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.") the solution is:
Move .HasConversion() method with it's lambda from:
OrderConfiguration : IEntityTypeConfiguration<T> to
OnModelCreating(ModelBuilder modelBuilder) in your DataContext.
// DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entityTypes = modelBuilder.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
foreach (var property in entityType.ClrType.GetProperties().Where(x => x != null && x.GetCustomAttribute<HasJsonConversionAttribute>() != null))
{
modelBuilder.Entity(entityType.ClrType)
.Property(property.PropertyType, property.Name)
.HasJsonConversion();
}
}
base.OnModelCreating(modelBuilder);
}
Create an attribute to handle the properties of the entities.
public class HasJsonConversionAttribute : System.Attribute
{
}
Create extention class to find Josn properties
public static class ValueConversionExtensions
{
public static PropertyBuilder HasJsonConversion(this PropertyBuilder propertyBuilder)
{
ParameterExpression parameter1 = Expression.Parameter(propertyBuilder.Metadata.ClrType, "v");
MethodInfo methodInfo1 = typeof(Newtonsoft.Json.JsonConvert).GetMethod("SerializeObject", types: new Type[] { typeof(object) });
MethodCallExpression expression1 = Expression.Call(methodInfo1 ?? throw new Exception("Method not found"), parameter1);
ParameterExpression parameter2 = Expression.Parameter(typeof(string), "v");
MethodInfo methodInfo2 = typeof(Newtonsoft.Json.JsonConvert).GetMethod("DeserializeObject", 1, BindingFlags.Static | BindingFlags.Public, Type.DefaultBinder, CallingConventions.Any, types: new Type[] { typeof(string) }, null)?.MakeGenericMethod(propertyBuilder.Metadata.ClrType) ?? throw new Exception("Method not found");
MethodCallExpression expression2 = Expression.Call(methodInfo2, parameter2);
var converter = Activator.CreateInstance(typeof(ValueConverter<,>).MakeGenericType(typeof(List<AttributeValue>), typeof(string)), new object[]
{
Expression.Lambda( expression1,parameter1),
Expression.Lambda( expression2,parameter2),
(ConverterMappingHints) null
});
propertyBuilder.HasConversion(converter as ValueConverter);
return propertyBuilder;
}
}
Entity example
public class Attribute
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[HasJsonConversion]
public List<AttributeValue> Values { get; set; }
}
public class AttributeValue
{
public string Value { get; set; }
public IList<AttributeValueTranslation> Translations { get; set; }
}
public class AttributeValueTranslation
{
public string Translation { get; set; }
public string CultureName { get; set; }
}
Download Source
For those who are working on entity framework core 5.0 and above. below can work if you are getting error like below
The entity type '{EntityName}' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. The error is asking to define a primary key on the model
Try this
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Person>(
eb =>
{
eb.Property(p => p.Addresses).HasConversion(
v => JsonConvert.SerializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }),
v => JsonConvert.DeserializeObject<IList<Address>>(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })
);
});
}
----------------- 2022 Update -----------------
Hey there,
Just sharing an update from Dec/2022.
Recently, EF Core 7.0 was launched, containing one of the most awaited features called JSON Columns.
This new feature allows us for mapping aggregates (written from .NET types) into JSON documents.
Just remember that, In EF Core, aggregate types are defined using Owned Entity Types.
https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities
Let's consider this scenario: A object called "LogDetail", that needs to be stored into a single column as a JSON, inside another object (or table) called "Log".
public class Log : BaseEntity
{
public string TraceID { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public LogDetail LogDetail { get; set; } = null!;
public string IpAddress { get; set; } = string.Empty;
}
public class LogDetail
{
public string InnerException { get; set; } = null!;
public string MemberMap { get; set; } = null!;
public string Message { get; set; } = null!;
public string Source { get; set; } = null!;
public string StackTrace { get; set; } = null!;
public string TypeMap { get; set; } = null!;
public string Path { get; set; } = null!;
}
By the end, all that you need is to configure the behavior on your configuration map class:
public sealed class LogMap : IEntityTypeConfiguration<Log>
{
public override void Configure(EntityTypeBuilder<Log> builder)
{
/* Owned Type Configurations */
builder.OwnsOne(e => e.LogDetail, options =>
{
options.ToJson("LOG_DETAIL");
});
}
}
The comment by #Métoule:
Be careful with this approach: EF Core marks an entity as modified only if the field is assigned to. So if you use person.Addresses.Add, the entity won't be flagged as updated; you'll need to call the property setter person.Addresses = updatedAddresses.
made me take a different approach so that this fact is obvious: use Getter and Setter methods, rather than a property.
public void SetExtendedData(JObject extendedData) {
ExtendedData = JsonConvert.SerializeObject(extendedData);
_deserializedExtendedData = extendedData;
}
//just to prevent deserializing more than once unnecessarily
private JObject _deserializedExtendedData;
public JObject GetExtendedData() {
if (_extendedData != null) return _deserializedExtendedData;
_deserializedExtendedData = string.IsNullOrEmpty(ExtendedData) ? null : JsonConvert.DeserializeObject<JObject>(ExtendedData);
return _deserializedExtendedData;
}
You could theoretically do this:
campaign.GetExtendedData().Add(something);
But it's much more clear that That Doesn't Do What You Think It Does™.
If you're using database-first and using some kind of class auto-generator for EF, then the classes will usually be declared as partial, so you can add this stuff in a separate file that won't get blown away the next time you update your classes from your database.

Entity Framework Core error not seen before Class.TempProperty is of type 'object' which is not supported by current database provider

I am using Entity Framework Core code-first with fluent API entity configurations, in an ASP .NET MVC Core application. My code currently compiles, but when I run add-migration in the Package Manager Console, it gives the error below:
The property 'Exam.TempId' is of type 'object' which is not supported
by current database provider. Either change the property CLR type or
manually configure the database type for it.
Searching Google for this error yields no results. Can anybody here help please?
"Exam" is a class in my domain model, but it doesn't have a "TempId" property so I guess that's something that Entity Framework is adding. It does have an "Id" property, but the type is int, not object.
I'll start by sharing the Exam class and the Exam configuration class. I can share more code if required. I'd be really grateful for any advice you can provide to resolve the problem.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace MySite.Core.Models
{
public class Exam : ActivatableEntity
{
private int _numberOfQuestionsToBeAttempted;
private Exam()
{
Topics = new Collection<Topic>();
}
public Exam(IUser createdByUser,
string name,
string description,
double timeAllowedInMinutes,
bool shuffleTopicsTogether = true) :
base(createdByUser)
{
Name = name;
Description = description;
Topics = new Collection<Topic>();
TimeAllowedInMinutes = timeAllowedInMinutes;
ShuffleTopicsTogether = shuffleTopicsTogether;
}
public string Name { get; private set; }
public string Description { get; private set; }
public double TimeAllowedInMinutes { get; private set; }
public bool ShuffleTopicsTogether { get; private set; }
public IEnumerable<Question> PossibleQuestions
{
get
{
return Topics.SelectMany(t => t.PossibleQuestions);
}
}
public int NumberOfQuestionsToBeAttempted
{
get
{
if (_numberOfQuestionsToBeAttempted != 0) return _numberOfQuestionsToBeAttempted;
foreach (Topic topic in Topics)
{
_numberOfQuestionsToBeAttempted += topic.NumberOfQuestionsToBeAttempted;
}
return _numberOfQuestionsToBeAttempted;
}
}
public IEnumerable<Topic> Topics { get; }
public void Update(IUser updatedByUser, string name, string description, double timeAllowedInMinutes, bool shuffleTopicsTogether = true)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Description = description;
TimeAllowedInMinutes = timeAllowedInMinutes;
ShuffleTopicsTogether = shuffleTopicsTogether;
Update(updatedByUser);
}
}
}
Exam configuration class
using MySite.Core.Models;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace MySite.Persistence.EntityConfiguration
{
public class ExamConfiguration
{
public ExamConfiguration(EntityTypeBuilder<Exam> entityBuilder)
{
entityBuilder.HasKey(e => e.Id);
entityBuilder.HasOne(e => (ApplicationUser)e.CreatedByUser)
.WithMany()
.HasForeignKey(e => e.CreatedByUserId)
.OnDelete(DeleteBehavior.SetNull);
entityBuilder.HasOne(e => (ApplicationUser)e.LastUpdatedByUser)
.WithMany()
.HasForeignKey(e => e.LastUpdatedByUserId)
.OnDelete(DeleteBehavior.SetNull);
entityBuilder.Property(e => e.Name).IsRequired().HasMaxLength(50);
entityBuilder.Property(e => e.Description).IsRequired().HasMaxLength(250);
entityBuilder.HasMany(e => e.Topics)
.WithOne(t => t.Exam).OnDelete(DeleteBehavior.Cascade);
}
}
}
As requested by posters, I'm adding the code for the base classes below:
using System;
namespace MySite.Core.Models
{
public abstract class ActivatableEntity :
UpdatableCreatableEntity,
IActivatable
{
protected ActivatableEntity() { }
protected ActivatableEntity(IUser createdByUser) : base(createdByUser) { }
public int? LastActivatedByUserId { get; private set; }
public IUser LastActivatedByUser { get; private set; }
public DateTime? WhenLastActivated { get; private set; }
public int? LastDeactivatedByUserId { get; private set; }
public IUser LastDeactivatedByUser { get; private set; }
public DateTime? WhenLastDeactivated { get; private set; }
public bool IsActive { get; private set; }
protected virtual void Activate(IUser activatedByUser)
{
LastActivatedByUser = activatedByUser ?? throw new ArgumentNullException(nameof(activatedByUser));
LastActivatedByUserId = activatedByUser.Id;
WhenLastActivated = DateTime.Now;
IsActive = true;
}
protected virtual void Deactivate(IUser deactivatedByUser)
{
LastDeactivatedByUser = deactivatedByUser ?? throw new ArgumentNullException(nameof(deactivatedByUser));
LastDeactivatedByUserId = deactivatedByUser.Id;
WhenLastDeactivated = DateTime.Now;
IsActive = false;
}
}
public abstract class UpdatableCreatableEntity :
CreatableEntity,
IUpdatable
{
protected UpdatableCreatableEntity() { }
protected UpdatableCreatableEntity(IUser createdByUser) : base(createdByUser) { }
public int? LastUpdatedByUserId { get; private set; }
public IUser LastUpdatedByUser { get; private set; }
public DateTime? WhenLastUpdated { get; private set; }
protected virtual void Update(IUser updatedByUser)
{
LastUpdatedByUser = updatedByUser ?? throw new ArgumentNullException(nameof(updatedByUser));
LastUpdatedByUserId = updatedByUser.Id;
WhenLastUpdated = DateTime.Now;
}
}
public abstract class CreatableEntity :
IIdentifiable,
ICreatable
{
protected CreatableEntity() { }
protected CreatableEntity(IUser createdByUser)
{
CreatedByUser = createdByUser ?? throw new ArgumentNullException(nameof(createdByUser));
CreatedByUserId = createdByUser.Id;
WhenCreated = DateTime.Now;
}
public int Id { get; private set; }
public int? CreatedByUserId { get; private set; }
public DateTime WhenCreated { get; private set; }
public IUser CreatedByUser { get; private set; }
}
}
I faced same problem and it confused me a lot. But luckily I was using version control, so I was able to trace reasons of the issue.
For me it was many-to-many relation entity model with constructor that assigns values to fields. I was relying to Visual Studio to generate properties for me automatically, and VS did poor job not detecting type of the property that later became a key.
VS created property of type object, which is too generic and hardly could be translated into underlying database abstractions. Hence the error.
I agree, quite not descriptive, hope they will fix that in future versions.
So try to search for properties of object type and check, are they used as keys, if yes, try to replace them with specific types supported by your database provider.
Reported error for developers: #9817.

Entity Framework core .Include() issue

Been having a play about with ef core and been having an issue with the include statement. For this code I get 2 companies which is what i expected.
public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
var c = db.Company;
return c;
}
This returns
[
{
"id":1,
"companyName":"new",
"admins":null,
"employees":null,
"courses":null
},
{
"id":2,
"companyName":"Test Company",
"admins":null,
"employees":null,
"courses":null
}
]
As you can see there are 2 companies and all related properties are null as i havnt used any includes, which is what i expected. Now when I update the method to this:
public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
var c = db.Company
.Include(t => t.Employees)
.Include(t => t.Admins)
.ToList();
return c;
}
this is what it returns:
[
{
"id":1,
"companyName":"new",
"admins":[
{
"id":2,
"forename":"User",
"surname":"1",
"companyId":1
}
]
}
]
It only returns one company and only includes the admins. Why did it not include the 2 companies and their employees?
public class Company
{
public int Id { get; set; }
public string CompanyName { get; set; }
public List<Admin> Admins { get; set; }
public List<Employee> Employees { get; set; }
public List<Course> Courses { get; set; }
public string GetFullName()
{
return CompanyName;
}
}
public class Employee
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company company { get; set; }
public ICollection<EmployeeCourse> Employeecourses { get; set; }
}
public class Admin
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
}
I'm not sure if you've seen the accepted answer to this question, but the problem is to do with how the JSON Serializer deals with circular references. Full details and links to more references can be found at the above link, and I'd suggest digging into those, but in short, adding the following to startup.cs will configure the serializer to ignore circular references:
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Make sure you are using Include from "Microsoft.EntityFrameworkCore" And Not from "System.Data.Entity"
Lazy loading is not yet possible with EF Core. Refer here.
Alternatively you can use eager loading.
Read this article
Below is the extension method i have created to achieve the eager loading.
Extension Method:
public static IQueryable<TEntity> IncludeMultiple<TEntity, TProperty>(
this IQueryable<TEntity> source,
List<Expression<Func<TEntity, TProperty>>> navigationPropertyPath) where TEntity : class
{
foreach (var navExpression in navigationPropertyPath)
{
source= source.Include(navExpression);
}
return source.AsQueryable();
}
Repository Call:
public async Task<TEntity> FindOne(ISpecification<TEntity> spec)
{
return await Task.Run(() => Context.Set<TEntity>().AsQueryable().IncludeMultiple(spec.IncludeExpression()).Where(spec.IsSatisfiedBy).FirstOrDefault());
}
Usage:
List<object> nestedObjects = new List<object> {new Rules()};
ISpecification<Blog> blogSpec = new BlogSpec(blogId, nestedObjects);
var challenge = await this._blogRepository.FindOne(blogSpec);
Dependencies:
public class BlogSpec : SpecificationBase<Blog>
{
readonly int _blogId;
private readonly List<object> _nestedObjects;
public ChallengeSpec(int blogid, List<object> nestedObjects)
{
this._blogId = blogid;
_nestedObjects = nestedObjects;
}
public override Expression<Func<Challenge, bool>> SpecExpression
{
get { return blogSpec => blogSpec.Id == this._blogId; }
}
public override List<Expression<Func<Blog, object>>> IncludeExpression()
{
List<Expression<Func<Blog, object>>> tobeIncluded = new List<Expression<Func<Blog, object>>>();
if (_nestedObjects != null)
foreach (var nestedObject in _nestedObjects)
{
if (nestedObject is Rules)
{
Expression<Func<Blog, object>> expr = blog => blog.Rules;
tobeIncluded.Add(expr);
}
}
return tobeIncluded;
}
}
Will be glad if it helps. Please note this is not a production ready code.
I test your code, this problem exist in my test. in this post LINK Proposed that use data projection. for your problem Something like the following, is work.
[HttpGet]
public dynamic Get()
{
var dbContext = new ApplicationContext();
var result = dbContext.Companies
.Select(e => new { e.CompanyName, e.Id, e.Employees, e.Admins })
.ToList();
return result;
}
I know this is an old issue, but its the top result in google, so im putting my solution i I found here. For a Core 3.1 web project there is a quick fix.
Add nuget package Microsoft.EntityFrameworkCore.Proxies.
Then you simply just need to specify in your options builder when configuring your services. Docs: https://learn.microsoft.com/en-us/ef/core/querying/related-data
public void ConfigureServices(IServiceCollection services) {
services.AddDbContextPool<YourDbContext>(options => {
options.UseLazyLoadingProxies();
options.UseSqlServer(this.Configuration.GetConnectionString("MyCon"));
});
}
Now your lazy loading should work as it did in previous EF versions.
If you not using it for a web project, you can do it right inside of the OnConfiguringMethod inside of your DbContext itself.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseLazyLoadingProxies();
}
My EF stuff is kept in a separate class library so i can re-use it through multiple company applications. So having the ability to not lazy load when not needed for a particular application is useful. So i prefer passing in the build options, for reuse-ability purposes. But both will work.

DbSet.Add() not working

I have a class like this:
[Table("member_activation")]
public partial class MemberActivation
{
[Key]
public Int64 member_id { get; set; }
public String token { get; set; }
}
My db:
public class SMADbContext : DbContext
{
public SMADbContext() : base("SMADB")
{
Database.SetInitializer<SMADbContext>(new NullDatabaseInitializer<SMADbContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Member> Members { get; set; }
public DbSet<MemberActivation> MemberActivations { get; set; }
public DbSet<ApiAccount> ApiAccounts { get; set; }
public DbSet<ApiHardware> ApiHardwares { get; set; }
public DbSet<MemberRelation> MemberRelations { get; set; }
}
In my controller:
[Route("tester")]
[AllowAnonymous]
public IHttpActionResult tester()
{
using (var db = new SMADbContext())
{
var memberActivation = new MemberActivation();
memberActivation.member_id = 10155;
memberActivation.token = "hello";
db.MemberActivations.Add(memberActivation);
return Json(new { dbset = db.MemberActivations.ToList(), memberAct = memberActivation });
}
}
db.MemberActivations.Add(memberActivation); does not work. When I return the json, the dbset does not include the newly created memberActivation. I do not have db.SaveChanges() because it will not save until the memberActivation is pushed to the dbset
You cant set member_id, it is the key and ef uses it as identity. It will be ignored. You can configure ef so that member_id is not identity but that's another topic.
db.MembershipActivations.Add( new MemberActivation { token = "hello"}):
db.SaveChanges();
should work fine.
if however , as it would appear , you have an existing member and you are trying to set a relationship with that entity via a join table. Then you should retrieve that entity and set the memberactivation. Ef will sort the rest out for you. Bit of guessing here as i would need to see the involved entities.

Categories

Resources