The EF core Firebird provider does not convert DBNull to nullable types. When I have an entity with a nullable int? property and I simply get all entries in a table using await this._dbContext.EmpLevels.ToListAsync() I get the exception that I cannot cast a DBNull to Int32 as stated here: https://learn.microsoft.com/en-us/dotnet/api/system.dbnull.system-iconvertible-toint32?view=net-6.0. Theoretically, this makes sense because an int cannot represent null but it's a nullable property.
I found a lot of questions with the same problem, but not related to Firebird, where they have access to the readers or data tables, but in my case, I don't have them. So how can I parse null values from firebird into a nullable int?
EDIT:
But when I add HasConversion it works fine although I just pass the type of the property:
Before: entity.Property(e => e.CertId).HasColumnName("CERT_ID");
After: entity.Property(e => e.CertId).HasColumnName("CERT_ID").HasConversion<int?>();
The problem is that I don't have to add it to the property CertId. I first added HasConversion to the wrong property by accident and this worked too (property: CapId) although it's not even a nullable int because CertId is the only nullable.
EDIT 2:
Entity:
public partial class EmpLevel
{
public int EmpId { get; set; } = default!;
public int CapId { get; set; } = default!;
public DateTime StartDate { get; set; } = default!;
public int LevelId { get; set; } = default!;
public int? CertId { get; set; }
public virtual Certificate? Cert { get; set; }
public virtual EmpsCap EmpsCap { get; set; } = default!;
public virtual CapLevel Level { get; set; } = default!;
}
DbContext:
public class CapsDbContext : DbContext {
...
public virtual DbSet<EmpLevel> EmpLevels { get; set; }
...
public CapsDbContext(DbContextOptions<CapsDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
...
modelBuilder.Entity<EmpLevel>(entity => {
entity.HasKey(e => new { e.EmpId, e.CapId, e.StartDate });
entity.ToTable("EMP_LEVELS");
entity.HasIndex(e => e.CertId);
entity.HasIndex(e => new { e.CapId, e.EmpId });
entity.HasIndex(e => e.LevelId);
entity.HasIndex(e => new { e.StartDate, e.CapId, e.EmpId });
entity.HasIndex(e => new { e.StartDate, e.CapId, e.EmpId })
.IsUnique();
entity.Property(e => e.EmpId).HasColumnName("EMP_ID");
entity.Property(e => e.CapId).HasColumnName("CAP_ID");
entity.Property(e => e.StartDate)
.HasColumnType("DATE")
.HasColumnName("START_DATE");
entity.Property(e => e.CertId).HasColumnName("CERT_ID").HasConversion<int?>();
entity.Property(e => e.LevelId).HasColumnName("LEVEL_ID");
entity.HasOne(d => d.Cert)
.WithMany(p => p.EmpLevels)
.HasForeignKey(d => d.CertId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_CERT_EMPLEVEL");
entity.HasOne(d => d.Level)
.WithMany(p => p.EmpLevels)
.HasForeignKey(d => d.LevelId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_LEVEL_EMPLEVEL");
entity.HasOne(d => d.EmpsCap)
.WithMany(p => p.EmpLevels)
.HasForeignKey(d => new { d.EmpId, d.CapId })
.HasConstraintName("FK_EMPSCAPS_EMPLEVEL");
});
...
}
}
Exception:
System.InvalidCastException: Object cannot be cast from DBNull to other types.
at System.DBNull.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ToInt32(Object value, IFormatProvider provider)
at FirebirdSql.Data.Common.DbValue.GetInt32()
at FirebirdSql.Data.FirebirdClient.FbDataReader.GetInt32(Int32 I)
at lambda_method1001(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
System.InvalidCastException: Object cannot be cast from DBNull to other types.
at System.DBNull.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ToInt32(Object value, IFormatProvider provider)
at FirebirdSql.Data.Common.DbValue.GetInt32()
at FirebirdSql.Data.FirebirdClient.FbDataReader.GetInt32(Int32 I)
at lambda_method1001(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
Related
I ran into such a problem that when I add an empty value to a foreign key, I get an exception: PostgresException: 23502: "NULL value in the "name" column of the "files" relationship adding a NOT NULL entry"
And it's not clear to me why the file adding table is called, because I'm passing an empty value to the card table. Please tell me what it could be
SQL table Card
CREATE TABLE Files
(
id SERIAL PRIMARY KEY,
name VARCHAR(30) NOT NULL,
file bytea NOT NULL
);
CREATE TABLE Card
(
id SERIAL PRIMARY KEY,
numMK INT NOT NULL,
id_file INT,
FOREIGN KEY (id_file) REFERENCES Files (id)
);
And my code C#:
public partial class Card
{
public int Id { get; set; }
public int Nummk { get; set; }
public int? IdFile { get; set; }
public virtual FilePdf IdFileNavigation { get; set; }
}
public partial class FilePdf
{
public int Id { get; set; }
public string Name { get; set; }
public byte[] File1 { get; set; }
public virtual ICollection<Card> Cards { get; } = new List<Card>();
}
Initialization С#:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Card>(entity =>
{
entity.HasKey(e => e.Id).HasName("card_pkey");
entity.ToTable("card");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Nummk).HasColumnName("nummk");
entity.HasOne(d => d.IdFileNavigation).WithMany(p => p.Cards)
.HasForeignKey(d => d.IdFile)
.HasConstraintName("card_id_file_fkey");
});
modelBuilder.Entity<FilePdf>(entity =>
{
entity.HasKey(e => e.Id).HasName("filepdf_pkey");
entity.ToTable("files");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.File1)
.IsRequired()
.HasColumnName("file");
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(30)
.HasColumnName("name");
});
OnModelCreatingPartial(modelBuilder);
}
And the very addition of the card
using (var db = new TrappinganimalsContext())
{
var card = new Card()
{
Id = 1,
Nummk = 2312,
IdFile = null,
};
db.Cards.Add(card);
db.SaveChanges();
}
I will be very grateful for your help
you can use db.Database.Log = s => System.Diagnostics.Debug.WriteLine(s); to get the generated query to see what is wrong with your query.
If required you can store an empty value in place of null
Using EF Power tools I have the following classes created:
Category.cs
public partial class Category
{
public Category()
{
Folders = new HashSet<Folder>();
Reports = new HashSet<Report>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string CategoryDescription { get; set; }
public string ImagePath { get; set; }
public virtual ICollection<Folder> Folders { get; set; }
public virtual ICollection<Report> Reports { get; set; }
}
Folder.cs
public partial class Folder
{
public Folder()
{
Reports = new HashSet<Report>();
}
public int FolderId { get; set; }
public string FolderName { get; set; }
public int CategoryId { get; set; }
public string FolderDescription { get; set; }
public string FolderImagePath { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Report> Reports { get; set; }
}
The underlying tables are linked one to many with Categoryid.
I believe the dbContext.cs was generated correctly based on the table schema.
dbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>(entity =>
{
entity.HasKey(e => e.CategoryId)
.IsClustered(false);
entity.ToTable("Category");
entity.Property(e => e.CategoryId)
.ValueGeneratedNever()
.HasColumnName("CategoryID");
entity.Property(e => e.CategoryDescription)
.HasMaxLength(300)
.IsUnicode(false);
entity.Property(e => e.CategoryName)
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.ImagePath)
.HasMaxLength(250)
.IsUnicode(false);
} );
modelBuilder.Entity<Folder>(entity =>
{
entity.HasKey(e => e.FolderId)
.IsClustered(false);
entity.ToTable("Folder");
entity.Property(e => e.FolderId)
.ValueGeneratedNever()
.HasColumnName("FolderID");
entity.Property(e => e.CategoryId).HasColumnName("CategoryID");
entity.Property(e => e.FolderDescription)
.HasMaxLength(300)
.IsUnicode(false);
entity.Property(e => e.FolderImagePath)
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.FolderName)
.HasMaxLength(100)
.IsUnicode(false);
entity.HasOne(d => d.Category)
.WithMany(p => p.Folders)
.HasForeignKey(d => d.CategoryId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Folder_01");
});
OnModelCreatingPartial(modelBuilder);
}
I then have the following in the controller:
[HttpGet]
public async Task<ActionResult<IEnumerable<Category>>> GetCategories()
{
return await _context.Categories.ToListAsync();
}
When I run the WebAPI I see the following:
[
{
"categoryId": 1,
"categoryName": "Some Name",
"folders": [],
"reports": []
},
{
"categoryId": 2,
"categoryName": "Another Name",
"folders": [],
"reports": []
},
]
How do I get the related data to populate in the folders array?
I also tried this:
public async Task<ActionResult<IEnumerable<Category>>> GetCategories()
{
var query = (from r in _context.Categories
from bu in _context.Folders
where r.CategoryId == bu.CategoryId
select r
).ToListAsync();
return await query;
I suspect that you application has the loading of relationship to lazy loading.
This means that every relation on the model specified wont be loaded until you ask to specifically see them.
In this specific case you could tell your application to load all the data you need in one go.
[HttpGet]
public async Task<ActionResult<IEnumerable<Category>>> GetCategories()
{
// note the .Include will tell you application context to load the relation Folders by default
return await _context.Categories.Include(x => x.Folders).ToListAsync();
}
Here is more information if you want to learn all the way's you could load your relationships by default
Service:
public Cart AddProductToCart(Product product, Cart cart)
{
var productExist = _dbContexet.CartProduct.Where(cp => cp.ProductId == product.Id).FirstOrDefault();
var quantityOfCart = _dbContexet.CartProduct.Where(cp => cp.CartId == cart.Id).FirstOrDefault().Quantity;
CartProduct cartProduct = new CartProduct();
cartProduct.CartId = cart.Id;
cartProduct.ProductId = product.Id;
if (productExist != null)
{
cartProduct.Quantity = quantityOfCart + 1;
_dbContexet.Attach(cartProduct).State = EntityState.Modified;
}
else
{
cartProduct.Cart = cart;
cartProduct.Product = product;
_dbContexet.CartProduct.Add(cartProduct);
}
_dbContexet.SaveChanges();
return cart;
}
Full error:
InvalidOperationException: The instance of entity type 'CartProduct'
cannot be tracked because another instance with the same key value for
{'CartId', 'ProductId'} is already being tracked. When attaching
existing entities, ensure that only one entity instance with a given
key value is attached. Consider using
'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the
conflicting key values.
I have many to many and table CartProduct I want to check if product exist and only update quantity if it exist but I get this error?
Eddit:
Product:
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public decimal CostToMake { get; set; }
public decimal FinalPrice { get; set; }
//public int? OrderId { get; set; }
public virtual Order Order { get; set; }
public List<CartProduct> CartProduct { get; set; }
}
Cart:
public class Cart
{
public int Id { get; set; }
public int ProductCount { get; set; }
public AppUser User { get; set; }
public List<CartProduct> CartProduct { get; set; }
}
CartProduct:
public class CartProduct
{
public int CartId { get; set; }
public int ProductId { get; set; }
public Cart Cart { get; set; }
public Product Product { get; set; }
public int Quantity { get; set; }
}
FluentAPI:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasAnnotation("Relational:Collation", "Cyrillic_General_CI_AS");
modelBuilder.Entity<Day>(entity =>
{
entity.Property(e => e.Date).HasColumnType("date");
entity.Property(e => e.MostCommonCategory)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.MostCommonProduct)
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.TotalMade).HasColumnType("decimal(18, 3)");
entity.Property(e => e.TotalSpent).HasColumnType("decimal(18, 3)");
});
modelBuilder.Entity<Order>(entity =>
{
entity.Property(e => e.Date).HasColumnType("date");
entity.Property(e => e.Status)
.IsRequired()
.HasMaxLength(100)
.IsUnicode(false);
entity.Property(e => e.Name).HasMaxLength(100);
entity.Property(e => e.Address).HasMaxLength(150);
entity.Property(e => e.PhoneNumber).HasMaxLength(20);
});
modelBuilder.Entity<Product>(entity =>
{
entity.HasIndex(e => e.OrderId, "IX_Products_OrderId");
entity.Property(e => e.Category)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.CostToMake).HasColumnType("decimal(18, 3)");
entity.Property(e => e.FinalPrice).HasColumnType("decimal(18, 3)");
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.Price).HasColumnType("decimal(18, 3)");
entity.HasOne(d => d.Order)
.WithMany(p => p.Products)
.HasForeignKey(d => d.OrderId);
});
modelBuilder.Entity<Cart>(entity => {
entity.HasKey(e => e.Id);
entity.HasOne(e => e.User)
.WithOne(e => e.Cart)
.HasForeignKey<AppUser>(e => e.CartId);
});
modelBuilder.Entity<CartProduct>()
.HasKey(e => new { e.CartId, e.ProductId });
modelBuilder.Entity<CartProduct>()
.HasOne(t => t.Cart)
.WithMany(t => t.CartProduct)
.HasForeignKey(t => t.ProductId);
modelBuilder.Entity<CartProduct>()
.HasOne(t => t.Product)
.WithMany(t => t.CartProduct)
.HasForeignKey(t => t.CartId);
OnModelCreatingPartial(modelBuilder);
}
You can perform some actions but in a different way, using EF features.
public Cart AddProductToCart(Product product, Cart cart)
{
var product = _dbContexet.Product.Where(cp => cp.ProductId == product.Id).Include(x => x.CartProduct).FirstOrDefault();
var quantityOfCart = _dbContexet.CartProduct.Where(cp => cp.CartId == cart.Id).FirstOrDefault().Quantity;
var cartProduct = product.CartProduct.Where(x=>x.CartId == cart.id).FirstOrDefault();
if (cartProduct != null)
{
cartProduct.Quantity = quantityOfCart + 1;
}
else
{
product.CartProduct.Add(new CartProduct { CartId = cart.id });
}
//_dbContexet.Update(product) if you are using QueryTrackingBehavior.NoTracking
_dbContexet.SaveChanges();
return cart;
}
Your error is because :
cartProduct.CartId = cart.Id;
cartProduct.ProductId = product.Id;
in reallity is the same as:
cartProduct.Cart = cart;
cartProduct.Product = product;
so just remove from your code:
cartProduct.Cart = cart;
cartProduct.Product = product;
This question already has answers here:
EF Core returns null relations until direct access
(2 answers)
Closed 3 years ago.
I am using EF core 2.1.14
Here is my Class for DbContext which I have created by scaffolding:
public partial class AgriDbContext : DbContext
{
public AgriDbContext()
{
}
public AgriDbContext(string connectionString)
: base(GetOptions(connectionString))
{
}
private static DbContextOptions GetOptions(string connectionString)
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
public virtual DbSet<Advertisement> Advertisements { get; set; }
public virtual DbSet<AgroItem> AgroItems { get; set; }
public virtual DbSet<BuyerAddsDifferentAdsToFav> BuyerAddsDifferentAdsToFavs { get; set; }
public virtual DbSet<BuyersAddAgroItemToInterest> BuyersAddAgroItemToInterests { get; set; }
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<SellersFavoritesBuyer> SellersFavoritesBuyers { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
modelBuilder.Entity<Advertisement>(entity =>
{
entity.Property(e => e.Picture).IsUnicode(false);
entity.HasOne(d => d.City)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("ADVERTISEMENTS_INCLUDE_A_CITY");
entity.HasOne(d => d.Item)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.ItemId)
.HasConstraintName("AN_ADVERTISEMENT_IS_RELATED_TO_AN_ITEM");
entity.HasOne(d => d.Seller)
.WithMany(p => p.Advertisements)
.HasForeignKey(d => d.SellerId)
.HasConstraintName("USERS_POST_ADVERTISEMENTS");
});
modelBuilder.Entity<AgroItem>(entity =>
{
entity.Property(e => e.Name).IsUnicode(false);
entity.Property(e => e.Uname).IsUnicode(false);
entity.HasOne(d => d.Category)
.WithMany(p => p.AgroItems)
.HasForeignKey(d => d.CategoryId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("AGRO_ITEMS_BELONG_TO_A_CATEGORY");
});
modelBuilder.Entity<BuyerAddsDifferentAdsToFav>(entity =>
{
entity.HasKey(e => new { e.BuyerId, e.AdId });
entity.HasOne(d => d.Ad)
.WithMany(p => p.BuyerAddsDifferentAdsToFavs)
.HasForeignKey(d => d.AdId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYER_ADDS_DIFFERENT_ADS_TO_FAV_ADVERTISEMENTS");
entity.HasOne(d => d.Buyer)
.WithMany(p => p.BuyerAddsDifferentAdsToFavs)
.HasForeignKey(d => d.BuyerId)
.HasConstraintName("FK_BUYER_ADDS_DIFFERENT_ADS_TO_FAV_USERS");
});
modelBuilder.Entity<BuyersAddAgroItemToInterest>(entity =>
{
entity.HasKey(e => new { e.BuyerId, e.ItemId });
entity.HasOne(d => d.Buyer)
.WithMany(p => p.BuyersAddAgroItemToInterests)
.HasForeignKey(d => d.BuyerId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYERS_ADD_AGRO_ITEM_TO_INTEREST_USERS");
entity.HasOne(d => d.Item)
.WithMany(p => p.BuyersAddAgroItemToInterests)
.HasForeignKey(d => d.ItemId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_BUYERS_ADD_AGRO_ITEM_TO_INTEREST_AGRO_ITEMS");
});
modelBuilder.Entity<Category>(entity =>
{
entity.Property(e => e.Name).IsUnicode(false);
entity.Property(e => e.Uname).IsUnicode(false);
});
modelBuilder.Entity<City>(entity =>
{
entity.HasIndex(e => e.Id)
.HasName("UNIQUE_LOCATION")
.IsUnique();
entity.Property(e => e.Name).IsUnicode(false);
});
modelBuilder.Entity<SellersFavoritesBuyer>(entity =>
{
entity.HasKey(e => new { e.SellerId, e.BuyerId });
entity.HasOne(d => d.Buyer)
.WithMany(p => p.SellersFavoritesBuyerBuyers)
.HasForeignKey(d => d.BuyerId)
.HasConstraintName("FK_SELLERS_FAVORITES_BUYERS_USERS1");
entity.HasOne(d => d.Seller)
.WithMany(p => p.SellersFavoritesBuyerSellers)
.HasForeignKey(d => d.SellerId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_SELLERS_FAVORITES_BUYERS_USERS");
});
modelBuilder.Entity<User>(entity =>
{
entity.HasIndex(e => new { e.CcompanyCode, e.CcountryCode, e.Cphone })
.HasName("UNIQUE_CONTACT")
.IsUnique();
entity.Property(e => e.Address).IsUnicode(false);
entity.Property(e => e.Fname).IsUnicode(false);
entity.Property(e => e.Lname).IsUnicode(false);
entity.HasOne(d => d.City)
.WithMany(p => p.Users)
.HasForeignKey(d => d.CityId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_USERS_CITIES");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
In the above code CityId is properly Related as a foreign Key and there is also a property for City in the User class. But whenever I get a user it returns null in the City Property but it returns CityId from the database.
Below is my Entity Model Class
[Table("USERS")]
public partial class User
{
public User()
{
Advertisements = new HashSet<Advertisement>();
BuyerAddsDifferentAdsToFavs = new HashSet<BuyerAddsDifferentAdsToFav>();
BuyersAddAgroItemToInterests = new HashSet<BuyersAddAgroItemToInterest>();
SellersFavoritesBuyerBuyers = new HashSet<SellersFavoritesBuyer>();
SellersFavoritesBuyerSellers = new HashSet<SellersFavoritesBuyer>();
}
public long Id { get; set; }
[Required]
[Column("FName")]
public string Fname { get; set; }
[Required]
[Column("LName")]
public string Lname { get; set; }
[Required]
[Column("CCompanyCode")]
[StringLength(3)]
public string CcompanyCode { get; set; }
[Required]
[Column("CCountryCode")]
[StringLength(3)]
public string CcountryCode { get; set; }
[Required]
[Column("CPhone")]
[StringLength(7)]
public string Cphone { get; set; }
[Required]
public string Address { get; set; }
[Column("GLat", TypeName = "decimal(10, 8)")]
public decimal? Glat { get; set; }
[Column("GLng", TypeName = "decimal(11, 8)")]
public decimal? Glng { get; set; }
public bool BuyerFlag { get; set; }
public bool SellerFlag { get; set; }
public short CityId { get; set; }
[ForeignKey("CityId")]
[InverseProperty("Users")]
public virtual City City { get; set; }
[InverseProperty("Seller")]
public virtual ICollection<Advertisement> Advertisements { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<BuyerAddsDifferentAdsToFav> BuyerAddsDifferentAdsToFavs { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<BuyersAddAgroItemToInterest> BuyersAddAgroItemToInterests { get; set; }
[InverseProperty("Buyer")]
public virtual ICollection<SellersFavoritesBuyer> SellersFavoritesBuyerBuyers { get; set; }
[InverseProperty("Seller")]
public virtual ICollection<SellersFavoritesBuyer> SellersFavoritesBuyerSellers { get; set; }
}
I am using this method of repository to get the user
public EFarmer.Models.User GetUser(ContactNumberFormat contact)
{
var user = users
.Where(x => x.CcountryCode == contact.CountryCode
&& x.CcompanyCode == contact.CompanyCode
&& x.Cphone == contact.PhoneNumber).First() ?? null;
return (user != null) ? new EFarmer.Models.User
{
Address = user.Address,
City = EFarmer.Models.City.Convert(user.City),
ContactNumber = new ContactNumberFormat(user.CcountryCode, user.CcompanyCode, user.Cphone),
IsBuyer = user.BuyerFlag,
IsSeller = user.SellerFlag,
Location = new GeoLocation { Latitude = user.Glat, Longitude = user.Glng },
Name = new NameFormat { FirstName = user.Fname, LastName = user.Lname },
Id = user.Id
} : null;
}
And here is the convert method which converts Entity model to my Business Model, it give null reference exception due to null in City from entity model
public static City Convert(EFarmerPkModelLibrary.Entities.City city)
{
return new City
{
GeoLocation = new GeoLocation { Latitude = city.Glat, Longitude = city.Glng },
Id = city.Id,
Name = city.Name
};
}
From what I can see from your example and tags (EF Core 2.1) the issue likely is that lazy loading hasn't been enabled. Check that you've added the dependency for the EF Core Proxies and added optionsBuilder.UseLazyLoadingProxies(); to the DbContext OnModelConfiguring override.
A recommendation though is not to roll your own mappers such as the static Convert method, and instead look to leverage an existing mapper like AutoMapper. A key feature of Automapper for integrating with EF is Projection (ProjectTo). This can build your DTOs/ViewModels as part of the Linq expression that is fed to the database resulting in far, far more efficient queries and no multi-hits from things like lazy loading.
With Lazy loading you will have 1 query hit to get all fields from your user, then 1 query hit to get all fields from your City, plus 1 hit for each and every other lazy-load call. If you are doing something like fetching a list of users, you would have 1 hit to get the list of users, but then 1 hit for each city for each user!
With eager loading you would have 1 query hit to get all fields from your user and their related city, which is a lower performance hit. However it is still fetching all fields, whether your DTO/ViewModel needs them or not. There is also the risk of forgetting to explicitly eager load related data in queries, especially when expanding entities to add new relationships, which ends up reverting to the lazy load performance penalties or issues.
With Automapper's ProjectTo, you would have 1 query hit to get only the fields from User, City, and any other related entities which the DTO/ViewModel requested. No need to remember to Include relatives, or worry about lazy load hits. Clean, efficient, and future proof.
I've been playing around with this quickstart example & have been trying to see how far I can customise the database (I have an existing database I've been half-trying to replicate).
I've managed to trigger the exception below and am having trouble fixing it, partially because I don't understand what the message is telling me.
InvalidOperationException: Entity type
'Microsoft.AspNetCore.Identity.IdentityRole' is in shadow-state. A
valid model requires all entity types to have corresponding CLR type.
My ApplicationDbContext is as follows:
using IdentityServerWithAspIdAndEF.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
namespace IdentityServerWithAspIdAndEF.Data
{
public class ApplicationDbContext : IdentityDbContext<User, Role, int>
{
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> Options
) : base(Options) { }
protected override void OnModelCreating(ModelBuilder ModelBuilder)
{
base.OnModelCreating(ModelBuilder);
// Customisations
// "IdentityServer4AspNetIdentity.Models.ApplicationUser"
ModelBuilder.Entity<User>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("AccountId")
.ValueGeneratedOnAdd();
B.Property<string>("ConcurrencyStamp")
.HasMaxLength(512)
.IsConcurrencyToken();
B.Property<string>("Email")
.HasMaxLength(512)
.IsRequired();
B.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd();
B.Property<string>("NormalisedEmail")
.HasMaxLength(512)
.IsRequired();
B.Property<string>("NormalisedUserName")
.HasMaxLength(256)
.IsRequired();
B.Property<string>("PasswordHash");
B.Property<string>("SecurityStamp")
.IsRequired();
B.Property<bool>("TwoFactorEnabled")
.ValueGeneratedOnAdd();
B.Property<string>("UserName")
.HasMaxLength(256)
.IsRequired();
B.Property<DateTime>("Registered")
.ValueGeneratedOnAdd();
B.Property<DateTime>("LastVisit")
.IsRequired();
B.HasKey("AccountId");
B.HasIndex("NormalisedEmail")
.HasName("IX_Users_NormalisedEmail");
B.HasIndex("NormalisedUserName")
.IsUnique()
.HasName("IX_Users_NormalisedUserName");
B.ToTable("Users");
});
// "Microsoft.AspNetCore.Identity.IdentityRole"
ModelBuilder.Entity<Role>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("RoleId")
.ValueGeneratedOnAdd();
B.Property<string>("ConcurrencyStamp")
.HasMaxLength(512)
.IsConcurrencyToken();
B.Property<string>("Name")
.HasMaxLength(256);
B.Property<string>("NormalisedName")
.HasMaxLength(256);
B.HasKey(P => P.Id);
B.HasIndex("NormalisedName")
.IsUnique()
.HasName("IX_Roles_NormalisedName");
B.ToTable("Roles");
});
// "Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>"
ModelBuilder.Entity<RoleClaim>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("ClaimId")
.ValueGeneratedOnAdd();
B.Property<string>("ClaimType")
.HasMaxLength(128);
B.Property<string>("ClaimValue")
.HasMaxLength(128);
B.Property<int>("RoleId")
.IsRequired();
B.HasIndex(P => P.RoleId)
.HasName("IX_RoleClaims_RoleId");
B.HasOne(D => D.Claim)
.WithMany()
.HasForeignKey(P => P.RoleId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("RoleClaims");
});
// "Microsoft.AspNetCore.Identity.IdentityUserClaim<string>"
ModelBuilder.Entity<UserClaim>(B =>
{
B.Property<int>(P => P.Id)
.HasColumnName("ClaimId")
.ValueGeneratedOnAdd();
B.Property<string>("ClaimType")
.HasMaxLength(128);
B.Property<string>("ClaimValue")
.HasMaxLength(128);
B.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
B.HasIndex("AccountId")
.HasName("IX_UserClaims_AccountId");
B.HasOne(D => D.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("UserClaims");
});
// "Microsoft.AspNetCore.Identity.IdentityUserLogin<string>"
ModelBuilder.Entity<Login>(B =>
{
B.Property<int>(P => P.UserId)
.HasColumnName("LoginId")
.ValueGeneratedOnAdd();
B.Property<string>("LoginProvider")
.HasMaxLength(450)
.IsRequired();
B.Property<string>("ProviderKey")
.HasMaxLength(450)
.IsRequired();
B.Property<string>("ProviderDisplayName");
B.Property<int>("AccountId")
.IsRequired();
B.HasIndex("LoginProvider")
.HasName("IX_Logins_LoginProvider");
B.HasIndex("ProviderKey")
.HasName("IX_Logins_ProviderKey");
B.HasIndex("AccountId")
.HasName("IX_Logins_AccountId");
B.HasOne(D => D.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("Logins");
});
// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(B =>
{
B.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
B.Property<int>("RoleId")
.IsRequired();
B.HasIndex("AccountId")
.HasName("IX_RoleMap_AccountId");
B.HasIndex("RoleId")
.HasName("IX_RoleMap_RoleId");
B.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
B.HasOne(P => P.Account)
.WithMany()
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("RoleMap");
});
// "Microsoft.AspNetCore.Identity.IdentityUserToken<string>"
ModelBuilder.Entity<Token>(B =>
{
B.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
B.Property<string>("LoginProvider")
.HasMaxLength(128)
.IsRequired();
B.Property<string>("Name")
.HasMaxLength(64);
B.Property<string>("Value");
B.HasOne(P => P.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("UserTokens");
});
// Non-identity extras
/* snipped */
}
}
}
The entities that correspond with these DbSets are as follows:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
namespace IdentityServerWithAspIdAndEF.Models
{
public class User : IdentityUser<int>
{
public int AccountId
{
get => base.Id;
set => base.Id = value;
}
public string NormalisedEmail
{
get => base.NormalizedEmail;
set => base.NormalizedEmail = value;
}
public string NormalisedUserName
{
get => base.NormalizedUserName;
set => base.NormalizedUserName = value;
}
public DateTime Registered { get; set; }
public DateTime LastVisit { get; set; }
public AccountDetail UserDetails { get; set; }
public AccountLockout Lockout { get; set; }
public PasswordReset PasswordResetRequested { get; set; }
public Concierge ConciergeAccountFlag { get; set; }
public NotValidated AccountValidatation { get; set; }
public ICollection<UserRole> AssignedRoles { get; set; }
public ICollection<UserClaim> ClaimsCollection { get; set; }
public ICollection<Login> Logins { get; set; }
public ICollection<LoginAttempt> LoginAttempts { get; set; }
public ICollection<Token> TokenCollection { get; set; }
public new int Id => throw new NotImplementedException();
public override string NormalizedEmail => throw new NotImplementedException();
public override string NormalizedUserName => throw new NotImplementedException();
}
public class Role : IdentityRole<int>
{
public int RoleId
{
get => base.Id;
set => base.Id = value;
}
public string NormalisedName
{
get => base.NormalizedName;
set => base.NormalizedName = value;
}
public ICollection<RoleClaim> ClaimsCollection { get; set; }
private new int Id => throw new NotImplementedException();
private new int NormalizedName => throw new NotImplementedException();
}
public class RoleClaim : IdentityRoleClaim<int>
{
public int ClaimId
{
get => base.Id;
set => base.Id = value;
}
public Role Claim { get; set; }
private new int Id => throw new NotImplementedException();
}
public class UserClaim : IdentityUserClaim<int>
{
public int ClaimId
{
get => base.Id;
set => base.Id = value;
}
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
private new int Id => throw new NotImplementedException();
private new int UserId => throw new NotImplementedException();
}
public class Login : IdentityUserLogin<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
private new int UserId => throw new NotImplementedException();
}
public class UserRole : IdentityUserRole<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
private new int UserId => throw new NotImplementedException();
}
public class Token : IdentityUserToken<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
private new int UserId => throw new NotImplementedException();
public User Account { get; set; }
}
}
I have read the posts "What does it mean for an entity type to be in “shadow state”?" and "Entity type 'type' is in shadow-state. A valid model requires all entity types to have corresponding CLR type"
Judging from the documentation I think that I may have missed, or mis-referenced, the Role entity somewhere, but it's not clear to me where.
Thanks in advance!
Edit:
Re-reading the shadow property documentation, the line "By convention, shadow properties are only created when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced." seems to support that I've mucked up the entity.
I've tried to rule out a property name mismatch by changing all property references in the Role and RoleClaim ModelBuilder entity declarations to expressions, to the following to see if hard referencing will help:
// "Microsoft.AspNetCore.Identity.IdentityRole"
ModelBuilder.Entity<Role>(B =>
{
B.Property(P => P.RoleId)
.ValueGeneratedOnAdd();
B.Property(E => E.ConcurrencyStamp)
.HasMaxLength(512)
.IsConcurrencyToken();
B.Property(E => E.Name)
.HasMaxLength(256);
B.Property(E => E.NormalisedName)
.HasMaxLength(256);
B.HasKey(P => P.Id);
B.HasIndex(E => E.NormalisedName)
.IsUnique()
.HasName("IX_Roles_NormalisedName");
B.ToTable("Roles");
});
// "Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>"
ModelBuilder.Entity<RoleClaim>(B =>
{
B.Property(P => P.ClaimId)
.ValueGeneratedOnAdd();
B.Property(E => E.ClaimType)
.HasMaxLength(128);
B.Property(E => E.ClaimValue)
.HasMaxLength(128);
B.Property(E => E.RoleId)
.IsRequired();
B.HasIndex(P => P.RoleId)
.HasName("IX_RoleClaims_RoleId");
B.HasOne(D => D.Claim)
.WithMany()
.HasForeignKey(P => P.RoleId)
.OnDelete(DeleteBehavior.Cascade);
B.ToTable("RoleClaims");
});
But no luck so far.
It appears as though I'd made several mistakes, the most pertinent is that I hadn't actually provided code for my UserRole object within the ApplicationDbContext sample; this is actually where the error was originating...
The code still referenced the original IdentityRole model:
// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(E =>
{
E.Property<int>(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
E.Property<int>("RoleId")
.IsRequired();
E.HasIndex("AccountId")
.HasName("IX_RoleMap_AccountId");
E.HasIndex("RoleId")
.HasName("IX_RoleMap_RoleId");
E.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") // Argh!
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
E.HasOne(P => P.Account)
.WithMany()
.HasForeignKey("AccountId")
.OnDelete(DeleteBehavior.Cascade);
E.ToTable("RoleMap");
});
This was updated to reference properties, instead of using magic strings to specify the field names; which highlighted the error I'd made.
// "Microsoft.AspNetCore.Identity.IdentityUserRole<string>"
ModelBuilder.Entity<UserRole>(E =>
{
E.Property(P => P.UserId)
.HasColumnName("AccountId")
.IsRequired();
E.Property(P => P.RoleId)
.IsRequired();
E.HasIndex(P => P.AccountId)
.HasName("IX_RoleMap_AccountId");
E.HasIndex(P => P.RoleId)
.HasName("IX_RoleMap_RoleId");
E.HasOne(P => P.Role) // 'UserRole' does not contain a definition for 'Role'
.WithMany()
.HasForeignKey(P => P.RoleId)
.OnDelete(DeleteBehavior.Cascade);
E.HasOne(P => P.Account)
.WithMany()
.HasForeignKey(P => P.AccountId)
.OnDelete(DeleteBehavior.Cascade);
E.ToTable("RoleMap");
});
And:
public class UserRole : IdentityUserRole<int>
{
public int AccountId
{
get => base.UserId;
set => base.UserId = value;
}
public User Account { get; set; }
public Role Role { get; set; } // Addition
}
At this point the exception informing me that IdentityRole was operating in a shadow-state seems to have disappeared and been replaced by others.
Additionally (and I'm not 100% sure that this contributed to the exception I was seeing) I had misconfigured my RoleStore within Startup->ConfigureServices.
Services.AddIdentity<User, Role>()
.AddUserStore<CustomerUserStore>()
.AddUserManager<CustomerManager>()
.AddRoleStore<Role>() // Should have been CustomerRoleStore
.AddRoleManager<RoleManager>()
.AddSignInManager<CustomerSignInManager>()
.AddDefaultTokenProviders();
CustomerRoleStore also required an override to allow IdentityServer to understand the roles after my customisations, which looks as so:
public class CustomerRoleStore
: RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>
{
public CustomerRoleStore(
ApplicationDbContext context,
IdentityErrorDescriber describer = null
) : base(
context,
describer
)
{ }
protected override RoleClaim CreateRoleClaim(Role role, Claim claim)
{
return new RoleClaim
{
RoleId = role.RoleId,
ClaimType = claim.Type,
ClaimValue = claim.Value
};
}
}