mapping multiple tables to a single entity class in entity framework - c#

I am working on a legacy database that has 2 tables that have a 1:1 relationship.
Currently, I have one type (1Test:1Result) for each of these tables defined
I would like to merge these particular tables into a single class.
The current types look like this
public class Result
{
public string Id { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public string Units { get; set; }
public bool OutOfRange { get; set; }
public string Status { get; set; }
public string Minimum { get; set; }
public string Maximum { get; set; }
public virtual Instrument InstrumentUsed { get; set; }
public virtual Test ForTest { get; set; }
}
public class Test
{
public int Id { get; set; }
public string Status { get; set; }
public string Analysis { get; set; }
public string ComponentList { get; set; }
public virtual Sample ForSample { get; set; }
public virtual Result TestResult { get; set; }
}
I would prefer them to look like this
public class TestResult
{
public int Id { get; set; }
public string Status { get; set; }
public string Analysis { get; set; }
public string ComponentList { get; set; }
public string TestName { get; set; }
public string Text { get; set; }
public string Units { get; set; }
public bool OutOfRange { get; set; }
public string Status { get; set; }
public string Minimum { get; set; }
public string Maximum { get; set; }
public virtual Instrument InstrumentUsed { get; set; }
}
I am currently using the fluent API for mapping these to our legacy Oracle database.
What would be the best method of combining these into a single class?
Please note that this is a legacy database. Changing the tables is not an option and creating views is not a viable solution at this point in the project.

You can use Entity Splitting to achieve this if you have the same primary key in both tables.
modelBuilder.Entity<TestResult>()
.Map(m =>
{
m.Properties(t => new { t.Name, t.Text, t.Units /*other props*/ });
m.ToTable("Result");
})
.Map(m =>
{
m.Properties(t => new { t.Status, t.Analysis /*other props*/});
m.ToTable("Test");
});
Here's a useful article

Related

How Map Multiple related Entities to one DTO Object using AutoMapper EF Core

I have three related Entities in my blazor application Opportunity, AppUser and AssignedOpportunity, What I want to achieve is to map Opportunity and AppUser to a DTO Object ReturnAssignedOpportunityDTO which has similar fields as the entities, using AutoMapper, but am not sure how to do that, below are the entities
public partial class AssignedOpportunity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("OpportunityID")]
public string OpportunityID { get; set; }
public string Status { get; set; }
public Opportunity opportunity { get; set; }
[ForeignKey("UserID")]
public string UserID { get; set; }
public AppUser User { get; set; }
}
The opportunity
public partial class Opportunity
{
public Opportunity()
{
AssignedOpportunities= new HashSet<AssignedOpportunity>();
}
[Key]
public string ID { get; set; }
public string OpportunityName { get; set; }
public string Description { get; set; }
public string CreatedBy { get; set; }
public DateTime DateCreated { get; set; }
public double EstimatedValue { get; set; }
public string EmployeeNeed { get; set; }
public double RealValue { get; set; }
public string Location { get; set; }
public string ReasonStatus { get; set; }
public string Status { get; set; }
public virtual ICollection<AssignedOpportunity> AssignedOpportunities { get; set; }
}
AppUser Class
public partial class AppUser : IdentityUser
{
public AppUser()
{
AssignedOpportunities = new HashSet<AssignedOpportunity>();
}
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string LGA { get; set; }
public string State { get; set; }
public virtual ICollection<AssignedOpportunity> AssignedOpportunities { get; set; }
}
Here's the DTO Object I want to map to.
public class ReturnOpportunitiesDTO
{
public int ID { get; set; }
public string OpportunityID { get; set; }
public string OpportunityName { get; set; }
public double EstimatedValue { get; set; }
public string EmployeeNeed { get; set; }
public double RealValue { get; set; }
public string Location { get; set; }
public string UserID { get; set; }
public string UserFullName { get; set; }
public string Status { get; set; }
}
Here is my query to fetch the records
var result = await _context.AssignedOpportunities.Include(o => o.opportunity).
ThenInclude(a => a.User).
Where(a=>a.UserID==UserID.ToString()).ToListAsync();
return result;
This is how i usually setup Map Profile
public AssignArtisanProfile()
{
CreateMap<AssignedOpportunity, ReturnOpportunities>();
}
But since I want to map multiple entities, how do I include the other entity
Your scenario is just another example of flattening a complex object. You have properties in child objects, which you want to bring to the ground level, while still leveraging AutoMapper mapping capabilities. If only you could reuse other maps from app user and opportunity when mapping from assigned opportunity to the DTO... Well, there is a method called IncludeMembers() (see the docs) that exists precisely for such case. It allows you to reuse the configuration in the existing maps for the child types:
config.CreateMap<AssignedOpportunity, ReturnOpportunitiesDTO>()
.IncludeMembers(source => source.opportunity, source => source.User);
config.CreateMap<Opportunity, ReturnOpportunitiesDTO>();
config.CreateMap<AppUser, ReturnOpportunitiesDTO>()
.ForMember(
dest => dest.UserFullName,
options => options.MapFrom(source =>
string.Join(
" ",
source.FirstName,
source.MiddleName,
source.LastName)));
Usage:
var mappedDtos = mapper.Map<List<ReturnOpportunitiesDTO>>(assignedOpportuniesFromDatabase);

The expected type was 'System.Nullable`1[System.Guid]' but the actual value was null

An exception occurred while reading a database value for property 'EMWH.UniqueAttchID'. The expected type was 'System.Nullable`1[System.Guid]' but the actual value was null.
I'm using EFCore 5.0 and I get the error listed above. If in my EMWH view I hide all records where there is a NULL in UniqueAttchID it works fine. But I can't seem to find a way to exclude the records where the principal key (for the relationship) is NULL. But still have the ability to view all records.
Code causing the error
var workOrder = await _context.EMWHs.AsNoTracking()
.Include(x => x.EMWIs).ThenInclude(x => x.HQATs)
.Where(x => x.KeyID == WorkOrderKeyId).SingleOrDefault();
EMWH
public class EMWH
{
public byte EMCo { get; set; }
public string WorkOrder { get; set; }
public string Equipment { get; set; }
public string? Description { get; set; }
public Guid? UniqueAttchID { get; set; }
[Column("udServiceRecordYN")]
public string? ServiceRecordYN { get; set; }
public char Complete { get; set; }
public long KeyID { get; set; }
[Column("DateSched")]
[Display(Name = "Scheduled Date")]
public DateTime ScheduledDate { get; set; }
public virtual EMEM EMEM { get; set; }
public virtual IEnumerable<EMWI> EMWIs { get; set; }
public virtual IEnumerable<HQAT> HQATs { get; set; }
}
HQAT
public class HQAT
{
public byte HQCo { get; set; }
public string FormName { get; set; }
public string KeyField { get; set; }
public string Description { get; set; }
public string AddedBy { get; set; }
public DateTime? AddDate { get; set; }
public string DocName { get; set; }
public int AttachmentID { get; set; }
public string TableName { get; set; }
public Guid? UniqueAttchID { get; set; }
public string OrigFileName { get; set; }
public string DocAttchYN { get; set; }
public string CurrentState { get; set; }
public int? AttachmentTypeID { get; set; }
public string IsEmail { get; set; }
public long KeyID { get; set; }
public virtual udEMCD EMCD { get; set; }
public virtual HQAF HQAF { get; set; }
public virtual EMWH EMWH { get; set; }
public virtual EMWI EMWI { get; set; }
public virtual udEMED EMED { get; set; }
}
DBContext
modelBuilder.Entity<EMWH>().ToTable("EMWH").HasKey(k=>new { k.EMCo, k.WorkOrder });
modelBuilder.Entity<HQAT>().HasOne(x => x.EMWH).WithMany(x => x.HQATs).HasForeignKey(x => x.UniqueAttchID)
.HasPrincipalKey(x => x.UniqueAttchID);
You have the relationship set up to count on public Guid? UniqueAttchID { get; set; } for keys between entities, but you have this set up as a nullable GUID type, so when the query runs the DB is likely coming across a null value in the table, and can't resolve the relationship. The simple solution and arguably best practice is to make those properties non-nullable, or define the relationship using int or long types as ID's, so they can't be null, and making sure your INSERT and UPDATE queries are properly setting the relationships. Either way, the null is where you should start and if you have records in the tables already that have null values you are expecting to use as keys, you could have some work on your hands to figure out how those are supposed to be linked and getting the nulls replaced with GUID values.

How to map a mongodb entity to elasticsearch create index in c#?

I am trying to create an index for my project to elasticsearch.
following are my classes
public class Asset
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public AssetComponent Component { get; set; }
public AssetSite Site { get; set; }
public AssetComposition AssetComposition { get; set; }
public string SerialNumber { get; set; }
public string WorkOrderNumber { get; set; }
public AssetFigure Figure {get;set;}
public string SkuNumber { get; set; }
public Status AssetStatus { get; set; }
public Status InspectionStatus { get; set; }
public BsonDocument UserDefinedAttributes { get; set; }
public TimeAndUserTrail Trail { get; set; }
[BsonIgnoreIfDefault]
public List<Identifier> Identifiers { get; set; } = new List<Identifier>();
[BsonIgnoreIfDefault]
public List<Document> Documents { get; set; } = new List<Document>();
public Status OrderStatus { get; set; }
}
public class AssetComponent
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
}
public class AssetSite
{
[BsonRepresentation(BsonType.ObjectId)]
public string LocationId { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
public string SiteId { get; set; }
}
public class AssetComposition
{
public List<AssetComponentComposition> SubAssemblies { get; set; }
public List<AssetComponentComposition> Parts { get; set; }
public List<AssetComponentComposition> Accessories { get; set; }
}
public class AssetComponentComposition
{
public string Name { get; set; }
public string AssetType { get; set; }
public List<ObjectId> AssetIds { get; set; }
}
public class AssetFigure
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
}
public class Status
{
public string Id { get; set; }
public string Value { get; set; }
}
And in elasticsearch create index method
var createIndexResponse = client.CreateIndex(indexName, c => c
.Mappings(ms => ms
.Map<Asset>(m => m
.AutoMap<AssetComposition>()
.AutoMap<AssetComponent>()
.AutoMap<AssetSite>()
.AutoMap<AssetFigure>()
.AutoMap<BsonDocument>()
.AutoMap<TimeAndUserTrail>()
.AutoMap<Core.Entities.Status>()
.AutoMap(typeof(AssetComponentComposition))
.AutoMap(typeof(Identifier))
.AutoMap(typeof(Document))
.AutoMap(typeof(ObjectId))
)
)
);
When I run this, I am getting the following exception.
System.Reflection.AmbiguousMatchException: 'Ambiguous match found.'
I tried to resolve this using the following link from elasticsearch official document
https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/auto-map.html
But again I didn't resolved the issue. Please help.
The problem is occurring due to the 'BsonDocument' which has inbuilt properties which need to be map to the elastic search mapping environment. After adding those additional fields the issue resolved

How to create a third table for Many to Many relationship in Entity Framework Core?

I have two classes:
One is User
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<Subscription> Subscriptions { get; set; }
}
Other is Subscription:
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
As you can see that User has a list of Subscriptions.
Now when using the entity framework code first approach I am getting a table for User which doesn't contain Subscriptions but a new column for User Id is being added to Subscription table. I was expecting to have a third table which contains two columns one with User ID and the other with subscription ID.
How can I achieve this?
From documentation:
Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
So this answer is correct.
I just corrected code a little bit:
class MyContext : DbContext
{
public DbSet<Use> Users { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserSubscription>()
.HasKey(t => new { t.UserId, t.SubscriptionId });
modelBuilder.Entity<UserSubscription>()
.HasOne(pt => pt.User)
.WithMany(p => p.UserSubscription)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserSubscription>()
.HasOne(pt => pt.Subscription)
.WithMany(t => t.UserSubscription)
.HasForeignKey(pt => pt.SubscriptionId);
}
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<UserSubscription> UserSubscriptions{ get; set; }
}
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<UserSubscription> UserSubscriptions{ get; set; }
}
public class UserSubscription
{
public int UserId { get; set; }
public User User { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
}
PS. You don't need use virtual in navigation property, because lazy loading still not available in EF Core.
Create a third middle table named: UserSubscriptions for example.
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public virtual ICollection<UserSubscription> Subscriptions { get; set; }
}
public class Subscription
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class UserSubscription
{
public int ID { get; set; }
public int SubscriptionID { get; set; }
public decimal Price { get; set; }
public int UserID { get; set; }
public virtual User { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
}
Second Solution:
Add reference for Subscription to User and name it CurrentSubscription for example.
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int CurrentSubscriptionID { get; set; }
public virtual Subscription Subscription { get; set; }
}
public class Subscription
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}

Automapper many to many mapping

Patrick, thanks for advice about correct question!
EDIT 1:
I have three table for many to many relationship. Like this:
GoodEntity:
public partial class GoodEntity
{
public GoodEntity()
{
this.GoodsAndProviders = new HashSet<GoodAndProviderEntity>();
}
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public decimal cost { get; set; }
public Nullable<decimal> price { get; set; }
public virtual ICollection<GoodAndProviderEntity> GoodsAndProviders { get; set; }
}
ProviderEntity:
public partial class ProviderEntity
{
public ProviderEntity()
{
this.GoodsAndProviders = new HashSet<GoodAndProviderEntity>();
}
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public string address { get; set; }
public string phone { get; set; }
public string email { get; set; }
public string url { get; set; }
public Nullable<int> rating { get; set; }
public virtual ICollection<GoodAndProviderEntity> GoodsAndProviders { get; set; }
}
Entity for many-to-many relationship:
public partial class GoodAndProviderEntity
{
public int id { get; set; }
public int good_id { get; set; }
public int provider_id { get; set; }
public virtual GoodEntity Goods { get; set; }
public virtual ProviderEntity Providers { get; set; }
}
GoodDTO:
public class GoodDTO
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public decimal cost { get; set; }
public decimal? price { get; set; }
public IList<ProviderDTO> providers { get; set; }
}
ProviderDTO:
public class ProviderDTO
{
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public string address { get; set; }
public string phone { get; set; }
public string email { get; set; }
public string url { get; set; }
public int? rating { get; set; }
}
This is code for creation maps:
Mapper.CreateMap<ProviderDTO, ProviderEntity>();
Mapper.CreateMap<ProviderEntity, ProviderDTO>();
Mapper.CreateMap<GoodEntity, GoodDTO>()
.ForMember(dto => dto.providers, opt => opt.MapFrom(x => x.GoodsAndProviders));
Mapper.CreateMap<GoodAndProviderEntity, ProviderDTO>();
And it works half. Automapper was mapped "goods" completely and was created list for all providers for this goods. But automapper don`t fill providers.
If I use Mapper.AssertConfigurationIsValid(), then:
Unmapped members were found. Review the types and members below. Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type ======================================================= ProviderDTO -> ProviderEntity (Destination member list) Core.DTO.ProviderDTO -> DAL.EF.Entities.ProviderEntity (Destination member list) Unmapped properties: GoodsAndProviders ============================================================== GoodAndProviderEntity -> ProviderDTO (Destination member list) DAL.EF.Entities.GoodAndProviderEntity -> Core.DTO.ProviderDTO (Destination member list)
How to create mapping for many-to-many relationship?
Regards, Anton
With your current code you're trying to map the GoodAndProviderEntity into ProviderDTO.
Mapper.CreateMap<GoodEntity, GoodDTO>()
.ForMember(dto => dto.providers, opt => opt.MapFrom(x => x.GoodsAndProviders));
What you want to do, is to map ProviderEntity into ProviderDTO, so all you have to do is select the Providers from GoodsAndProviders as a list:
Mapper.CreateMap<GoodEntity, GoodDTO>()
.ForMember(dto => dto.providers, opt => opt.MapFrom(x => x.GoodsAndProviders.Select(y => y.Providers).ToList()));

Categories

Resources