EF core optional relationship not working - .net-core

I have the following (unorthodox) setup:
public class OrderShipment
{
public Guid Id { get; set; }
...
public string Number { get; set; }
}
public class LogisticsAdministrationLine
{
public Guid Id { get; set; }
...
public string OrderShipmentNumber { get; set; }
public OrderShipment OrderShipment { get; set; }
}
modelBuilder.Entity<OrderShipment>(e =>
{
e.HasMany(x => x.LogisticsAdministrationLines)
.WithOne(x => x.OrderShipment)
.HasForeignKey(x => x.OrderShipmentNumber)
.HasPrincipalKey(x => x.Number)
.OnDelete(DeleteBehavior.NoAction);
});
Since both keys are string which is nullable this should be an optional relationship that shouldn't really be enforced in the database.
My issue here is that OrderShipments are frequently deleted and recreated, but the LogisticsAdministrationLines remain. This is why i want an optional relationship.
Unfortunately when now deleting an Ordershipment i get the following error:
Microsoft.Data.SqlClient.SqlException (0x80131904):
The DELETE statement conflicted with the REFERENCE constraint "FK_LogisticsAdministrationLines_OrderShipments_OrderShipmentNumber".
The conflict occurred in database "REDACTED", table "dbo.LogisticsAdministrationLines", column 'OrderShipmentNumber'.
How can i properly create an optional relationship between my 2 entities?

Related

Establishing one to one relations with Entity Framework 7

Having the following parent class:
[Table("smart_recharge_registro")]
public class SmartRechargeRegistro
{
[Key]
public int id { get; set; }
public SmartRechargeRequest request { get; set; }
public SmartRechargeProceso proceso { get; set; }
public SmartRechargeResponse response { get; set; }
}
Which in turn references the following child classes:
[Table("smart_recharge_request")]
public class SmartRechargeRequest
{
public String nombreDeUsuario { get; set; }
public String passwordDeUsuario { get; set; }
public String msisdnSuscriptor { get; set; }
}
and:
[Table("smart_recharge_proceso")]
public class SmartRechargeProceso
{
[Key]
public int id { get; set; }
public String carrierId { get; set; }
public String cliente { get; set; }
public String network { get; set; }
}
and lastly:
[Table("smart_recharge_response")]
public class SmartRechargeResponse
{
public Boolean responseSuccess { get; set; }
public int responseCode { get; set; }
public String? responseDetails { get; set; }
}
The Add-Migration and Update-Database command execute without problems. However, when I try to save
await _repository.RegistroColeccion.AddAsync(registro);
await _repositorio.SaveChangesAsync();
I get the following error:
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Cannot add
or update a child row: a foreign key constraint fails
(beservicebroker_dev.registro_eventos_srdotnet, CONSTRAINT
FK_registro_eventos_srdotnet_SmartRechargeProceso_procesoid FOREIGN
KEY (procesoid) REFERENCES smartrechargeproceso (id) O)
To solve the problem, I tried to create one-to-one relationships following this tutorial
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.request)
.WithOne(r => r.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeRequest>(r => r.id);
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.proceso)
.WithOne(p => p.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeProceso>(p => p.id);
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.response)
.WithOne(r => r.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeResponse>(r => r.id);
Inside SmartRechargeRequest, SmartRechargeProceso and SmartRechargeResponse, added the following:
[JsonIgnore]
public SmartRechargeRegistro SmartRechargeRegistro { get; set; }
Also added inside SmartRechargeRequest and SmartRechargeResponse an id
[Key]
public int id { get; set; }
I'm still unable to test the endpoint because the SmartRechargeRequest and SmartRechargeResponse are completely disfigured in the swagger (even if the [JsonIgnore] or [IgnoreDataMember] annotations are set) due to the presence of that SmartRechargeRegistro object.
I'm pretty sure my solution is misguided and I'm getting the process completely wrong.
What would be the proper way to map one-to-one relationships for this case? Any help will be appreciated.
Please note that in reality, these classes are huge (dozens of properties), so it's not possible to merge all of them on a single table.

Entity Framework Core many-to-many relation - getting error "Invalid column name 'id'"

I'm doing a triple many-to-many relationship in the same relationship table in Entity Framework Core.
The certificate, language and candidate entities are related to each other from many-to-many in the CandidateLanguageCertificationLanguage relationship table.
The database tables are:
CREATE TABLE candidato.Candidato
(
ID_CANDIDATO int ,
NOMBRE VARCHAR(50) ,
PRIMARY KEY(ID_CANDIDATO)
);
CREATE TABLE candidato.Idiomaa
(
ID_IDIOMA int ,
NOMBRE VARCHAR(50) ,
PRIMARY KEY(ID_IDIOMA)
);
CREATE TABLE candidato.CertificadoIdiomaa
(
ID_CERTIFICADO_IDIOMA int,
NOMBRE VARCHAR(50),
PRIMARY KEY(ID_CERTIFICADO_IDIOMA)
);
CREATE TABLE candidato.CandidatoIdiomaCertificacionIdiomaa
(
ID_CANDIDATO int,
ID_IDIOMA int,
ID_CERTIFICADO_IDIOMA int,
PRIMARY KEY(ID_CANDIDATO, ID_IDIOMA, ID_CERTIFICADO_IDIOMA),
FOREIGN KEY (ID_CANDIDATO) REFERENCES candidato.Candidato (id_candidato),
FOREIGN KEY (ID_IDIOMA) REFERENCES candidato.Idiomaa (ID_IDIOMA),
FOREIGN KEY (ID_CERTIFICADO_IDIOMA) REFERENCES candidato.CertificadoIdiomaa (ID_CERTIFICADO_IDIOMA)
);
The relationships established in the context through Fluent API are:
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>().ToTable("CandidatoIdiomaCertificacionIdiomaa", "candidato");
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>().HasKey(cici => new { cici.IdCandidato, cici.IdIdioma, cici.IdCertificadoIdioma });
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>().Property(cici => cici.IdCandidato).HasColumnName("Id_Candidato");
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>().Property(cici => cici.IdIdioma).HasColumnName("Id_Idioma");
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>().Property(cici => cici.IdCertificadoIdioma).HasColumnName("Id_Certificado_Idioma");
builder.Entity<CertificadoIdiomaa>().ToTable("CertificadoIdiomaa", "candidato");
builder.Entity<CertificadoIdiomaa>().HasKey(ci => ci.Id);
builder.Entity<CertificadoIdiomaa>().Property(ci => ci.Id).HasColumnName("Id_Certificado_Idioma").ValueGeneratedOnAdd();
builder.Entity<CertificadoIdiomaa>().Property(ci => ci.Nombre).HasColumnName("Nombre");
builder.Entity<Idiomaa>().ToTable("Idiomaa", "candidato");
builder.Entity<Idiomaa>().HasKey(i => i.Id);
builder.Entity<Idiomaa>().Property(i => i.Id).HasColumnName("Id_Candidato").ValueGeneratedOnAdd();
builder.Entity<Idiomaa>().Property(i => i.Nombre).HasColumnName("Nombre");
builder.Entity<Candidato>().ToTable("Candidato", "candidato");
builder.Entity<Candidato>().HasKey(i => i.Id);
builder.Entity<Candidato>().Property(i => i.Id).HasColumnName("Id_Candidato").ValueGeneratedOnAdd();
builder.Entity<Candidato>().Property(i => i.Nombre).HasColumnName("Nombre");
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>()
.HasOne(cici => cici.CertificadoIdioma)
.WithMany(cei => cei.CandidatosIdiomasCertificacionesIdiomaas)
.HasForeignKey(cf => cf.IdCertificadoIdioma);
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>()
.HasOne(cici => cici.Candidato)
.WithMany(c => c.CandidatosIdiomasCertificacionesIdiomaas)
.HasForeignKey(cici => cici.IdCandidato);
builder.Entity<CandidatoIdiomaCertificacionIdiomaa>()
.HasOne(cici => cici.Idioma)
.WithMany(cei => cei.CandidatosIdiomasCertificacionesIdiomaas)
.HasForeignKey(cf => cf.IdIdioma);
The models are:
public class Idiomaa
{
#region propiedades
public int Id { get; internal set; }
public string Nombre { get; internal set; }
//public ICollection<CandidatoIdiomaa> CandidatosIdiomas { get; internal set; }
public ICollection<CandidatoIdiomaCertificacionIdiomaa> CandidatosIdiomasCertificacionesIdiomaas { get; internal set; }
#endregion
}
public class CertificadoIdiomaa
{
public int Id{ get; internal set; }
public string Nombre { get; internal set; }
public ICollection<CandidatoIdiomaCertificacionIdiomaa> CandidatosIdiomasCertificacionesIdiomaas { get; internal set; }
}
public class CandidatoIdiomaCertificacionIdiomaa
{
#region propiedades
public int IdCandidato { get; internal set; }
public int IdIdioma { get; internal set; }
public int IdCertificadoIdioma { get; internal set; }
public CertificadoIdiomaa CertificadoIdioma { get; internal set; }
public Candidato Candidato { get; internal set; }
public Idiomaa Idioma { get; internal set; }
#endregion
}
public class Candidato
{
#region propiedades
public int Id { get; internal set; }
public string CorreoElectronico { get; internal set; }
#endregion
}
The query I wrote is:
contexto.Where(certificadoIdioma => certificadoIdioma.Nombre.Contains(filtro))
.Include(ci => ci.CandidatosIdiomasCertificacionesIdiomaas)
.ThenInclude(cici => cici.Idioma).ToList();
The error I get:
Invalid column name 'Id_Candidato'
Invalid column name 'Id_Candidato'
The funny thing is that if I change the include of the query to candidate it works perfect:
contexto.Where(certificadoIdioma => certificadoIdioma.Nombre.Contains(filtro))
.Include(ci => ci.CandidatosIdiomasCertificacionesIdiomaas)
.ThenInclude(cici => cici.candidato).ToList();
The tables have been made by a DBA, I am a programmer and I cannot modify the database, I know that you could make the relations in several tables instead of one, your reasons will be, that's another story. I am sure that in Entity Framework Core this can be done but I do not know how, I suspect that I have made a mistake when setting up the relationships in Fluent API, but I have been reviewing it for hours and nothing ...
Could someone take a look at it?
Thank you so much guys.
regards
You are missing int keyword:
public class Idiomaa
{
#region propiedades
public Id { get; internal set; } here int keyword is missing
public string Nombre { get; internal set; }
//public ICollection<CandidatoIdiomaa> CandidatosIdiomas { get; internal set; }
public ICollection<CandidatoIdiomaCertificacionIdiomaa> CandidatosIdiomasCertificacionesIdiomaas { get; internal set; }
#endregion
}
public Id { get; internal set; } here int keyword is missing

InvalidOperationException: The entity type 'Array' requires a primary key to be defined

I am making a Web API with .Net and it is receiving a custom JSON object from a web form to serialize to the model. I am currently having an issue displaying the API response. I am storing the data in an in memory database at first just to get it working.
I am not sure why it is asking me for a primary key despite there being a key defined on my model.
My next step is to add multipart-form data but that is its own can of worms.
The error happens on the _context.ProjectItems.ToListAsync(); line.
I have already added an ID to the model.
[Required]
[Key]
public new long Id { get; set; }
[HttpGet("{id}")]
public async Task<ActionResult<ProjectItem>> GetProjectItem(long id)
{
var projectItem = await _context.ProjectItems.FindAsync(id);
if (projectItem == null)
{
return NotFound();
}
return projectItem;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<ProjectItem>>>
GetProjectItems()
{
return await _context.ProjectItems.ToListAsync();
}
My model: ProjectItem.cs
using System.ComponentModel.DataAnnotations;
namespace RupAPI.Controllers
{
public class ProjectItem : Project
{
[Required]
[Key]
public new long Id { get; set; }
public string ProjName { get; set; }
public DateTime DateSub { get; set; }
public DateTime DueDate { get; set; }
public DateTime ArrivalDate { get; set; }
public string Desc { get; set; }
public Array Sku { get; set; }
public Array SkuDesc { get; set; }
public Array Item { get; set; }
public Array ItemDesc { get; set; }
public Array FileName { get; set; }
public new byte[] File { get; set; }
}
}
An unhandled exception occurred while processing the request. InvalidOperationException: The entity type 'Array' requires a primary key to be defined.
Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys(IModel model)
Stack Query Cookies Headers
InvalidOperationException: The entity type 'Array' requires a primary key to be defined.
+
return await _context.ProjectItems.ToListAsync();
lambda_method(Closure , object )
Reconsider the usage of Array as type for your properties, which cause this issue.
If those properties are really more than just string, create additional model(s) to represent the properties and use relationships to store them in your database.
If the properties are lists of strings, there are several solutions proposed here. If possible I would go for Sasan's solution, which is for your case to replace Array with string[] and add
modelBuilder.Entity<ProjectItem>()
.Property(e => e.Sku)
.HasConversion(
v => string.Join(',', v),
v => v.Split(',', StringSplitOptions.RemoveEmptyEntries));
// TODO: also to that for the other properties
to your database context's OnModelCreating.

Return additional Entity not mapped, on an odata controller

I am in need of returning a list of entities (Proposals) from an odata controller, but it needs to also return another Property entity together (Summary) with it that is populated from the Summarize method. This other entity is not persisted nor does existing in the dbcontext (builder.Ignore(p => p.Summary)).
The issue is that it can't be a complextype for my use, so I tried to add it as a EntitySet on the buiilder and Automatic expand on the parent (as I can't add it to the query the expand), but then every time I try to query it, the following exception raises.
System.Runtime.Serialization.SerializationException: Cannot serialize a null 'ResourceSet'.
at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) [...]
I wonder if this happens because of the entity not existing to the context.
So is there a way to make this work? to not have this other entity persisted but return it together with the odata query for its parent?
Results:
{"#odata.context":"http://localhost:57450/OData/$metadata#Proposal","value":[{"ProposalID":"e91cacfc-f345-4617-bd54-b1f440e4fd65","CustomerNumber":null,"Description":"","ServiceType":"LeaseReturn","Currency":null,"PartnerName":null,"QuoteDate":"0001-01-01T00:00:00-02:00","CreatedOn":"2018-08-06T16:27:52.8169404-03:00","CreatedBy":"Admin","UpdatedOn":null,"UpdatedBy":null,"ProposalServiceFees":[]},{"ProposalID":"28ddfea6-2ac9-4898-b72f-d5e284d5072f","CustomerNumber":"2","Description":"a","ServiceType":"ResaleAndRecycle","Currency":null,"PartnerName":null,"QuoteDate":"0001-01-01T00:00:00-02:00","CreatedOn":"2018-08-15T15:11:07.531755-03:00","CreatedBy":"Admin","UpdatedOn":null,"UpdatedBy":null,"ProposalServiceFees":[]}]}
Expected:
{"#odata.context":"http://localhost:57450/OData/$metadata#Proposal","value":[{"ProposalID":"e91cacfc-f345-4617-bd54-b1f440e4fd65","CustomerNumber":null,"Description":"","ServiceType":"LeaseReturn","Currency":null,"PartnerName":null,"QuoteDate":"0001-01-01T00:00:00-02:00","CreatedOn":"2018-08-06T16:27:52.8169404-03:00","CreatedBy":"Admin","UpdatedOn":null,"UpdatedBy":null,"Summary":{"SummaryId":"e91cacfc-f345-4617-bd54-b1f440e4fd65","Items":[{"Type":"workstation","Price":0.00}],"Fees":[]},"ProposalServiceFees":[]},{"ProposalID":"28ddfea6-2ac9-4898-b72f-d5e284d5072f","CustomerNumber":"2","Description":"a","ServiceType":"ResaleAndRecycle","Currency":null,"PartnerName":null,"QuoteDate":"0001-01-01T00:00:00-02:00","CreatedOn":"2018-08-15T15:11:07.531755-03:00","CreatedBy":"Admin","UpdatedOn":null,"UpdatedBy":null,"Summary":{"SummaryId":"28ddfea6-2ac9-4898-b72f-d5e284d5072f","Items":[{"Type":"strings","Price":0.00}],"Fees":[]},"ProposalServiceFees":[]}]}
Entities:
public class Proposal : EntityBase {
public Guid ProposalID { get; set; }
public string CustomerNumber { get; set; }
public string Description { get; set; }
public string ServiceType { get; set; }
public string Currency { get; set; }
public Summary Summary { get; set; }
public string PartnerName { get; set; }
public DateTime QuoteDate { get; set; }
[...]
public void Summarize()
=> Summary = new Summary(Assets, ProposalServiceFees.Select(f=>f.ServiceFee), ProposalID);
}
public class Summary
{
public Guid SummaryId { get; set; }
public IEnumerable<SummaryItem> Items { get; private set; }
public IEnumerable<SummaryItem> Fees { get; private set; }
internal Summary(IEnumerable<Asset> assets, IEnumerable<ServiceFee> fees, Guid id)
{
SummaryId = id; //dummy for test
Items = assets
.GroupBy(asset => asset.ProductType, GroupIntoSummaryItem)
.ToList();
Fees = fees
.Select(f=> FeeToSummaryItem(f.ApplyServiceFee(assets.Count())))
.ToList();
}
[...]
}
Controller:
[EnableQuery]
[ODataRoute()]
public IQueryable<Proposal.Domain.Entities.Proposal> Get(ODataQueryOptions opts)
{
opts.Validate(settings);
IQueryable results = opts.ApplyTo(_context.Proposal.Include(x => x.Assets)).AsQueryable();
var values = (results as IQueryable<Proposal.Domain.Entities.Proposal>)?.ToList();
values?.ForEach(x => x.Summarize());
return values?.AsQueryable();
}
OdataConventionModelBuilder:
EntitySet<Summary>(nameof(Summary))
.EntityType
.HasKey(p => p.SummaryId)
.Filter()
.Count()
.Expand()
.OrderBy()
.Page()
.Select();
EntitySet<Domain.Entities.Proposal>(nameof(Domain.Entities.Proposal))
.EntityType
.HasKey(p => p.ProposalID)
.Expand(SelectExpandType.Automatic, nameof(Summary))
.Filter()
.Count()
.Expand()
.OrderBy()
.Page()
.Select();
This is most likely due to your model structure not matching your DB structure. This happens quite often when you cut/paste and forget to rename an entity.

How to reference ASP.Net Identity User Email property as Foreign Key

Trying to reference the Email property of an ASP.Net Identity User as a Foreign Key but keep getting an error message
using MVC6, EF7
I have an AppAccount which is the Primary model and the ApplicationUser: IdentityUser is the dependant.
I'm trying to set the Email property of the ApplicationUser as a foreign key the AppAccount model
public class AppAccount
{
public string AppAccountID { get; set; }
public string AccountType { get; set; }
public DateTime DateCreated { get; set; }
public virtual ApplicationUser AppUser { get; set; }
}
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string Surname { get; set; }
public DateTime DOB { get; set; }
public virtual AppAccount AppAccount { get; set; }
}
'Peeking' to the definition of the IdentityUser tells me the Email property is of type string...
public class IdentityUser<TKey> where TKey : IEquatable<TKey>
{
...
//
// Summary:
// Gets or sets the email address for this user.
public virtual string Email { get; set; }
...
}
I have set the PK of the AppAccount Model to string and made the Email property of the ApplicationUser an Alternate key, then set a One-To-One relationship using fluent API...
builder.Entity<ApplicationUser>(au =>
{
au.HasAlternateKey(u => u.Email);
au.HasAlternateKey(u => u.UserName);
});
builder.Entity<AppAccount>(aa =>
{
aa.HasKey(a => a.AppAccountID);
aa.HasOne(a => a.AppUser)
.WithOne(u => u.AppAccount)
.HasPrincipalKey<ApplicationUser>(u => u.Email); // PK of AppAccount is FK of AppUser
});
When I run the migration it works ok but when I try to update the database I get the following error
Error Number:1753,State:0,Class:16
Column 'AspNetUsers.Email' is not the same length or scale as
referencing column 'AppAccount.AppAccountID'
in foreign key 'FK_AppAccount_ApplicationUser_AppAccountID'.
Columns participating in a foreign key relationship must
be defined with the same length and scale.
Could not create constraint or index. See previous errors.
I have tried manually setting the maximum length of the AppAccountID and Email properties to the same limit
builder.Entity<ApplicationUser>(au =>
{
...
au.Property(u => u.Email).HasMaxLength(100);
});
builder.Entity<AppAccount>(aa =>
{
...
aa.Property(a => a.AppAccountID).HasMaxLength(100);
...
});
I have tried setting both properties to the same type on the server...
builder.Entity<ApplicationUser>(au =>
{
...
au.Property(u => u.Email).ForSqlServerHasColumnType("nvarchar(100)");
});
builder.Entity<AppAccount>(aa =>
{
...
aa.Property(a => a.AppAccountID).ForSqlServerHasColumnType("nvarchar(100)");
...
});
tried overriding the Email property in the ApplicationUser class to
public override string Email {get ; set ;}
and I tried setting the AppAccountID property of the AppAccount Model to virtual
`public virtual string AppAccountID {get ; set ;}
I think this may be a server issue but checking the database the Email column type is nvarchar, so I dont understand why it doesnt compile?
Try this Model
public class AppAccount
{
public string AppAccountID { get; set; }
public string AccountType { get; set; }
public DateTime DateCreated { get; set; }
[ForeignKey("UserId")
public virtual ApplicationUser AppUser { get; set; }
}
Apologies all, its amateur hour
Firstly I was writing HasPrincipleKey instead of HasForeignKey as pointed out by #TetsuyaYamamoto (Thankyou).
Secondly, after checking the database for the umpteenth time, the Email property for ApplicationUser is of type NVARCHAR(256), so updating the customisation as below allowed EF to compile the model successfully.
builder.Entity<AppAccount>(aa =>
{
aa.HasKey(a => a.AppAccountID);
aa.Property(a => a.AppAccountID).ForSqlServerHasColumnType("NVARCHAR(256)");
aa.HasOne(a => a.AppUser)
.WithOne(u => u.AppAccount)
.HasForeignKey<ApplicationUser>(u => u.Email);
});
Thanks all...this is why you don't code at 1 in the morning

Resources