One to many relationship mapping error with code first 6.0 - c#

I have two entities Task and Attempt, One task has many Attempt, so I defined the entities as below.
public class Task
{
public long TaskId { get; set; }
[Required]
public int DestinationNumber { get; set; }
[Required]
public int CountryCode { get; set; }
// blah blah
public virtual ICollection<Attempt> Attempts { get; set; }
}
public class Attempt
{
public long Id { get; set; }
public string AttemptsMetaData { get; set; }
public DateTime Time { get; set; }
public bool Answered { get; set; }
public DateTime Disconnected { get; set; }
// foreign key
public long TaskId { get; set; }
public virtual Task Task { get; set; }
}
I used Code First Relationships Fluent API to map the relationship.
public class OutboundContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
public DbSet<Attempt> Attempts { get; set; }
public OutboundContext()
: base("Outbound")
{
Database.SetInitializer<OutboundContext>(new CreateDatabaseIfNotExists<OutboundContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Task>().HasMany(t => t.Attempts).WithRequired();
}
Unit test was passed but when I check the table [Outbound].[dbo].[Attempts]. The columns are
[Id]
,[AttemptsMetaData]
,[Time]
,[Answered]
,[Disconnected]
,[Task_TaskId]
,[Task_TaskId1]
You see first [Task_TaskId] is wrong and one more extra column [Task_TaskId1] was generated.
What is wrong?
EDIT:
TaskId should be the foreign key.
// Add dummy data
public bool AddAttempt(List<Attempt> attempt)
{
using (OutboundContext db = new OutboundContext())
{
foreach (var item in attempt)
{
db.Attempts.Add(item);
}
try
{
db.SaveChanges();
return true;
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
}
List<Attempt> attempts = new List<Attempt>();
Attempt objAtt = new Attempt();
long id = 123456789;
objAtt.AttemptId = id;
objAtt.Time = DateTime.Now;
objAtt.AttemptsMetaData = "test";
objAtt.Answered = true;
objAtt.Disconnected = DateTime.Now;
objAtt.TaskId = 33333333;
attempts.Add(objAtt);
//Then call AddAttempt

It seems that the problem happened because the Collection of Attempt you have in the Task class is not being actually referenced to the "Task" property you are having in the Attempt class. My suggestion is to clearly declare a int TaskId property in the Attempt class and do the mapping in the following way for Attempt Class:
HasRequired(attempt => attempt.Task)
.WithMany(task => task.Attempts)
.HasForeignKey(attempt => attempt.TaskId);
Hope this helps.

Using Fluent API.
modelBuilder.Entity<Task>().HasMany(t => t.Attempts).WithRequired(t=>t.Task).HasForeignKey(t => t.TaskId);
The other point is that we have to save the tables in correct order. Something like:
db.Tasks.Add(task);
db.SaveChanges();
Attempt a = new Attempt() { Id = Guid.NewGuid(), TaskId = task.TaskId, AttemptsMetaData = "1" };
db.Attempts.Add(a);
db.SaveChanges();
Otherwise it shows "invalid column" on TaskId in SQL Server Management Studio.

I made a very small change to the OnModelCreating and its not creating a an additional [Task_TaskId1] in the database:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Task>().HasMany(t => t.Attempts);
}

Related

How to add value to a navigation property with Blazor and EF?

I have a reservation and a ship class for the DB with the Ship and Reservations navigation properties,
public class Reservation {
public Reservation() {
Ship = new Ship();
}
public int Id { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public Ship Ship { get; set; }
}
public class Ship {
public int Id { get; set; }
public string Name { get; set; }
public string Port{ get; set; }
public List<Reservation> Reservations { get; set; }
}
The DBcontext.cs file:
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Reservation>().HasOne(r => r.Ship).WithMany(s => s.Reservations);
modelBuilder.Entity<Reservation>().HasOne(u => u.Person).WithMany(r => r.Reservations);
modelBuilder.Entity<Ship>().HasMany(res => res.Reservations).WithOne(s => s.Ship);
}
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Ship> Ships { get; set; }
}
}
The reservationController.cs on the sever side:
[HttpPost]
public async Task<IActionResult> CreateReservation(ReservationGetDTO reservationDTO) {
_context.Reservations.Add(Mapper.Map(reservationDTO, new Reservation()));
await _context.SaveChangesAsync();
return await GetReservations();
}
The reservationService.cs on the client side:
public async Task<List<ReservationGetDTO>> CreateReservation(ReservationPostDTO reserv) {
var result = await _httpClient.PostAsJsonAsync("api/reservations", reserv);
reservations = await result.Content.ReadFromJsonAsync<List<ReservationGetDTO>>();
return reservations;
}
And the razor page that uses this service:
#code {
[Parameter]
public ShipDTO SelectedShip { get; set; }
private ReservationPostDTO reservation = new ReservationPostDTO {
FromDate = DateTime.Today,
ToDate = DateTime.Today.AddDays(1),
};
async void HandleSubmit() {
reservation.Ship = SelectedShip;
await ReservationService.CreateReservation(reservation);
}
When I press the submit reservation button and the HandleSubmit function is called I got the following error: Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert explicit value for identity column in table 'Ships' when IDENTITY_INSERT is set to OFF.
So I tried this:
public async Task CreateReservation(ReservationGetDTO reservationDTO)
{
await _context.Database.ExecuteSqlRawAsync("SET IDENTITY_INSERT [dbo].[Reservations] ON");
_context.Reservations.Add(Mapper.Map(reservationDTO, new Reservation()));
await _context.SaveChangesAsync();
await _context.Database.ExecuteSqlRawAsync("SET IDENTITY_INSERT [dbo].[Reservations] OFF");
}
//I also tried to put this annotation to the Ship
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Ship Ship { get; set; }
I also tried this modelBuilder.Entity<Reservation>().Property(a => a.Ship).ValueGeneratedNever(); But I got an error that said the ship is a navigation property so i cant use this function
Any ideas?
If I understood you correctly, then ship id property is used as an index by EF by default naming convention, if you set it as code first.
I would try to set this property to a default value of it's kind (0 for int, null for string, etc) - it should allow EF to insert it into the database - this works if you want to actually add a ship.
If there is no ship in newly created entity, than just set ship to null - should do the trick.
THe reason why I did not work was that I tried to add a value to the navigation property on the client side, which you only can do on the server side

How to hide items, in an API reply, from a db query?

I'm currently using MVC with EF to have a small server with API querying a SQL database. But in the API reply I'm not able to hide some parameters.
The main object
public class AssetItem
{
[Key]
public Int32 AssetId { get; set; }
public String AssetName { get; set; }
public int OdForeignKey { get; set; }
[ForeignKey("OdForeignKey")]
public OperationalDataItem OperationalDataItem { get; set; }
}
The other one:
public class OperationalDataItem
{
[Key]
public Int32 OperationalDataId { get; set; }
public String Comunity { get; set; }
public List<AssetItem> AssetItems { get; set; }
}
From what I have read, this should be ok, I have also set the context:
public AssetContext(DbContextOptions<AssetContext> options) : base(options)
{}
public DbSet<AssetItem> AssetItems { get; set; }
public DbSet<OperationalDataItem> OperationalDataItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetItem>().HasOne(p =>
p.OperationalDataItem).WithMany(b => b.AssetItems).HasForeignKey(p =>
p.OdForeignKey);
}
And the seeding in program.cs
context.AssetItems.Add(
new AssetItem { AssetName = "Test test", OdForeignKey = 1,
OperationalDataItem =
new OperationalDataItem {Comunity = "Comunity1" }});
So calling the API this results in:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":null }
From what I read this is because of the lazy loading, how can I hide the result operationalDataItem?
In case is not possible i have of course try to query for it and give it back and it give something like:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":
{ "operationalDataId":1,
"comunity":"Comunity1",
"assetItems":[
But in this case I would like to hide "assetsItems" in the reply to the FE.
How can I hide those parameters?
The API is quite simple, just an example code:
var todoItem = await _context.AssetItems.FindAsync((Int32)id);
var item = _context.OperationalDataItems.Find((Int32)todoItem.OdForeignKey);
todoItem.OperationalDataItem = item;
return todoItem
If you want to fetch data from the database, but you only want to fetch some properties, use Select. Usually this is more efficient than using Find, because you'll only transfer the data that you actually plan to use.
To fetch some properties of the assetItem that has primary key assetItemId:
var result = dbContext.AssetItems
.Where(assetItem => assetItem.AssetItmId = assetItemId)
.Select(assetItem => new
{
// Select only the properties that you plan to use
Id = assetItem.AssertItemId,
Name = assetItem.Name,
OperationalData = new
{
// again, select only the properties that you plan to use
Id = assetItem.OperationalData.OperationalDataId,
Community = assetItem.OperationalData.Community,
},
})
.FirstOrDefault();
Or the other way round:
Fetch several properties of all (or some) OperationalDataItems, each with some properties of all (or some) of its AssetItems:
var result = dbContext.OperqationalDataItems
.Where(operationalDataItem => ...) // only if you don't want all
.Select(operationalDataItem => new
{
Id = operationalDataItem.Id,
Community = operationalDataItem.Community
AssetItems = operationalDataItem.AssetItems
.Where(assetItem => ...) // only if you don't want all its assetItems
.Select(assetItem => new
{
// Select only the properties you plan to use:
Id = assetItem.Id,
...
// not useful: you know the value of the foreign key:
// OperationalDataId = assetItem.OperationalDataId,
})
.ToList();
})
.ToList(); // or: FirstOrDefault if you expect only one element
Entity framework knows your one-to-many relation and is smart enough to know which (group-)join is needed for your query.
Some side remarks
You've declare your many-relation a List<AssetItem>. Are you sure that operationalDataItem.AssetItems[4] has a defined meaning? Wouldn't it be better to stick to the entity framework code first conventions? This would also eliminate the need for most attributes and / or fluent API
public class OperationalDataItem
{
public int Id { get; set; }
public String Comunity { get; set; }
...
// Every OperationalDataItem has zero or more AssetItems (one-to-many)
public virtual ICollection<AssetItem> AssetItems { get; set; }
}
public class AssetItem
{
public int Id { get; set; }
public String Name { get; set; }
...
// every AssetItem belongs to exactly one OperationalDataItem, using foreign key
public int OperationDataItemId { get; set; }
public virtual OperationalDataItem OperationalDataItem { get; set; }
}
In entity framework the columns of a table are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Because I stuck to the conventions, no attributes nor fluent API is needed. Entity framework is able to detect the one-to-many relation and the primary and foreign keys. Only if I am not satisfied with the names or the types of the columns I would need fluent API.

Entity Framework Circular Reference in same table with both required

I want to have pair of rows for every transfer which are poi
I have:
public class Transfer : DependentRestrictedEntity
{
public virtual Account Acount { set; get; }
public DateTime Time { set; get; }
public Transfer PairedTransfer { set; get; }
[NotMapped]
public override RestrictedEntity DependentOn => Acount;
//other code.
}
public class Account : RestrictedEntity
{
public string Name { get; set; }
//other code.
}
public class RestrictedEntity : Entity
{
public Filter Filter { get; set; }
//other code.
}
public abstract class DependentRestrictedEntity : RestrictedEntity
{
[NotMapped]
public abstract RestrictedEntity DependentOn { get; }
//other code.
}
public class Entity
{
[Key, JsonProperty, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { set; get; }
//other code.
}
public class MyMoneyContext : UserDbContext
{
public DbSet<Account> Accounts { get; set; }
public DbSet<Transfer> Transfers { get; set; }
protected override void OnModelCreating(DbModelBuilder _modelBuilder)
{
base.OnModelCreating(_modelBuilder);
_modelBuilder.Entity<Transfer>()
.HasRequired(_t => _t.PairedTransfer)
.WithRequiredDependent();
}
//other code
}
Somehow I need pair 2 Transfers. One of them should belong to 1st account, and second one should belong to 2nd account. User which have permissions to first account but do not have permission to second account should not be able to see both of those entries.
Problem here is that that I do not know how to add new entry. I tried:
var accounts = Database.Accounts.ToList();
Transfer pair, pair2;
Database.Transfers.Add(
pair = new Transfer()
{
ID = Guid.NewGuid(),
Ammount = 100,
Filter = accounts[0].Filter,
Acount = accounts[0],
}
);
Database.Transfers.Add
(
pair2 = new Transfer()
{
ID = Guid.NewGuid(),
Ammount = -100,
Filter = accounts[1].Filter,
Acount = accounts[1],
}
);
pair.PairedTransfer = pair2;
pair2.PairedTransfer = pair;
Database.SaveChanges();
But I am getting circular Reference problem.
Problem is that EF does not know which Transfer add to the DB first, because 1. have reference to 2. and 2. have reference to 1. (so it can't assign foreign keys)
I recommend to insert first transfer with null PairedTransfer, then insert second with assigned first PairedTransfer, then finally assign to second transfer to first's PairedTransfer :)

The value of the navigation property of my EntityFramework class created in code fist mode always be null

I am a beginner of EntityFramework. The codes below is extracted form my project.
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserId { get; set; }
public virtual int UserType { get; set; }
}
public class Person : User
{
public override int UserType
{
get
{
return 0;
}
set
{
base.UserType = 0;
}
}
public string PersonName { get; set; }
public ICollection<Sunny.Models.WorkExperience> WorkExperiences { get; set; }
}
public class WorkExperience
{
[Key]
public int ExperienceId { get; set; }
public int PersonId { get; set; }
public string Job { get; set; }
[ForeignKey("PersonId")]
public Person Person { get; set; }
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
this.Map<User>(user => user.ToTable("User"));
this.Map<Person>(person => person.ToTable("Person"));
}
}
public class DbContext : System.Data.Entity.DbContext
{
public DbContext() : base("name=Model")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserConfiguration());
modelBuilder.Conventions.Remove<Conventions.PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Person> Persons { get; set; }
public DbSet<WorkExperience> WorkExperiences { get; set; }
}
static void Main(string[] args)
{
DbContext context = new Models.DbContext();
Person person = new Models.Person();
person.UserId = 1;
person.PersonName = "Name";
context.Persons.Add(person);
WorkExperience experience = new Models.WorkExperience();
experience.PersonId = 1;
experience.Job = "Coder";
context.WorkExperiences.Add(experience);
context.SaveChanges();
context = new DbContext();
Console.Write(context.WorkExperiences.First().Person == null);
Console.Read();
}
The running result of the Main method above is displaying true ,That is to say ,the value of the property WorkExperiences.Person always be null .But i have inserted data into the tables .
How to let the property WorkExperiences.Person load with the referenced key value ? Thanks in advance for any help.
Entity framework won't automatically load associated entities unless you specifically query for them.The reason is that it would be too easy to load far more than you expected if you always loaded all navigation properties - you might end up pulling most of your database back even on a simple query, if you have a lot of relationships. Imagine if you went to Amazon and it ran a query for your orders, which then included all products in those orders, which then included all sellers from those products, which then included all products from those sellers, ...
Entity Framework gives you several techniques to control when you want to load related data.
You can use DbExtensions.Include() to force it to include a related entity with the original query, which means one trip to the database:
Console.Write(context.WorkExperiences.Include(w => w.Person).First().Person == null);
Alternatively, you can use .Load() to force the load of an entity which isn't loaded:
var firstWE = context.WorkExperiences.First();
firstWE.Reference("Person").Load();
Console.Write(firstWE.Person == null);
Or you can enable lazy loading, which will make it load on demand the first time you access the property. You do this by adding virtual to it (which allows EF the ability to add some code to your property and load on demand):
public virtual Person Person { get; set; }

Entity Framework many to many relationship insert

I have a Web API application with entity framework 6.
I have 2 entities - Player and Match with many to many relationship between them:
public class Player {
public string PlayerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Match> Matches { get; set; }
}
public class Match {
public string MatchId { get; set; }
public DateTime DateTime { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
This is how my DBContext looks like:
public class FIFA15RankingContext : DbContext {
public DbSet<Player> Players { get; set; }
public DbSet<Match> Matches { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Player>()
.HasMany(t => t.Matches)
.WithMany(t => t.Players)
.Map(m => {
m.ToTable("PlayerMatches");
m.MapLeftKey("PlayerId");
m.MapRightKey("MatchId");
}
);
base.OnModelCreating(modelBuilder);
}
}
EF created for me behind the scenes the table PlayerMatches (PlayerId, MatchId).
This is my MatchesController (as scaffolded by VS 2013):
// POST: api/Matches
[ResponseType(typeof(Match))]
public async Task<IHttpActionResult> PostMatch(Match match)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
db.Matches.Add(match);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (MatchExists(match.MatchId))
return Conflict();
else
throw;
}
return CreatedAtRoute("DefaultApi", new { id = match.MatchId }, match);
}
I want to be able to create new match and associate with it existing users.
I couldn't make it work in any other way but to add this code (which smells pretty bad) before db.Matches.Add(match):
var playersFromDB = new List<Player>();
if (match.Players != null) {
foreach (var p in match.Players) {
playersFromDB.Add(db.Players.Find(p.PlayerId));
}
}
match.Players = playersFromDB;
What am I doing wrong?
If all players already exists in database, you can attach them to dbcontext instead of load from db.
if (match.Players != null) {
foreach (var player in match.Players) {
db.Players.Attach(player);
}
}
Manually attach the User entities and set their state to Unchanged
foreach( var p in match.Players )
{
db.Entry( p ).State = EntityState.Unchanged;
}

Categories

Resources