I have a hierarchy of parent > children models and DTO using C# and AutoMapper 7 in a .Net Core 3 project. I am having problems mapping the parent model to the lowest child DTO.
The models involved look like this:
Parent Model #1:
public class ParentModel
{
public int Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<MainElement> MainElements { get; set; }
public ParentElementModel()
{
MainElements = new Collection<MainElement>();
}
}
Parent Model #2 (This is actually a .Net Core Identity User but simplified for this):
public class ParentUserModel
{
public int Id { get; set; }
public string UserName { get; set; }
public string OtherData { get; set; }
}
Parent Model #3 Which is the MainElement Model which includes a CreatedBy that is of type of the ParentUserModel above:
public class MainElement
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ParentUserModel CreatedBy { get; set; }
public int CreatedById { get; set; }
public string OtherData { get; set; }
}
The DTO to map are:
Most parent DTO:
public class ParentModelForReturnDto
{
public int Id { get; set; }
public string Title { get; set; }
public ICollection<MainElementForParentModelDto> MainElements { get; set; }
}
The ICollection DTO model:
public class MainElementForParentModelDto
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public UserCreatorForMainElementDto CreatedBy { get; set; }
}
The DTO for the CreatedBy ParentUserModel that lacks the OtherData which would be sensitive info I can't include:
public class UserCreatorForMainElementDto
{
public int Id { get; set; }
public string UserName { get; set; }
}
My problem is that the ParentModelForReturnDto's MainElementForParentModelDto's CreatedBy is coming back null when I want it to have the CreatedBy ParentUserModel's Id and Username
Here are my related mappings that don't fully map it:
CreateMap<ParentModel, ParentModelForReturnDto>();
CreateMap<MainElement, MainElementForParentModelDto>();
CreateMap<ParentUserModel, UserCreatorForMainElementDto >();
Can anyone please help me figure out how to map these so that the CreatedBy returns of type UserCreatorForMainElementDto and not null?
I believe the error to be in the mapping from MainElement to MainElementForParentModelDto but I am unsure. I have been trying for awhile to figure this out.
Any questions I can clarify. Tried to take out the unnecessary while keeping enough. Thank you to anyone who can help!
Edit:
The above mappings are using Profile.
Here is what I am mapping in my controller:
var parentModelFromRepo = _context.GetParent(name)
var parentToReturn = _mapper.Map<ParentModelForReturnDto>(parentModelFromRepo);
Related
I have to import a set of data from one database to another with a somewhat different schema, and I'm considering using AutoMap. I could just write a bunch of SQL scripts, but I already have both databases in EF and I want to learn AutoMap ...
While many of the classes are similar, the problem I'm having is where the structure is really different. The target models were designed with several more layers of classes. Instead of flattening, I need to expand.
The target classes have the following properties:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public ContactInfo Location { get; set; }
public List<Policy> Policies { get; set; }
}
public class ContactInfo
{
public int Id { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public State State { get; set; }
public string Zip { get; set; }
public string EMail { get; set; }
public List<Phone> PhoneNumbers { get; set; }
}
public class Phone
{
public int Id { get; set; }
public string Name { get; set; }
public string Number { get; set; }
}
public class Policy
{
public int Id { get; set; }
public PolicyNumber PolicyNumber { get; set; }
public List<Transaction> Transactions { get; set; }
}
The source tables, however, are relatively flattened.
public partial class Account
{
public string AccountId { get; set; }
public string AccountName { get; set; }
public string PolicyNumber { get; set; }
}
public partial class Transaction
{
public int TransactionId { get; set; }
public int AccountId { get; set; }
public Nullable<System.DateTime> EffectiveDate { get; set; }
public string InsuredName { get; set; }
public string InsuredAddress { get; set; }
public string InsuredCity { get; set; }
public string InsuredState { get; set; }
public string InsuredZip { get; set; }
public string InsuredPhone { get; set; }
}
I can create the Map, but I don't know how to tell AutoMapper to handle converting the string Policy to a policy object and then add it to the list of Policies.
Mapper.CreateMap<Source.Account, Destination.Account>();
Even worse, the source data inexplicitly has the name and address info at the transaction level. Before you tell me that AutoMap might not be the best solution, please understand that these two source tables are 2 out of over 40 tables in this database, and that the others are not nearly as troublesome.
Can I configure AutoMap to convert the string property PolicyNumber to a Policy Object and add it to the Policies List of the target class?
Any suggestions on how I can get the name and address information from the Transaction into a ContactInfo class and add it at the Account level?
Thank you.
Thanks to Thomas Weller. Custom Value Resolvers handled exactly what I needed.
I'm new to ASP.NET MVC and EF hopefully this is not a silly question
When i pass model to view i'm getting this error - Exception Details: System.Data.SqlClient.SqlException: Invalid column name 'Environment_Id'.
Model nor database table has a property by that name. Could any guide me on this?.
**Here is the Version Model Class**
public partial class Version
{
public Version()
{
this.ProfileVersions = new List<ProfileVersion>();
this.ServerInfoes = new List<ServerInfo>();
}
public int Id { get; set; }
public string Number { get; set; }
public string Tag { get; set; }
public string Owner { get; set; }
public string Approver { get; set; }
public string Description { get; set; }
public virtual ICollection<ProfileVersion> ProfileVersions { get; set; }
public virtual ICollection<ServerInfo> ServerInfoes { get; set; }
}
**Profile Version Class**
public partial class ProfileVersion
{
public ProfileVersion()
{
this.PlatformConfigurations = new List<PlatformConfiguration>();
}
public int Id { get; set; }
public int ProfileId { get; set; }
public int EnvironmentId { get; set; }
public int VersionId { get; set; }
public Nullable<bool> Locked { get; set; }
public string LockedBy { get; set; }
public string Comments { get; set; }
public Nullable<int> Active { get; set; }
public virtual Environment Environment { get; set; }
public virtual ICollection<PlatformConfiguration> PlatformConfigurations { get;
set; }
public virtual PlatformProfile PlatformProfile { get; set; }
public virtual Version Version { get; set; }
}
**ServerInfo**
public partial class ServerInfo
{
public ServerInfo()
{
this.PlatformConfigurations = new List<PlatformConfiguration>();
}
public int Id { get; set; }
public string ServerName { get; set; }
public int ProfileId { get; set; }
public int VersionId { get; set; }
public int EnvironmentId { get; set; }
public string ServerType { get; set; }
public Nullable<short> Active { get; set; }
public string Domain { get; set; }
public string Location { get; set; }
public string IP { get; set; }
public string Subnet { get; set; }
public string Gateway { get; set; }
public Nullable<int> VLan { get; set; }
public string DNS { get; set; }
public string OS { get; set; }
public string OSVersion { get; set; }
public string Func { get; set; }
public Nullable<short> IISInstalled { get; set; }
public string ADDomainController { get; set; }
public string ADOrganizationalUnit { get; set; }
public string ADGroups { get; set; }
public string LastError { get; set; }
public Nullable<System.DateTime> LastUpdate { get; set; }
public virtual Environment Environment { get; set; }
public virtual ICollection<PlatformConfiguration> PlatformConfigurations { get;
set; }
public virtual PlatformProfile PlatformProfile { get; set; }
public virtual Version Version { get; set; }
public virtual VMConfiguration VMConfiguration { get; set; }
}
**Controller Code-**
public ViewResult Index(string id )
{
var profileVerList = from ver in _context.Versions
where !(from pfv in _context.ProfileVersions
select pfv.VersionId).Contains(ver.Id)
select ver;
var bigView = new BigViewModel
{
VersionModel = profileVerList.ToList(),
};
return View(model: bigView);
}
**In the View where the exception is thrown**
#Html.DropDownList(
"SelectedVersionID",
new SelectList(
Model.VersionModel.Select(x => new { Value = x.Id, Text = x.Number}),
"Value",
"Text"
)
)
In your ProfileVersion and ServerInfo entities you have an Environment navigation property. By default, Entity Framework will try to create a database column called [Property Name]_[Referenced class PK]. In your scenario, that's Environment_Id. The problem, right now, is that you have not done a migration to have this database column created.
If I had to imagine what happened here, I'd say you first created the classes with EnvironmentId properties, migrated, then later decided to add the navigation properties, Environment to each, expecting EF to associate that with your existing EnvironmentId properties. That's where you went wrong. As I said above, EF convention is to look for a database column named Environment_Id, so if you want EF to use EnvironmentId instead, you just need to tell it so with the ForeignKey data annotation:
[ForeignKey("Environment")]
public int EnvironmentId { get; set; }
In My Case I have added My Primary Key Relationship to Same Key .. SO I have simply remove..
I realize this question is 3 years old now, but I saw a different reason for the error - both in the original question and in my own code that was pretty similar. And, in my case, I had the same error as stated above.
I had a "MY_ACTIONS" table with an ID and Name pair that I wanted to be added to a dropdown. Here's the model:
namespace TestSite.Models
{
public class MY_ACTIONS
{
//[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MY_ACTIONS()
{
this.o_actions = new HashSet<MY_ACTIONS>();
}
[Key]
public int action_id { get; set; }
[StringLength(100)]
public string action_name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MY_ACTIONS> o_actions { get; set; }
}
}
And to get an action to display on the dropdown it had an ID set in an int field called LASTACTION in my main table. In that model I had declared the ForeignKey relationship:
namespace TestSite.Models
{
[Table("MAIN_TABLE")]
public partial class MAIN_TABLE
{
[Key]
public int MAIN_TABLE_ID { get; set; }
public int LASTACTION { get; set; } // this would carry a number matching action_id
[ForeignKey("LASTACTION")]
public virtual MY_ACTIONS MY_ACTIONS { get; set; }
}
}
I had the error Invalid column name 'MY_ACTIONS_action_id' when loading this dropdown in my view:
#Html.DropDownList("lastaction", null, htmlAttributes: new { #class = "form-control" })
...for which I was using this ViewBag in my Controller function:
Model1 db = new Model1(); // database context
MAIN_TABLE o_main = new MAIN_TABLE();
o_main.lastaction = 2;
ViewBag.lastaction = new SelectList(db.MY_ACTIONS, "action_id", "action_name", o_main.lastaction);
If I did not have my FK relationship declared:
[ForeignKey("LASTACTION")]
public virtual MY_ACTIONS MY_ACTIONS { get; set; }
I probably also would've had the same issue. Having the representation of a virtual instance requires linking it with some physical property. This is similar to how this:
public virtual Environment Environment { get; set; }
Should be:
[ForeignKey("EnvironmentId")]
public virtual Environment Environment { get; set; }
in the ProfileVersion class, in the question, above, assuming that EnvironmentId is the Primary Key in a table called Environment (that model is not shown above).
For me, though, I already had that and I was still getting the error, so doing that still might not solve everything.
Turns out all I had to do was get rid of that ICollection<MY_ACTIONS> o_actions in the MY_ACTIONS model and the this.o_actions = new HashSet<MY_ACTIONS>(); line and it all went through fine.
There are many such lists and ICollections in play in the question above, so I would wager something is wrong with having them, as well. Start with just a plain model that represents the fields, then add in your virtual objects that represent tables linked to with foreign keys. Then you make sure your dropdown loads. Only after that should you start adding in your ICollections, HashSets, Lists<T> and other such amenities that are not actually physically part of the database - this can throw off Entity Framework into thinking it needs to do something with them that it doesn't need to do.
I am trying to make a wcf data service where i dont want to get acces to the database models but instead i want to use Data transfer objects. I have been reading a lot on the internet about how to accomplish this but i cant get a good answer for my problem. It is the first time for me doing something with wcf data services so i am a little inexperienced.
Oke here are my models that are linked to my database using Entity Framework
public class User
{
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string CountryCode { get; set; }
public string PhoneNumber { get; set; }
public ICollection<User> Contacts { get; set; }
public virtual Language Language { get; set; }
public User()
{
Contacts = new List<User>();
}
}
public class Message
{
[Key]
public int MessageId { get; set; }
public DateTime SentDate { get; set; }
public virtual User Sender { get; set; }
public virtual User Receiver { get; set; }
public string Content { get; set; }
public string OriginalCultureInfoEnglishName { get; set; }
public string ForeignCultureInfoEnglishName { get; set; }
}
public class Language
{
[Key]
public int LanguageId { get; set; }
public string CultureInfoEnglishName { get; set; }
}
Now i made a Service.svc which has my DatabaseContext so it can directly acces my database models. What i want to achieve is that instead of directly getting the database models i would like to get the DTO models when i query against my service.
A Example of how my dto's would look like
public class UserDTO
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Country { get; set; }
public string PhoneNumber { get; set; }
public ICollection<ContactDTO> Contacts { get; set; }
public virtual LanguageDTO Language { get; set; }
public UserModel()
{
Contacts = new List<ContactDTO>();
}
}
public class ContactDTO
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Country { get; set; }
public string PhoneNumber { get; set; }
public virtual LanguageDTO Language { get; set; }
}
public class LanguageDTO
{
public int LanguageId { get; set; }
public string CultureInfoEnglishName { get; set; }
}
public class MessageDTO
{
public int MessageId { get; set; }
public DateTime SentDate { get; set; }
public virtual ContactDTO Sender { get; set; }
public virtual ContactDTO Receiver { get; set; }
public string Content { get; set; }
public string OriginalCultureInfoEnglishName { get; set; }
public string ForeignCultureInfoEnglishName { get; set; }
}
Now is it possible to do it like this by making a different context that i can use in my service.svc or is there any other way to achieve the this?
for example i would like to get ContactDto by userid which is a user but with less properties because they are not relevant in the client application. I see this happening by a uri http://localhost:54895/Service.svc/ContactDto(1)
Hopefully anyone can clear this up for me because it is really frustrating :)
I'm not sure that what you're interested in is possible, exactly. You are looking to have multiple entity sets per type (aka MEST), and I don't know how well that's supported.
Beyond that point, and into a discussion around DTOs in general...
If you use custom providers, you can implement your own IDataServiceMetadataProvider and IDataServiceQueryProvider. When your service starts, you can make calls into the IDataServiceMetadataProvider to control what entities and properties are exposed or hidden -- including exposing properties that do not actually exist on your entity. The upshot is that you end up with a DTO without coding a DTO class. The exposed metadata is the DTO. This is a good resource for creating your own providers.
In your case, this isn't a 100% solution, because you can't selectively choose when a property is exposed and when it's not.
Hope this helps...
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)
I'm trying to create a one-to-one relation between two tables, but as a result I have one-to-many. What is the problem with this code?
namespace EFCF_Demo.Models
{
public class Post
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public string MiniContent { get; set; }
public string Author { get; set; }
public DateTime PublishDate { get; set; }
public int Rating { get; set; }
public virtual Content MainContent { get; set; }
}
public class Content
{
public int ID { get; set; }
public virtual Post Post { get; set; }
public string FullContent { get; set; }
}
public class PostEntities : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Content> Contents { get; set; }
}
}
Don't you need PostId in the Content class, and ContentId in the Post class?
public class Content
{
[Key]
public int PostId { get; set; }
public virtual Post Post { get; set; }
public string FullContent { get; set; }
}
what about this:) This should do it.
Problem was resolved by removing
public DbSet<Content> Contents { get; set; }
After that we don't need to use the Fluent API but I have some problems with saving.
Try this:
http://social.msdn.microsoft.com/Forums/eu/adonetefx/thread/4b5a46e0-f5c2-4c38-a73c-0038eaef2537
How to declare one to one relationship using Entity Framework 4 Code First (POCO)