Automapper ProjectTo DefaultIfEmpty Configuration? - c#

AutoMapper v 8.1.1 / EF6
In this example, where Owner.CarId = null, OwnerDto.Car is also null. So if the default behavior is null on a nested property, is there a way to handle the equivalent of .DefaultIfEmpty(new CarDto()) in ProjectTo?
In using a ProjectTo map to convert an optional one-to-one database relationship, if the Owner.CarId is NULL, the CarDto property is null. I thought the default behavior (based on how null collections are handled as I didn't see any mention of how nulls would be evaluated in Nested Mappings) was to return a new CarDto object...and then from there, wasn't sure if the properties of that new CarDto object would be their natural default values (i.e. int = 0) or they'd be null.
Source Classes
public class Car
{
public int Id { get; set; }
public string Model { get; set; }
}
public class Owner
{
public int Id { get; set; }
public Car Car { get; set; }
public int? CarId { get; set; }
public string Name { get; set; }
}
Destination Classes
public class CarDto
{
public int Id { get; set; }
public string Model { get; set; }
}
public class OwnerDto
{
public int Id { get; set; }
public CarDto Car { get; set; }
public int? CarId { get; set; }
public string Name { get; set; }
}
Mapping Configuration
new MapperConfiguration(settings =>
{
settings.CreateMap<Owner, OwnerDto>()
settings.CreateMap<Car, CarDto>();
});

Related

Automapper generic source to known target resolver issue

It's been a while since i've used automapper, but i'm almost sure that my situation should be possible.
Setup
I created the following mapping configuration:
var map = cfg.CreateMap<TSource, Structure>();
So in my situation the source is a generic type (unknown) and the target type is Structure (known).
A possible option for the TSource type could be:
public class DataChannel
{
public string Id { get; set; }
public string Description { get; set; }
public string Ean { get; set; }
public DateTimeOffset ValidFrom { get; set; }
public bool IsManual { get; set; }
public string Type { get; set; }
public string Unit { get; set; }
public string Address { get; set; }
public string BuildingId { get; set; }
}
The target Structure object looks like this:
public class Structure : IStructure
{
public Structure()
{
Children = new List<Structure>();
Properties = new List<StructureProperty>();
}
public int Id { get; set; }
public ICollection<StructureProperty> Properties { get; set; }
public List<Structure> Children { get; set; }
}
Situation
For example, I would like the string properties "Unit" and "Type" to be added as a StructureProperty object to the Properties collection of the Structure entity.
map.ForMember(c => c.Properties, m => m.MapFrom<StructurePropertyResolver<TSource>>());
How can this be done?

Entity Framework, table with 2 foreign keys to 2 different tables

I have 3 models with fields in it like the following:
public class RootObject
{
[Key]
public int RootObjectId { get; set; }
[ForeignKey("RootObjectId")]
public virtual AObject AObject { get; set; }
[ForeignKey("RootObjectId")]
public virtual BObject BObject { get; set; }
public string Name { get; set; }
}
public class AObject
{
[Key]
public int AObjectId { get; set; }
//Other fields
}
public class BObject
{
[Key]
public int BObjectId { get; set; }
//Other fields
}
I want it so that if I were to visually inspect the RootObject table I would see see a list of RootObjectId's and Name's. For ease, lets assume even numbered RootObjectId's are mapped to AObjectId's and odds are mapped to BObjectId's. If I were to visually inspect AObject, I would expect to see the ID's 2, 4, 6, ... that are FK's for RootObject. If were to visually inspect BObject, I would expect to see the ID's 1, 3, 5, ... that are FK's for RootObject.
Currently, when I try this approach I get the following error:
"An error occurred while updating the entries...Referential integrity constraint violations. A Dependent Role has multiple principals with different values."
I tried to remove the FK attributes in RootObject but that created 2 additional columns in RootObject that were populated with ID numbers. I don't want this since every RootObject has either one AObject or one BObject. It can't have both.
To me, you are looking for something for which the TPT (Table per Type) approach in Entity Framework could be a solution. Applied to your case (there are many approaches, but this I tested and it works):
public class RootObject
{
[Key]
public int RootObjectId { get; set; }
public string Name { get; set; }
}
[Table("AObjects")]
public class AObject : RootObject
{
//Other fields
public string AField { get; set; }
}
[Table("BObjects")]
public class BObject : RootObject
{
//Other fields
public string BField { get; set; }
}
For the DbContext class:
public DbSet<RootObject> RootObjects { get; set; }
public DbSet<AObject> AObjects { get; set; }
public DbSet<BObject> BObjects { get; set; }
Seed example:
AObject a1 = new AObject() { Name = "ImA", AField = "adata" };
BObject b1 = new BObject() { Name = "ImB", BField = "bdata" };
context.AObjects.Add(a1);
context.BObjects.Add(b1);
context.SaveChanges();
I don't think it is possible to use ONE column to be a foreign key to two different tables. You should rather think of two (optional) FK like:
public class RootObject
{
[Key]
public int RootObjectId { get; set; }
public int? EvensAObjectId { get; set; }
public int? OddsBObjectId { get; set; }
[ForeignKey("EvensAObjectId")]
public virtual AObject AObject { get; set; }
[ForeignKey("OddsBObjectId")]
public virtual BObject BObject { get; set; }
public string Name { get; set; }
}
To set up 1-0..1 relationships you need to define the relationships explicitly during model configuration.
public class Model1 : DbContext
{
public Model1()
: base("name=Model1")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AObject>()
.HasRequired(e => e.RootObject).WithOptional(r => r.AObject);
modelBuilder.Entity<BObject>()
.HasRequired(e => e.RootObject).WithOptional(r => r.BObject);
base.OnModelCreating(modelBuilder);
}
public virtual DbSet<RootObject> RootObjects { get; set; }
public virtual DbSet<AObject> AObjects { get; set; }
public virtual DbSet<BObject> BObjects { get; set; }
}
public class RootObject
{
[Key]
public int RootObjectId { get; set; }
public virtual AObject AObject { get; set; }
public virtual BObject BObject { get; set; }
public string Name { get; set; }
}
public class AObject
{
[Key]
public int AObjectId { get; set; }
public virtual RootObject RootObject { get; set; }
}
public class BObject
{
[Key]
public int BObjectId { get; set; }
public virtual RootObject RootObject { get; set; }
}
You'll want to be really careful when setting RootObject.AObject and RootObject.BObject, as if there is already a related row you will get an error when you save. Also, I don't think there's any way to get EF enforce the constraint that each RootObject must have either an AObject or a BObject, but not both - you'd need to enforce that in your code.

Entity Framework 5: Error saving new child entity that has multiple parent relationships

For a project at work I have created entities similar to the following:
public class ObjectA
{
public Guid ObjectAGUID { get; set; } // key
public string Name { get; set; }
public int Number { get; set; }
}
public class ObjectB
{
public Guid ObjectBGUID { get; set; } //key
public string Name { get; set; }
public int Number { get; set; }
}
public class Note
{
public Guid NoteGUID { get; set; } // key
public Guid AssociationGUID { get; set; }
public string Text { get; set; }
public string NoteType { get; set; }
}
Both ObjectA and ObjectB have a 1-Many relationship with Note where Note.AssociatedGUID is the dependent property to A and B's respective entity key. In other words, A and B can have any number Note entities associate with either one.
My problem:
When I create a new ObjectA and a new Note where Note.AssociatedGUID = ObjectA.ObjectAGUID , I get the following error on DBContext.SaveChanges():
Entities in 'Entities.Notes' participate in the 'ObjectBNote' relationship. 0 related 'ObjectB' were found. 1 'ObjectB' is expected.
Musings
If I want to have Notes for both ObjectA and ObjectB do I need to create separate Note entities,say NoteA and NoteB, to do this? If so this seems redundant.
Or, do I need to have a separate nullable foreign key on Note for each parent entity? While more manageable this seems a little overkill as well.
public class Note
{
public System.Guid NoteGUID { get; set; }
public System.Guid? ObjectAGUID { get; set; }
public System.Guid? ObjectBGUID { get; set; }
public string Text { get; set; }
public string NoteType { get; set; }
}
Help?

Entity Framework Multiple Column as Primary Key by Fluent Api

These are my simplified domain classes.
public class ProductCategory
{
public int ProductId { get; set; }
public int CategoryId { get; set; }
public virtual Product Product { get; set; }
public virtual Category Category { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentCategoryId { get; set;}
}
This is my mapping class. But it doesn't work.
public class ProductCategoryMap : EntityTypeConfiguration<ProductCategory>
{
public ProductCategoryMap()
{
ToTable("ProductCategory");
HasKey(pc => pc.ProductId);
HasKey(pc => pc.CategoryId);
}
}
How should I map these classes to provide, so that one product can be seen in multiple categories?
Use anonymous type object instead of 2 separated statements:
HasKey(pc => new { pc.ProductId, pc.CategoryId });
From Microsoft Docs: EntityTypeConfiguration.HasKey Method
If the primary key is made up of multiple properties then specify an anonymous type including the properties. For example, in C# t => new { t.Id1, t.Id2 } and in Visual Basic .Net Function(t) New With { t.Id1, t.Id2 }.

Entity Framework MVC 4 Class Issue

I am trying to build a code first database with the Entity Framework with ASP.Net MVC 4.
I'm new to MVC & Entity Framework and I'm struggling with how to design my Class Objects.
I want a Members Class Like the one that follows, that has a data property of the AddressInformation Class :-
public class Member
{
public virtual int MemberID { get; set; }
public virtual string Forename { get; set; }
public virtual string Surname { get; set; }
public virtual int age { get; set; }
public virtual AddressInformation Address { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string HomePhoneNumber { get; set; }
public virtual string MobileNumber { get; set; }
}
public class AddressInformation
{
public virtual int MemberID { get; set; }
public virtual string HouseNoName { get; set; }
public virtual string StreetName { get; set; }
public virtual string Town { get; set; }
public virtual string County { get; set; }
public virtual string PostCode { get; set; }
public virtual string Country { get; set; }
}
I also have another class that inherits from DbContext :-
public class CentralDataStore :DbContext
{
public DbSet<Member> Members { get; set; }
public DbSet<AddressInformation> AddressInfo { get; set; }
}
When I add the controller I am not getting the abilty to enter AddressInformation, only members info has populated through to my View's.
Anyone suggest the best method to attack this with? As I say, I'm new to MVC.
You do not need to make all of your properties virtual, only the ones used for navigation. And you need to setup up the relationship between Member and AddressInformation using the Fluency API. Also your primary key needs to be named Id or use an attribute or Fluency API to specify it is a primary key. You are also missing the id for mapping the Member to the AddressInformation. Here is what your class definition should look like.
public class Member
{
public int ID { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int age { get; set; }
public virtual int AddressId { get; set; }
public virtual AddressInformation Address { get; set; }
public string EmailAddress { get; set; }
public string HomePhoneNumber { get; set; }
public string MobileNumber { get; set; }
}
public class AddressInformation
{
public int ID { get; set; }
public string HouseNoName { get; set; }
public string StreetName { get; set; }
public string Town { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
}
Note I added the property AddressId to provide the mapping to the AddressInformation object/table. Configure the relationships in the Fluency API like this.
public class MemberConfig : EntityTypeConfiguration<Member>
{
internal MemberConfig()
{
this.HasKey(m => m.ID);
this.HasRequired(m => m.Address)
.WithRequiredDependent(a => a.ID)
.HasForeignKey(m => m.AddressId);
}
}
By setting up the foreign key relationship EF will automatically load the AddresssInformation into the Member object.
As far as I know the standard templates for generating the views does not implement the input fields for nested objects. But there is an option to expand the standard templates of MVC applications like in this link. There you can add the generation of input fields for nested classes if you are a fimilar to T4 templates.
You must be careful using this template, you can EASILY get a stackoverflow
Especially when using Entity Framework, when you have two entities with navigation properties that point to each other
The default Object template prevents recursion to a specific depth to prevent an infinite loop. I didn't like this so I wrote my own:
/Views/Shared/object.cshtml
#model object
#using System.Text;
#using System.Data;
#{
ViewDataDictionary viewData = Html.ViewContext.ViewData;
TemplateInfo templateInfo = viewData.TemplateInfo;
ModelMetadata modelMetadata = viewData.ModelMetadata;
System.Text.StringBuilder builder = new StringBuilder();
string result;
// DDB #224751
if (templateInfo.TemplateDepth > 2)
{
result = modelMetadata.Model == null ? modelMetadata.NullDisplayText
: modelMetadata.SimpleDisplayText;
}
foreach (ModelMetadata propertyMetadata in modelMetadata.Properties
.Where(pm => pm.ShowForEdit
&& pm.ModelType != typeof(System.Data.EntityState)
&& !templateInfo.Visited(pm)))
{
builder.Append(Html.Editor(propertyMetadata.PropertyName).ToHtmlString());
}
result = builder.ToString();
}
#Html.Raw(result)

Categories