I'm new into this sort of object programming, do you mind helping me?
I have to invoke the child nodes, but specific fields, but I can't figure out how to do that. I have already looked into a huge number of places, but it doesn't seem to work.
For example, I want just the FirstName in the RepunicAccount, and all the other info from RepunicAccountType, or anything like it.
My DbContext:
public class RepunicContext : DbContext
{
public virtual DbSet<RepunicAccount> RepunicAccount { get; set; }
public virtual DbSet<RepunicAccountType> RepunicAccountType { get; set; }
protected override void OnConfiguring (DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer ("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Repunic;Data Source=DESKTOP- 6I8LD45\\SQLEXPRESS_ERICH");
}
public RepunicContext (DbContextOptions<RepunicContext> options) :base (options)
{ }
public RepunicContext () { }
}
My model class:
public class RepunicAccount
{
public int ID { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey ("ID_Type")]
public int? ID_Type { get; set; }
public ICollection<RepunicAccountType> TipoConta { get; set; }
public DateTime? DataCadastro { get; set; } = DateTime.Now;
public DateTime? DataAlteracao { get; set; } = DateTime.Now;
}
My child node class:
public class RepunicAccountType
{
[Key]
public int ID_Type { get; set; }
public string Descricao { get; set; }
public DateTime DataCadastro { get; set; } = DateTime.Now;
public DateTime DataAlteracao { get; set; } = DateTime.Now;
}
I have a repository, that is where I do my coding, before putting into the controller, it's pretty much the same in every place, but I'm going to show the example that I'm trying to use in both places.
public IEnumerable<RepunicAccount> GetAllByIDType ()
{
var data = db.RepunicAccount.Where (a => a.ID_Type != null)
.Include (p => p.types);
var type = db.RepunicAccountType.OrderBy (b => b.Descricao);
return data.ToList();
}
The problem is: I dont know how to invoke specific items nor make anything else other thanToList();`
So, what should I do? If there is any more info that I can send, just ask me.
I would love to comment however, I do not have the required reputation to do so.
Since you are new to the subject please be sure to follow the documentation.
EF Core Documentation
I also have created a youtube video for getting started that I would suggest you take a few minutes to view.
Video on EF Core
in short, you need a DBContext and your Models. You then can start a query for data and returning in the format you wish. Like so:
using (var context = new RepunicContext())
{
var accounts = context.RepunicAccounts.ToList();
}
accounts will then have a list of every account that you can iterate through. Hope this helps.
Related
I created a .Net 5 Entity Framework Core web app. I let Visual Studio create the Models automatically from the database/dbcontext class.
The system works well, but I have this one controller that returns a list of breweries:
// GET: api/LocalBreweries
[HttpGet("LocalBreweries")]
public List<Brewery> LocalBreweries()
{
var breweries = _context.Brewery
.Include(p => p.Market)
.Include(p => p.Brewmaster)
.Where(p => p.IsSeasonal == true)
.ToList();
foreach (var brewery in breweries)
{
_ProcessData(brewery, brewery.Market, brewery.Brewmaster);
}
return breweries;
}
Based on the .Where clause, I should only be getting back 10 results.
In fact, when I set a breakpoint, I can see that breweries contains 10 items, which is correct.
However, when I navigate to the API controller endpoint in my browser, I get a huge list of breweries with tons of duplicates of the same breweries over and over.
I am not quite sure why this is happening, but I think it might have to do with how Entity Framework set up my Models.
When I comment out both of the Include statements above, the controller endpoint only returns the 10 results and no duplicates.
My question is, is there a way to fix my controller so that it doesn't return a huge list of duplicate breweries?
Thanks!
Here are my models for this controller:
public partial class Brewery
{
public Brewery()
{
BreweryTrainees = new HashSet<BreweryTrainees>();
}
public long Id { get; set; }
public string Title { get; set; }
public bool IsSeasonal { get; set; }
public long BrewmasterId { get; set; }
public long MarketId { get; set; }
public virtual Brewmaster Brewmaster { get; set; }
public virtual Market Market { get; set; }
public virtual ICollection<BreweryTrainees> BreweryTrainees { get; set; }
}
public partial class Brewmaster
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Brewery> Brewery { get; set; }
}
public partial class Market
{
public Market()
{
Brewery = new HashSet<Brewery>();
}
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Brewery> Brewery { get; set; }
}
I think it's becouse of ReferenceLoop in your model you can return json instead of list and ignore ReferenceLoop in it like this:
return JsonConvert.SerializeObject(breweries, Formatting.Indented,new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore});
I hope be helpful for you.
In my web API when I run project to get data from the database got this error
.net core 3.1
JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
These are my codes:
my Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
my productCategory class is:
public class ProductCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string CatText { get; set; }
public string ImagePath { get; set; }
public int Priority { get; set; }
public int Viewd { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
public DateTime CreateDate { get; set; }
public DateTime ModifyDate { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
my repo is
public async Task<IList<Product>> GetAllProductAsync()
{
return await _context.Products.Include(p => p.ProductCategory).ToListAsync();
}
my interface
public interface IProductRepository
{
...
Task<IList<Product>> GetAllProductAsync();
...
}
and this is my controller in api project
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductsController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public ActionResult Get()
{
return Ok(_productRepository.GetAllProduct());
}
}
When I run API project and put this URL: https://localhost:44397/api/products
I got that error,
I can't resolve it
this is happening because your data have a reference loop.
e.g
// this example creates a reference loop
var p = new Product()
{
ProductCategory = new ProductCategory()
{ products = new List<Product>() }
};
p.ProductCategory.products.Add(p); // <- this create the loop
var x = JsonSerializer.Serialize(p); // A possible object cycle was detected ...
You can not handle the reference loop situation in the new System.Text.Json yet (netcore 3.1.1) unless you completely ignore a reference and its not a good idea always. (using [JsonIgnore] attribute)
but you have two options to fix this.
you can use Newtonsoft.Json in your project instead of System.Text.Json (i linked an article for you)
Download the System.Text.Json preview package version 5.0.0-alpha.1.20071.1 from dotnet5 gallery (through Visual Studio's NuGet client):
option 1 usage:
services.AddMvc()
.AddNewtonsoftJson(
options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
// if you not using .AddMvc use these methods instead
//services.AddControllers().AddNewtonsoftJson(...);
//services.AddControllersWithViews().AddNewtonsoftJson(...);
//services.AddRazorPages().AddNewtonsoftJson(...);
option 2 usage:
// for manual serializer
var options = new JsonSerializerOptions
{
ReferenceHandling = ReferenceHandling.Preserve
};
string json = JsonSerializer.Serialize(objectWithLoops, options);
// -----------------------------------------
// for asp.net core 3.1 (globaly)
services.AddMvc()
.AddJsonOptions(o => {
o.JsonSerializerOptions
.ReferenceHandling = ReferenceHandling.Preserve
});
these serializers have ReferenceLoopHandling feature.
Edit : ReferenceHandling changed to ReferenceHandler in DotNet 5
but if you decide to just ignore one reference use [JsonIgnore] on one of these properties. but it causes null result on your API response for that field even when you don't have a reference loop.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
// [JsonIgnore] HERE or
public virtual ProductCategory ProductCategory { get; set; }
}
public class ProductCategory
{
public int Id { get; set; }
// [JsonIgnore] or HERE
public ICollection<Product> products {get;set;}
}
.NET 5 Web API
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddControllers()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
}
I have the same issue, my fix was to add async and await keyword since I am calling an async method on my business logic.
Here is my original code:
[HttpGet]
public IActionResult Get()
{
//This is async method and I am not using await and async feature .NET which triggers the error
var results = _repository.GetAllDataAsync();
return Ok(results);
}
To this one:
HttpGet]
public async Task<IActionResult> Get()
{
var results = await _repository.GetAllDataAsync();
return Ok(results);
}
In .Net 6, you can use System.Text.Json to initialize a startup action with AddControllersWithViews like this in Program.cs,
using System.Text.Json.Serialization;
builder.Services.AddControllersWithViews()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
also you can use AddMvc like this,
builder.Services.AddMvc()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
but quote from Ryan
asp.net core 3.0+ template use these new
methodsAddControllersWithViews,AddRazorPages,AddControllers instead of
AddMvc.
I will recommend to use the first solution.
Ensure you have [JsonIgnore] on the correct fields to avoid a circular reference.
In this case you will need
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
You probably don't need the ProductCategoryId field (depends if you are using EF and code first to define your DB)
Edit - In answer to noruk
There is often confusion in connected objects and navigation properties. You can get the data you want in JSON but also define the EF structures to get the correct DB structure (foreign keys, indexes, etc).
Take this simple example. A Product (for example a T-Shirt) has many sizes or SKUs (e.g. Small, Large, etc)
public class Product
{
[Key]
[MaxLength(50)]
public string Style { get; set; }
[MaxLength(255)]
public string Description { get; set; }
public List<Sku> Skus { get; set; }
}
public class Sku
{
[Key]
[MaxLength(50)]
public string Sku { get; set; }
[MaxLength(50)]
public string Barcode { get; set; }
public string Size { get; set; }
public decimal Price { get; set; }
// One to Many for Product
[JsonIgnore]
public Product Product { get; set; }
}
Here you can serialise a Product and the JSON data will include the SKUs. This is the normal way of doing things.
However if you serialise a SKU you will NOT get it's parent product. Including the navigation property will send you into the dreaded loop and throw the "object cycle was detected" error.
I know this is limiting in some use cases but I would suggest you follow this pattern and if you want the parent object available you fetch it separately based on the child.
var parent = dbContext.SKUs.Include(p => p.Product).First(s => s.Sku == "MY SKU").Product
I fixed my API Core Net6.0 adding [JsonIgnore]:
public class SubCategoryDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public int CategoryId { get; set; }
[JsonIgnore]
public Category Category { get; set; }
}
For net core 3.1 you have to add in Startup.cs:
services.AddMvc.AddJsonOptions(o => {
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.JsonSerializerOptions.MaxDepth = 0;
})
and import at least this package using nuget.org include prerelease:
<PackageReference Include="System.Text.Json" Version="5.0.0-rc.1.20451.14" />
following code is working for me in dotnet 5.0 :
services.AddControllersWithViews()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
Finally fixed mine with System.Text.Json not NewtonSoft.Json using
var options = new JsonSerializerOptions()
{
MaxDepth = 0,
IgnoreNullValues = true,
IgnoreReadOnlyProperties = true
};
Using options to serialize
objstr = JsonSerializer.Serialize(obj,options);
My project built with a similar error.
Here's the code before
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
public PrimaryClass primaryClass { get; set; }
}
I took away the PrimaryClass object from the DependentClass model.
Code after
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
}
I also had to adjust the OnModelCreating method from
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses).WithOne(d => d.primaryClass).HasForeignKey(d => d.PrimaryClassId);
to
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses);
The DbSet query that's running is
public async Task<List<DependentClass>> GetPrimaryClassDependentClasses(PrimaryClass p)
{
return await _dbContext.DependentClass.Where(dep => dep.PrimaryClassId == p.PrimaryClassId).ToListAsync();
}
The error could have been with any of these 3 sections of code, but removing the primary object reference from the dependent class and adjusting the OnModelCreating resolved the error, I'm just not sure why that would cause a cycle.
In my case the problem was when creating the entity relationships. I linked the main entity using a foreign key inside the dependent entity like this
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
also I referred the dipendend entity inside the main entity as well.
public List<Device> devices { get; set; }
which created a cycle.
Dependent Entity
public class Device
{
[Key]
public int id { get; set; }
public int asset_number { get; set; }
public string brand { get; set; }
public string model_name { get; set; }
public string model_no { get; set; }
public string serial_no { get; set; }
public string os { get; set; }
public string os_version { get; set; }
public string note { get; set; }
public bool shared { get; set; }
public int week_limit { get; set; }
public bool auto_acceptance { get; set; }
public bool booking_availability { get; set; }
public bool hide_device { get; set; }
public bool last_booked_id { get; set; }
//getting the relationships category 1 to many
public int category_id { get; set; }
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
public List<Booking> Bookings { get; set; }
}
Main Entity
public class Device_Category
{
public int id { get; set; }
public string name { get; set; }
public List<Device> devices { get; set; }
}
}
So I commented the
public List<Device> devices { get; set; }
inside main entity (Device_Category) and problem solved
I have created classes using EF Code First that have collections of each other.
Entities:
public class Field
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<AppUser> Teachers { get; set; }
public Field()
{
Teachers = new List<AppUser>();
}
}
public class AppUser
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public virtual List<Field> Fields { get; set; }
public AppUser()
{
Fields = new List<FieldDTO>();
}
}
DTOs:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<AppUserDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<AppUserDTO>();
}
}
public class AppUserDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
Mappings:
Mapper.CreateMap<Field, FieldDTO>();
Mapper.CreateMap<FieldDTO, Field>();
Mapper.CreateMap<AppUserDTO, AppUser>();
Mapper.CreateMap<AppUser, AppUserDTO>();
And I am getting StackOverflowException when calling this code (Context is my dbContext):
protected override IQueryable<FieldDTO> GetQueryable()
{
IQueryable<Field> query = Context.Fields;
return query.ProjectTo<FieldDTO>();//exception thrown here
}
I guess this happens because it loops in Lists calling each other endlessly. But I do not understand why this happens. Are my mappings wrong?
You have self-referencing entities AND self-referencing DTOs. Generally speaking self-referencing DTOs are a bad idea. Especially when doing a projection - EF does not know how to join together and join together and join together a hierarchy of items.
You have two choices.
First, you can force a specific depth of hierarchy by explicitly modeling your DTOs with a hierarchy in mind:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<TeacherDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<TeacherDTO>();
}
}
public class TeacherDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
}
public class AppUserDTO : TeacherDTO
{
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
This is the preferred way, as it's the most obvious and explicit.
The less obvious, less explicit way is to configure AutoMapper to have a maximum depth it will go to traverse hierarchical relationships:
CreateMap<AppUser, AppUserDTO>().MaxDepth(3);
I prefer to go #1 because it's the most easily understood, but #2 works as well.
Other option is using PreserveReferences() method.
CreateMap<AppUser, AppUserDTO>().PreserveReferences();
I use this generic method:
public static TTarget Convert<TSource, TTarget>(TSource sourceItem)
{
if (null == sourceItem)
{
return default(TTarget);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings);
return JsonConvert.DeserializeObject<TTarget>(serializedObject);
}
...
MapperConfiguration(cfg =>
{
cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...
When you giving 1 navigation_property to 2nd entity and visa-versa it go in an infinite loop state. So, the compiler automatically throws a Stackoverflow exception.
So, to avoid that, you just need to remove one navigation_property from any of the entities.
I am working with Entity Framework (code first) for the first time and I have a little problem.
I have a class called Taxi & one called Driver
Taxi has a reference to the Driver, you can see both classed below
public partial class Taxi
{
public Taxi()
{
}
public int TaxiId { get; set; }
public Driver Driver { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public Color Colour { get; set; }
public string NumPlate { get; set; }
public int MaxPassengers { get; set; }
}
public partial class Driver
{
public Driver()
{
}
public int DriverId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string HomePhone { get; set; }
public string MobilePhone { get; set; }
public DateTime JoinedFirm { get; set; }
}
And I am saving the changed Taxi like this:
using (var db = new DataModel())
{
db.Configuration.ProxyCreationEnabled = false;
db.Taxis
.Where(x => x.TaxiId == CurrenltySelectedTaxi.TaxiId)
.ToList()
.ForEach(x =>
{
x.Make = CurrenltySelectedTaxi.Make;
x.Model = CurrenltySelectedTaxi.Model;
x.NumPlate = CurrenltySelectedTaxi.NumPlate;
x.Colour = CurrenltySelectedTaxi.Colour;
x.MaxPassengers = CurrenltySelectedTaxi.MaxPassengers;
x.Driver = CurrenltySelectedTaxi.Driver;
});
db.SaveChanges();
}
My problem is that the Driver gets duplicated the in database every time I save a taxi.
You can see the database here:
Can someone point me in the right direction,
Thanks
EDIT:
The drivers are in a combo box and are selected like this
private void cmbTaxiDriver_SelectedIndexChanged(object sender, EventArgs e)
{
using (var db = new DataModel())
{
db.Configuration.ProxyCreationEnabled = false;
Driver listSelected = (Driver) cmbTaxiDriver.SelectedItem;
CurrenltySelectedTaxi.Driver = db.Drivers.Where(x => x.DriverId == listSelected.DriverId).ToArray()[0];
}
}
And the combo box is populated like this:
listDrivers.Items.AddRange(db.Drivers.ToArray());
The only thing that I can possibly find based on the content you have provided is that CurrenltySelectedTaxi.Driver is a detached or new driver. Be careful to ensure that the object pointed to by CurrenltySelectedTaxi.Driver is an attached driver record.
An option you can do to help determine the status of that record is to put a breakpoint on that line and then look at the DbEntry record status.
Your design doesn't create a relationship between Taxi and Driver that's going to translate to a database. Try something like this:
public partial class Taxi
{
public Taxi()
{
}
public int TaxiId { get; set; }
public int DriverId {get; set; }
public virtual Driver Driver { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public Color Colour { get; set; }
public string NumPlate { get; set; }
public int MaxPassengers { get; set; }
}
You may also want to add the following to the Driver object:
public virtual IEnumerable<Taxi> Taxis { get; set; }
So it turns out my issue is because i followed the tutorial on MSDN that says to recreate your context EVERYTIME you use it. Although this 'may' be good practice, in this instance it caused the problem.
If I make one context for the entire class it works as expected/
I have a Model like this
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<string> SolvedBy { get; set; }
}
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
}
and then Controller like this. But I cannot update the List "SolvedBy", the next time I step through with the debugger, the list is still empty.
[HttpPost]
public string Index(string flag = "", int id=0)
{
Challenge challenge = db.Challenges.Find(id);
if (flag == challenge.Flag)
{
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<string>();
}
chall.SolvedBy.Add(User.Identity.Name);
db.Entry(chall).State = EntityState.Modified;
db.SaveChanges();
//congrats, you solved the puzzle
return "got it";
}
else
{
return "fail";
}
}
is there any way around it to make a list of strings kept in the database?
EF don't know how to store an array in database table so it just ignore it. You can create another table/entity or use XML/JSON to store the list. You can serialize the list before saving and deserialize it after loading from database
A List<T> in a model would normally map to a second table, but in your DbContext you only have a single table. Try adding a second table.
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
public DbSet<Solution> Solutions {get; set;}
}
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<Solution> SolvedBy { get; set; }
}
public class Solution
{
public int ID { get; set; }
public string Name { get; set; }
}
Then your controller can use code along the lines of...
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<Solution>();
}
chall.SolvedBy.Add(new Solution {Name=User.Identity.Name});
None of the above has been tested and I may have made some mistakes there, but the general principle I want to illustrate is the fact that you need another table. The List<T> represents a JOIN in SQL.