Scenario
public class Product : Entity, IAggregateRoot
{
public string Name { get; set; }
public string Dimension { get; set; }
public decimal Volume { get; set; }
public bool Featured { get; set; }
public Farm Farm { get; set; }
public int FarmId { get; set; }
/// <summary>
/// Sell Price
/// </summary>
public decimal BidPrice { get; set; }
public int QuantityAvaliable { get; set; }
public ICollection<Image> Images { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class Category : Entity, IAggregateRoot
{
public string Title { get; set; }
public string CategoryImage { get; set; }
public Category Parent { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
Relationship setup
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Created).HasColumnType("DateTime");
Property(x => x.Modified).HasColumnType("DateTime");
Property(x => x.BidPrice).HasColumnType("Decimal");
#region RELATIONSHIP
//BelongsTo
HasRequired(x => x.Farm);
HasRequired(x => x.Category);
HasMany(x => x.Images);
#endregion
}
So I have this two model where I need to bring the data from Product model with Category information
I have checked my database, the data is consistent, the Product record have the FK for the Category record.
but when I try to get Product Data using EF6, the category information doesnt come, I get a null object.
Because of = () =>
{
_product = _repository.Find(p => p.Id == 1, p => p.Category);
};
It should_not_be_bull = () =>
_product.Category.ShouldNotBeNull();
the response from data base is for Category is null. but the record is there.
I had it working properly before. for some random magic reason it just stop working.
THE FIND method
public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate = null, params Expression<Func<TEntity, object>>[] includes)
{
var set = CreateIncludedSet(includes);
return (predicate == null) ?
set.FirstOrDefault() :
set.FirstOrDefault(predicate);
}
the CreateIncludeSet
private IDbSet<TEntity> CreateIncludedSet(IEnumerable<Expression<Func<TEntity, object>>> includes)
{
var set = CreateSet();
if (includes != null)
{
foreach (var include in includes)
{
set.Include(include);
}
}
return set;
}
the CreateSet method
private IDbSet<TEntity> CreateSet()
{
return Context.CreateSet<TEntity>();
}
MY DbContext implementation is here
https://github.com/RobsonKarls/FreedomWebApi/blob/dev/Source/Freedom.Infrastructure.DataAccess/Factories/FreedomDbContext.cs
all project is there too for further analisys
any help is valuable.
Thank you
The problem in your code is in this line in CreateIncludedSet method:
set.Include(include);
Yes, you include the data but you do not change you set. You should change it to something like:
set = set.Include(include);
Your code is a bit unclear, but try something like this....
_product = _repository.Include(p => p.Category).SingleOrDefault(x => x.Id == 1);
also see...
https://stackoverflow.com/a/7348694/6200410
Related
I have 6 classes where the first class Money, goes deep 5 levels with objects. Whatever I try I cannot get this representation, so I hope someone would be kind to offer some help. At least for first 2,3 levels then I can continue.
public class Money
{
public Money()
{
Dollars = new HashSet<MoneyDetails>();
Pesos = new HashSet<MoneyDetails>();
Pounds = new HashSet<MoneyDetails>();
}
public int Id { get; set; }
public virtual ICollection<MoneyDetails> Dollars { get; }
public virtual ICollection<MoneyDetails> Pesos { get; }
public virtual ICollection<MoneyDetails> Pounds { get; }
public string Note { get; }
}
public class MoneyDetails
{
public MoneyDetails()
{
Valuations = new HashSet<Valuations>();
}
public int Id { get; set; }
public string Description { get; set; }
public double Value { get; set; }
public virtual ICollection<Valuation> Valuations { get; set; }
}
public class Valuations
{
public Valuations ()
{
Lows = new HashSet<Deep>();
Highs = new HashSet<Deep>();
}
public int Id { get; set; }
public string Sum { get; set; }
public virtual ICollection<Deep> Lows { get; set; }
public virtual ICollection<Deep> Highs { get; set; }
}
public class Deep
{
public Deep()
{
Shallows = new HashSet<Shallow>();
}
public int Id { get; set; }
public object Data { get; set; }
public virtual ICollection<Shallow> Shallows { get; set; }
}
EDIT :
I'm using Entity Framework Core.
Following is the configuration I tried myself.
You can see below how I started, I just don't know how to go deeper into objects and make relationships between them so they are connected.
public void Configure(EntityTypeBuilder<Money> builder)
{
builder.ToTable("Money");
builder.HasKey(e => e.Id);
builder.HasMany(s => s.Dollars)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
builder.HasMany(s => s.Pesos)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
builder.HasMany(s => s.Pounds)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
}
You are using -
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
in your configuration code, but you don't have a Money navigation property or a MoneyId foreign-key property in MoneyDetails.
Since you are not using navigation and foreign-key properties in any of your entity models, I'd suggest not to configure the relations manually. Configure other properties in the Configure method if you need, but do not configure the relations. That way, EF will automatically create nullable foreign-keys in your tables and use them as Shadow Property.
EDIT - A better solution for you :
Even though the suggestion above will create all your tables with all the relations, I'm confused about how you plan to use those relations. For example, in the Money entity model Dollars, Pesos, Pounds are all collections of MoneyDetails. Therefore, all the following queries -
var money = myDbContext.Money.Include(p=> p.Dollars).FirstOrDefault(p=> p.Id == someId);
var money = myDbContext.Money.Include(p=> p.Pesos).FirstOrDefault(p=> p.Id == someId);
var money = myDbContext.Money.Include(p=> p.Pounds).FirstOrDefault(p=> p.Id == someId);
will give you the same result - the Money with the specified Id, with a list of all related MoneyDetails. So, there's no point of having three collection properties and three different relations.
Try the following approach to filter related data (you need EF Core 5.0) -
Create enum to identify the entity type -
public enum MoneyDetailsType { Dollar = 1, Peso = 2, Pound = 3 }
public enum DeepType { High = 1, Low = 2 }
Modify your entity models like -
public class Money
{
public Money()
{
MoneyDetails = new HashSet<MoneyDetails>();
}
public int Id { get; set; }
public string Note { get; }
public virtual ICollection<MoneyDetails> MoneyDetails { get; set; }
}
public class MoneyDetails
{
public MoneyDetails()
{
Valuations = new HashSet<Valuations>();
}
public int Id { get; set; }
public string Description { get; set; }
public double Value { get; set; }
public MoneyDetailsType Type { get; set; } // added - type
public int MoneyId { get; set; } // added - foreign-key
public Money Money { get; set; } // added - navigation property (optional)
public virtual ICollection<Valuations> Valuations { get; set; }
}
public class Valuations
{
public Valuations ()
{
Deeps = new HashSet<Deep>();
}
public int Id { get; set; }
public string Sum { get; set; }
public int MoneyDetailsId { get; set; } // added - foreign-key
public MoneyDetails MoneyDetails { get; set; } // added - navigation property (optional)
public virtual ICollection<Deep> Deeps { get; set; }
}
public class Deep
{
public int Id { get; set; }
public string Data { get; set; }
public DeepType Type { get; set; } // added - type
public int ValuationsId { get; set; } // added - foreign-key
public Valuations Valuations { get; set; } // added - navigation property (optional)
}
Notice, in Deep entity, currently you have Data as of type object which is not allowed. You have to change it to some primitive type. I'm using it as string. I have also omitted the Shallows property since you haven't added the Shallow model.
Your configuration methods should look like -
public void Configure(EntityTypeBuilder<Money> builder)
{
builder.ToTable("Money");
builder.HasMany(p => p.MoneyDetails)
.WithOne(p => p.Money)
.HasForeignKey(p => p.MoneyId);
}
public void Configure(EntityTypeBuilder<MoneyDetails> builder)
{
builder.ToTable("MoneyDetails");
builder.Property(p => p.Type).IsRequired(true).HasConversion(new EnumToStringConverter<MoneyDetailsType>());
builder.HasMany(p => p.Valuations)
.WithOne(p => p.MoneyDetails)
.HasForeignKey(p => p.MoneyDetailsId);
}
public void Configure(EntityTypeBuilder<Valuations> builder)
{
builder.ToTable("Valuations");
builder.HasMany(p => p.Deeps)
.WithOne(p => p.Valuations)
.HasForeignKey(p => p.ValuationsId);
}
public void Configure(EntityTypeBuilder<Deep> builder)
{
builder.ToTable("Deep");
builder.Property(p => p.Type).IsRequired(true).HasConversion(new EnumToStringConverter<DeepType>());
}
If you don't want to include the navigation properties in your entity models, then you can just keep the .WithOne() method empty in your configuration, like -
builder.HasMany(p => p.MoneyDetails)
.WithOne()
.HasForeignKey(p => p.MoneyId);
Now you can query like -
var money = myDbContext.Money
.Include(p=> p.MoneyDetails.Where(r=> r.Type == MoneyDetailsType.Dollar))
.FirstOrDefault(p=> p.Id == someId);
and it will give you the Money with a list of only Dollar type MoneyDetails.
I Run into this scenario. I have related entities: Case and a Confinement
A Case has an optional navigation to confinement - ConfinementId, while the confinement has a required CaseId.
My problem is when I try to update the Case, by attaching the Confinement Record, the confinementID on the case entity is not updated.
Here are my mapping and Entity Classes:
public class Confinement : Entity
{
public int Id { get; set; }
public string ReferenceId { get; set; }
public int CaseId { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsActive { get; set; }
public int BranchId { get; set; }
public string Status { get; set; }
public virtual Case Case {get;set;}
}
public class Case : Entity
{
public int CaseId { get; set; }
public string CaseReferenceId { get; set; }
public int? ConsultationId { get; set; }
public int? ConfinementId { get; set; }
public virtual Confinement Confinement { get; set; }
****Omitted
}
Configuration
public void Configure(EntityTypeBuilder<Case> builder)
{
builder.HasKey(c => c.CaseId);
builder.HasMany(t => t.Details)
.WithOne(t => t.Case)
.HasForeignKey(t => t.CaseId);
builder.HasOne(t => t.Confinement)
.WithOne(a=>a.Case)
.HasForeignKey<Confinement>(t => t.CaseId);
}
public void Configure(EntityTypeBuilder<Confinement> builder)
{
builder.HasKey(c => c.Id);
builder.HasOne(a => a.Case)
.WithOne(a => a.Confinement)
.HasForeignKey<Confinement>(a => a.CaseId);
}
Controller Code
public async Task<IActionResult> AddConfinement(int caseId)
{
if (caseId == 0)
return BadRequest();
if (_service.ExistingCase(caseId))
return BadRequest("Case has already a confinement record!");
var #case = await _caseService.FindAsync(caseId);
if (#case == null)
return NotFound("Case not found!");
var confinement = _converter.ConvertFromCase(#case);
confinement.ObjectState = ObjectState.Added;
#case.Confinement = confinement;
#case.ObjectState = ObjectState.Modified;
#case.ConfinementId = confinement.Id;
_caseService.Update(#case);
await _unitOfWork.SaveChangesAsync();
return Ok();
}
When the AddConfinement Method is called, the confinement is added on the database, but the Case.ConfinementId is not updated. Is there anything wrong to this implementation.
As a workaround, I created a trigger on the database but i want to accomplish this on the application level.
WEB API
Model::
public class Empresa
{
[Key]
public string CDEmpresa { get; set; }
public string NomeFantasia { get; set; }
[IgnoreDataMember]
public string Nome{ get; set; }
public List<EmpresaRamoAtividade> EmpresaRamoAtividade { get; set; }
}
public class EmpresaRamoAtividade
{
[Key]
public int CTRamoAtividade { get; set; }
[IgnoreDataMember]
public string CDEmpresa { get; set; }
public List<RamoAtividade> RamoAtividade { get; set; }
}
public class RamoAtividade
{
[IgnoreDataMember]
[Key]
public int CTRamoAtividadeTraducao { get; set; }
public int CTRamoAtividade { get; set; }
public string Atividade { get; set; }
public int Idioma { get; set; }
}
Controller::
Working ok:::
{
return db.Empresas
.Where(a => a.Associado.IsAssociado)
.Include(empresaRamo => empresaRamo.EmpresaRamoAtividade)
.Include(ramo => ramo.EmpresaRamoAtividade.Select(atividade => atividade.RamoAtividade));
}
Not working, I have to filter by "idioma" (language):::
{
return db.Empresas
.Where(a => a.Associado.IsAssociado)
.Include(empresaRamo => empresaRamo.EmpresaRamoAtividade)
.Include(ramo => ramo.EmpresaRamoAtividade.Select(atividade => atividade.RamoAtividade.Where(idioma => idioma.Idioma == 1)));
}
Error:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path
Can't I filter 3 level collection child?
Thank you..
You cannot filter using Include methods. It only supports select.
Disclaimer: I'm the owner of the project Entity Framework Plus (EF+)
EF+ Query IncludeFilter allow you to easily filter related entities:
{
return db.Empresas
.Where(a => a.Associado.IsAssociado)
.IncludeFilter(empresaRamo => empresaRamo.EmpresaRamoAtividade)
.IncludeFilter(ramo => ramo.EmpresaRamoAtividade.Select(atividade => atividade.RamoAtividade.Where(idioma => idioma.Idioma == 1)));
}
You can find the documentation here
I have these classes in my project (the names in the code are in Portuguese, if necessary I can translate) :
public class EntityWithGuid : IEntityWithId<string>
{
protected EntityWithGuid()
{
this.Id = Guid.NewGuid().ToString("N").ToLower();
}
[Key]
public string Id { get; set; }
}
public class Servico : EntityWithGuid
{
public DateTime? Data { get; set; }
public string Descricao { get; set; }
public string Cliente_Id { get; set; }
[ForeignKey("Cliente_Id")]
public Cliente Cliente { get; set; }
[Required]
public ICollection<ServicoImagem> Imagens { get; set; }
[Required]
public ICollection<Tag> Tags { get; set; }
}
public class ServicoImagem : EntityWithGuid
{
[Required]
public string Nome { get; set; }
public string Servico_Id { get; set; }
[Required]
public Servico Servico { get; set; }
}
public class Tag : EntityWithGuid
{
[Required]
public string Nome { get; set; }
public string Fonetica { get; set; }
public ICollection<Servico> Servicos { get; set; }
}
And this is the Context configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ServicoConfiguration());
}
internal class ServicoConfiguration : EntityTypeConfiguration<Servico>
{
internal ServicoConfiguration()
{
this.HasMany(s => s.Tags)
.WithMany(t => t.Servicos)
.Map(mf =>
{
mf.MapLeftKey("Servico_Id");
mf.MapRightKey("Tag_Id");
mf.ToTable("ServicoTag");
});
this.HasMany(s => s.Imagens)
.WithRequired(i => i.Servico)
.HasForeignKey(f => f.Servico_Id);
}
}
After load a Servico entity the update method can do any operation with the Servico.Tags property (add and remove items), mark as modified and finally call Context.SaveChanges(). Everything works perfectly.
var servico = Context.Set<Servico>()
.Include(x => x.Cliente)
.Include(x => x.Tags)
.Include(x => x.Imagens)
.FirstOrDefault(x => x.Id == id);
...
// Remove tags
servico.Tags = servico.Tags.Except(oldTags).ToList();
// Add new tags
servico.Tags = servico.Tags.Concat(newTags).ToList();
...
Context.Entry(servico).State = EntityState.Modified;
Context.SaveChanges();
If I do the same thing with the Images property is only possible to make one type of operation at a time, add OR remove. If added and removed at the same time, the added item does not receive the value of the foreign key and error occurs in Context.SaveChanges() but if I do only one type of operation, it works perfectly.
The only solution I found was to make a loop to mark the item as deleted.
// Mark image as deleted
foreach (var imagem in imagensParaDeletar)
{
Context.Entry(imagem).State = System.Data.Entity.EntityState.Deleted;
}
I would like to understand why the problem ONLY occurs in this type of relationship and ONLY when I need to do both type of operation on the same property.
I have Many To Many relationship defined and when I try to query for the records that should be in the map I get null.
public class Record
{
public int RecordId { get; set; }
public DateTime DateRecordCreated { get; set; }
public ICollection<Street> Streets { get; set; }
public ICollection<Street> CrossStreets { get; set; }
}
public class RecordMap : EntityTypeConfiguration<Record>
{
public RecordMap()
{
// Primary Key
this.HasKey(t => t.RecordId);
this.HasMany(r => r.Streets)
.WithMany(c => c.Records)
.Map(sl =>
{
sl.ToTable("StreetRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("StreetId");
});
this.HasMany(r => r.CrossStreets)
.WithMany(c => c.AnotherRecord)
.Map(sl =>
{
sl.ToTable("AnotherStreetRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("StreetId");
});
this.Property(t => t.DateRecordCreated).IsRequired();
}
}
public class House : Record
{
public string HouseNumber { get; set; }
public string StreeName { get; set; }
public int ZipCode { get; set; }
}
public class Street
{
public int StreetId { get; set; }
public string StreetName { get; set; }
public ICollection<Record> Records { get; set; }
public ICollection<Record> AnotherRecord { get; set; }
}
Now when I run the following query below I get houses.CrossStreets as null, I tried adding enabling lazy loading and had the same out come.
public static void GetRecords()
{
using (var context = new SandboxContext())
{
var entities = context.Houses.Include(r => r.CrossStreets);
var houses = entities.ToList();
}
}