I am trying to access tables in a database, but the DbSets are null.
Here is the first half of my model (it matches the view in my SQL Server Database):
[Table("vwAccounts")]
public class Account
{
[Column("AccountId")]
public int Id { get; set; }
[Column("AccountUserName")]
public string UserName { get; set; }
[Column("AccountPassword")]
public string Password { get; set; }
[Column("AccountActive")]
public bool Active { get; set; }
[Column("CompanyId")]
public int CompanyId { get; set; }
[Column("DriverId")]
public int DriverId { get; set; }
[Column("AccountTypeName")]
public string TypeName { get; set; }
}
Here is my DbContext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{ }
public DbSet<Account> Accounts { get; set; }
public DbSet<Driver> Drivers { get; set; }
}
Here is my program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer("name=ConnectionStrings:myConString"));
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
...
app.Run();
And here is where I'm trying to access the DbSets, but they are null:
public IEnumerable<Account> GetAccounts()
{
try
{
// HERE is the error.
// db.context.Accounts is null, so it doesn't matter what method I call from it, it throws the error
Account results = dbContext.Accounts.First();
Console.WriteLine(results);
return results;
} catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
This is the actual error message:
Using the debugger, dbContext.Accounts is not null, it is {Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1[MySite.Models.Entities.Account]}.
When I try to access anything in dbSte is where the issue occurs.
After digging into the debugger stack trace, I beleive the issue might have to do with incorrect variable typing.
at Microsoft.Data.SqlClient.SqlBuffer.get_Int32()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at System.Linq.SystemCore_EnumerableDebugView`1.get_Items()
I figured it out.
After commenting out proetries until I narrowed down the issue to these properties, I realized the data in the database has null values. So I fixed this by making my integers nullable:
[Column("CompanyId")]
public Int32? CompanyId { get; set; }
[Column("DriverId")]
public Int32? DriverId { get; set; }
Related
I'm working with EF 6 and I need to create a model ignoring nested properties.
I have a model like this (not the same but it's similar)
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public int? IdEnt { get; set; }
public Enterprise Enterprise { get; set; }
public int? IdContact { get; set; }
public Contact DirectContact { get; set; }
public Client(string Name, Enterprise enterprise, Contact contact)
{
this.Name = Name;
Enterprise = enterprise;
DirectContact = contact;
}
}
public class Contact
{
public int IdContact { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
public class Enterprise
{
public int IdEnt { get; set; }
public string NameEnt { get; set; }
public string Dir { get; set; }
}
Contacts are created when I create a new Client, but Enterprise is an entity that exist in DB, so I only need to connect this properties but when I add a new Client and save changes appears an error indicate I try to insert an Enterprise (DB has automatic primary key in Enterprise table)
This is the transaction that I use to create a Client
bool transaction = false;
string msj = "";
var strategy = Context.Database.CreateExecutionStrategy();
strategy.Execute(() =>
{
using var dbContextTransaction = Context.Database.BeginTransaction();
try
{
Context.Set<Client>().Add(client); // <= client is created with constructor before this part of code
Context.SaveChanges();
dbContextTransaction.Commit();
Transaccion = true;
}
catch (Exception e)
{
dbContextTransaction.Rollback();
msj = e.InnerException.ToString();
/*
Exception:
Cannot insert explicit value for identity column in table 'X' when IDENTITY_INSERT is set to OFF.
I cannot set off this cause I only need connect the entities.
*/
msj = e.Message;
}
});
How I can this?
I'm sorry for my explication and my english if i said something wrong.
Extra:
I configured the entities with this code:
public class ClientConfig : IEntityTypeConfiguration<Client>
{
public void Configure(EntityTypeBuilder<Cliente> builder)
{
builder.ToTable("U_Clients");
builder.HasKey(ent => ent.ID);
builder.Property(i => i.ID)
.HasColumnName("IdClient");
builder.HasOne(ent => ent.Contact)
.WithMany()
.HasForeignKey(e=>e.IdContact);
builder.HasOne(ent => ent.Enterprise)
.WithMany()
.HasForeignKey(e=>e.IdEnterprise);
}
}
I am implementing Entity Framework in ASP.NET Core-6 Web API and MSSQL DB. I have this code:
Models:
public class BankUser
{
public Guid { get; set; }
[ForeignKey("UserId")]
public string UserId { get; set; }
public string Description { get; set; }
[ForeignKey("BankBranchId")]
public Guid BankBranchId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual BankBranch BankBranch { get; set; }
}
public class BankBranch
{
public Guid { get; set; }
public string BranchName { get; set; }
public virtual BankUser BankUser { get; set; }
}
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual BankUser BankUser { get; set; }
}
BankBranch has many BankUser, while BankUser and Application User is one-to-one.
Configuration:
public class ApplicationUserConfigurations : IEntityTypeConfiguration<ApplicationUser>
{
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
builder.Property(u => u.IsDeleted).HasDefaultValue(false);
}
}
public void Configure(EntityTypeBuilder<BankBranch> builder)
{
builder.ToTable(name: "bank_branches");
builder.HasKey(b => b.Id);
builder.Property(b => b.Id).HasDefaultValueSql("NEWID()");
builder.HasIndex(b => b.BranchName).IsUnique();
builder.HasIndex(b => b.BranchNumber).IsUnique();
}
}
public class BankUserConfigurations : IEntityTypeConfiguration<BankUser>
{
public void Configure(EntityTypeBuilder<BankUser> builder)
{
builder.ToTable(name: "bank_users");
builder.HasKey(b => b.Id);
builder.Property(b => b.Id).HasDefaultValueSql("NEWID()");
}
}
Then this is the Dto:
public class BankUserCreateDto
{
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Role { get; set; }
public string MobileNumber { get; set; }
public Guid BankBranchId { get; set; }
}
Finally the implementation:
public async Task<Response<string>> CreateBankUserAsync(BankUserCreateDto model)
{
var response = new Response<string>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var bankUser = _mapper.Map<ApplicationUser>(model);
var bankPassword = "#Password*123";
var result = await _userManager.CreateAsync(bankUser, bankPassword);
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(bankUser, model.Role);
var user = new BankUser()
{
UserId = bankUser.Id,
BankBranchId = model.BankBranchId,
Description = model.Description,
CreatedBy = _currentUserService.UserName
};
await _unitOfWork.AdminUsers.InsertAsync(user);
await _unitOfWork.Save();
_logger.Information("Bank User created successfully");
response.StatusCode = (int)HttpStatusCode.Created;
response.Successful = true;
response.Data = _mapper.Map<BankUserDto>(bankUser);
response.Message = "Bank User created successfully!";
transaction.Complete();
return response;
}
return response;
};
}
When I submitted, I got this error:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): The INSERT statement conflicted with the FOREIGN KEY constraint "FK_bank_users_bank_branches_BankBranchId". The conflict occurred in database "ERPs", table "dbo.bank_branches", column 'Id'.
The statement has been terminated.
How do I get this sorted out?
Thanks
I think you're having an issue with BankBranch not being populated. If that entity set (table) is empty, then when you're trying to add a BankUser there is no branch to map them to.
Also, you mentioned that BankBranches have many BankUsers. Your code explicitly says in BankBranch:
public virtual BankUser BankUser { get; set; }
This communicates that there is 1 BankUser to the BankBranch. I think you'd rather want:
public virtual IColelction<BankUser> BankUsers { get; set; }
And it seems you're mixing Attributes with FluentAPI. While this can be done, I would suggest trying to stick to one way or the other unless there's a reason not to. Even MS-EF says that attributes can do much of hard work, but FluentAPI is better for your edge cases.
I recently implemented OData in my ASP .NET Core web API. I have found success as long as I am returning the database models directly. I run into trouble, however, as soon as I attempt to return domain models instead.
The underlying issue involves mapping a data class to a domain class while maintaining the IQueryable return type. While I have found partial success using AutoMapper's MapTo extension method, I find that I am unsuccessful when using the $extend method to expand a collection of entities that are also domain objects.
I have created a sample project to illustrate this issue. You may view or download the full project on github here. See the description below.
Given the following two database classes:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
public Product() {
Orders = new Collection<Order>();
}
}
public class Order
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the following domain models...
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<OrderEntity> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public Double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
And the Products Controller
public class ProductsController
{
private readonly SalesContext context;
public ProductsController(SalesContext context) {
this.context = context;
}
[EnableQuery]
public IQueryable<ProductEntity> Get() {
return context.Products
.ProjectTo<ProductEntity>()
.AsQueryable();
}
}
All the following OData queries Pass:
http://localhost:51004/odata/Products
http://localhost:51004/odata/Orders
http://localhost:51004/odata/Orders?$expand=Products
The following query, however, does not pass:
http://localhost:51004/odata/Products?$expand=Orders
An HTTP response is never returned. The only failure message I get comes from the console:
System.InvalidOperationException: Sequence contains no matching element at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source, Func`2 predicate)
Finally, here is a reference to the mapping profile:
public static class MappingProfile
{
public static void RegisterMappings() {
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Order, OrderEntity>();
cfg.CreateMap<Product, ProductEntity>();
});
}
}
I can solve the issue by simply returning a List instead of an IEnumerable in the controller, but this of course would trigger a large query against the database that would be performance intensive.
As stated above, you can find a link to the full project on Github here. Let me know if you find any answers!
I was able to get this working with a few small revisions.
Updating the domain models:
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Order> Orders { get; set; }
}
public class OrderEntity
{
public int Id { get; set; }
public double Price { get; set; }
public DateTime OrderDate { get; set; }
[Required]
public int ProductId { get; set; }
public Product Product { get; set; }
}
Manually enabling expansion on the route builder:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SalesModelBuilder modelBuilder)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select();
routeBuilder.MapODataServiceRoute("ODataRoutes", "odata",
modelBuilder.GetEdmModel(app.ApplicationServices));
});
}
Using the following Queries:
http://localhost:51004/odata/Products
http://localhost:51004/odata/Orders
http://localhost:51004/odata/Orders?$expand=Product
http://localhost:51004/odata/Products?$expand=Orders
I have already created a working register user method in which a user is registered to the database. I have also created a working new package method which creates a new package. They both work fine however when a user is created his account type is set to null, I simply want to set the users "accountType" to be the "PackageName".
Package Model:
public class Packages
{
public int Id { get; set; }
public string PackageName { get; set; }
public int OrderLimit { get; set; }
public decimal Cost { get; set; }
public string ResetIntervalType { get; set; }
public int ResetInterval { get; set; }
}
User Model:
public class AppUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string AccountType { get; set; }
public int? PeriodOrderQty { get; set; }
public int? TotalOrderQty { get; set; }
public Guid? APIKey { get; set; }
//added packages foreign key, connection established
public Packages Package { get; set; }
public Cities City { get; set; }
public QualificationLevels Qualifications { get; set; }
}
Here is the put method i'm attempting to implement, after break pointing each component the main problem is the mapper being set towards the start of the method. However i'm not sure what else to try, only just started using these sort of methods.
[HttpPut("api/auth/assignpackage/{id}")]
public async Task<IActionResult> AssignPackage(int id ,[FromBody]Packages packageAssignValue)
{
try
{
var assignPackage = packageAssignValue;
var assignUser = Mapper.Map<Packages, AppUser>(packageAssignValue);
assignUser.AccountType = packageToEdit.PackageName;
var packageToEdit = await _context.Packages
.AsNoTracking()
.SingleOrDefaultAsync(m => m.Id == id);
assignUser.AccountType = assignPackage.PackageName;
if (packageToEdit == null)
{
return NotFound("Could not update Package as it was not Found");
}
else
{
_context.Update(assignUser);
await _context.SaveChangesAsync();
return Ok(assignUser);
}
}
catch (DbUpdateException)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
return NotFound("Package not found");
}
}
For reference here is my postman input:
{
"packageName": "Bronze"
}
Fixed the problem by solving problem with auto mapper, however a new error has appeared asked another question that shows more detail on updated method.
The profile:
public class StudentProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Student, StudentIndexModel>();
}
}
And the line it breaks on:
public ActionResult Index()
{
// Breaks here
var students = Mapper.Map<IEnumerable<StudentIndexModel>>(db.Students.ToList());
return View(students);
}
I'm getting an unsupported mapping exception when it gets records from the database. No exception when there are no records returned (the table has no records)
Error
Missing type map configuration or unsupported mapping.
Mapping types:
Student -> StudentIndexModel
ContosoUniversity.Models.Student -> ContosoUniversity.ViewModels.StudentIndexModel
Destination path:
IEnumerable`1[0]
The student class:
public class Student : Entity
{
public Student() { }
public string LastName { get; set; }
public string Forenames { get; set; }
public DateTime EnrolmentDate { get; set; }
public virtual ICollection<Enrolment> Enrolments { get; set; }
}
And the StudentIndexModel class:
public class StudentIndexModel
{
public int Id { get; set; }
public string LastName { get; set; }
public string Forenames { get; set; }
public DateTime EnrolmentDate { get; set; }
}
Where am I going wrong?
I've done it again, I find the answer to my own question 2 seconds later.
I added this to the Global.asax:
AutoMapperConfiguration.Configure();
Where AutomapperConfiguration has the Mapper.Initialize() method