How to deal with relational entity mapping in AutoMapper? - entity-framework

Our Entity model has navigation properties:
public class Course
{
public Guid Id { get; set; }
...
public Guid InstructorId { get; set; }
public virtual Instructor Instructor { get; set; }
public virtual ICollection<Instructor> Coinstructors { get; set; }
}
That is, a course have one instructor and multiple coinstructors.
My view model has the id's of those instructors.
public class CourseCreateModel
{
...
public InstructorModel Instructor { get; set; }
public IEnumerable<InstructorModel> Coinstructors { get; set; }
}
The InstructorModel contains the Id:
public class InstructorModel
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
To make the data transfer from our DTO CourseCreateModel into the domain model Course, we can map the instructor easily because there is a InstructorId in the domain model:
Mapper.CreateMap<CourseCreateModel, Course>()
.ForMember(dest => dest.InstructorId, opts => opts.MapFrom(src => src.Instructor.Id))
...;
But how to map Coinstructors? We can get an array of coinstructor's id. But how to do the mapping?

I believe you have two options:
Option nr. 1 :
.ForMember(c=>c.Coinstructors,op=>op.MapFrom(v=>v.Coinstructorids.Select(c=>new Instructor(){Id=c})))
where Coinstructorids = List<int>();
Option nr. 2:
Create an custom resolver:
public class CustomConvert : ValueResolver<IList<int>, IList<Instructor>>
{
protected override string ResolveCore(IList<int> source)
{
string result = new List<Instructor>();
foreach (var item in source)
{
result.Add(new Instructor() {Id = item});
}
return result;
}
}
and the use it :
.ForMember(c => c.Coinstructors, op => op.ResolveUsing<CustomConvert>().FromMember(c => c.Coinstructorids));
where Coinstructorids = List<int>();

Related

EF Core self referential many to many both sides

I have an entity that has a many to many onto itself
public class Docket
{
public long Id { get; set; }
public string Name { get; set; }
public virtual List<DocketDocket> RelateDockets{ get; set; }
}
public class DocketDocket
{
public int LeftDocketId { get; set; }
public Docket.Docket LeftDocket { get; set; }
public int RightDocketId { get; set; }
public Docket.Docket RightDocket { get; set; }
}
With the following config
modelBuilder.Entity<Joins.DocketDocket>().HasKey(t => new { t.LeftDocketId, t.RightDocketId });
modelBuilder.Entity<Joins.DocketDocket>().HasOne(pt => pt.LeftDocket).WithMany(t => t.RelatedDockets).HasForeignKey(pt => pt.LeftDocketId);
modelBuilder.Entity<Joins.DocketDocket>().HasOne(pt => pt.RightDocket).WithMany().HasForeignKey(pt => pt.RightDocketId).OnDelete(DeleteBehavior.Restrict);
I then manually create the link in my repo as such
await base.Insert(new Joins.DocketDocket() { LeftDocketId = item.Id, RightDocketId = i.RightDocketId });
This works fine but I need this relationship to be double sided so I add the record for the other side
await base.Insert(new Joins.DocketDocket() { LeftDocketId = i.RightDocketId, RightDocketId = item.Id });
and on this second insert I get
Violation of PRIMARY KEY constraint 'PK_RelatedDockets'. Cannot insert duplicate key in object 'dbo.RelatedDockets'. The duplicate key value is (10791, 10790).
Shouldn't EF have my key as (10790, 10791) for the first entry and then (10791,10790) for the second one and therefore NOT duplicate? If not how can I define a unique key for this type of arrengement?
You need to change your config and need to modify Docker entity.
Config:
modelBuilder.Entity<DocketDocket>().HasKey(t => new { t.LeftDocketId, t.RightDocketId });
modelBuilder.Entity<DocketDocket>().HasOne(m => m.LeftDocket).WithMany(t => t.LeftDockets).HasForeignKey(m => m.LeftDocketId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DocketDocket>().HasOne(m => m.RightDocket).WithMany(t => t.RightDockets).HasForeignKey(m => m.RightDocketId).OnDelete(DeleteBehavior.Restrict);
Entity:
public class Docket {
public int Id { get; set; }
public string Name { get; set; }
public virtual List<DocketDocket> LeftDockets { get; set; } = new List<DocketDocket>();
public virtual List<DocketDocket> RightDockets { get; set; } = new List<DocketDocket>();
public virtual List<DocketDocket> AllDockets => LeftDockets.Union(RightDockets).ToList();
}
Example: https://dotnetfiddle.net/m6tgDk

How to filter child collections Entity Framework

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

EF6 Ignoring related data

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

Entity Framework 6: one-to-many doesn't update foreign key when inserting and removing in the same operation

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.

How to join two model and display them in view in mvc 3.0 EF 5

I have two tables which have primary and foriegn key concept. I want to get the combined data on behalf of those keys. i don't know how to bind both the table into single model and display it into view.
Model
public class TVSerialModel
{
public Int32 Serial_ID { get; set; } // primary key
public string Serial_Name { get; set; }
public int? Release_Year { get; set; }
}
public class TVSerialEpisodeModel
{
public Int64 Video_ID { get; set; }
public Int32 Serial_ID { get; set; }// foriegn key
public string Episode_Name { get; set; }
public string Description { get; set; }
public DateTime Uploaded_Time { get; set; }
}
public class TVSerial_Episode_VM
{
public IEnumerable<TVSerialEpisodeModel> tvserialEpisode { get; set; }
public IEnumerable<TVSerialModel> Tvserial { get; set; }
}
Controller
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.
Join(tvContext.dbTvSerials, p => p.Serial_ID, r => r.Serial_ID,(p, r) => new { p, r }).
Select(o => new TVSerial_Episode_VM
{ ****what should i write here to get all columns from both table**** }).
Take(9).ToList();
return View(tves);
}
Expected Result
If TVSerialEpisode has a property TVSerial, you can just dot through your foreign keys.
cDBContext.dbTvSerialEpisode
.Select(t =>
new {
t.TVSerial.Serial_ID,
t.TVSerial.Serial_Name,
t.Episode_Name
})
.Take(9)
.ToList();
You need to improve little bit the models you used with EF. You must include the reference object in model.
Like this
public virtual TVSerialModel TVSerialModel { get; set; }
in main table. This way you can select referred table too.
EF Include
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.Include("TVSerialEpisodeModel")
.Include("TVSerialModel").ToList();
return View(tves);
}