Issue Description
My current C# project has an implementation where one Member can have one Address. The Address holds the foreign key to the Member it belongs to. This is working fine, but now a client asked to add another type, namely Supporter. A Supporter holds properties similar to a Member, including an address.
What would be the ideal way of implementing this? I thought about creating a new parent class Person that holds the shared properties including Address. I am however unsure about whether this approach would actually work, and how the resulting table (I'm using Entity Framework Core) would look like. Would there be a better way of implementing this?
Current Implementation
Current Member.cs
public class Member
{
public new int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
...
}
Current Address.cs
public class Address
{
public string Street { get; set; }
public string Street { get; set; }
public virtual Member Member{ get; set; }
public int MemberId { get; set; }
Possible Implementation
Possible Person.cs
public abstract class Person
{
public new int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
...
}
Possible Member.cs
public class Member : Person
{
public string MemberSpecificProp { get; set; }
...
}
Possible Supporter.cs
public class Supporter: Person
{
public string SupporterSpecificProp { get; set; }
...
}
Possible Address.cs
public class Address
{
public String Street { get; set; }
public String City { get; set; }
public virtual Person Person { get; set; }
public int PersonId { get; set; }
}
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);
I have two Dto's:
[TableName("Address")]
public class AddressDto
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Building { get; set; }
public string Appartment { get; set; }
public string ZipCode { get; set; }
public string Floor { get; set; }
public DateTime CreatedDate { get; set; }
}
[TableName("DistributionPoint")]
public class DistributionPointDto
{
[Column("Id")]
public int Id { get; set; }
public string Name { get; set; }
[Reference(ReferenceType.Foreign, ColumnName = "AddressId", ReferenceMemberName = "Id")]
public AddressDto Address { get; set; }
}
How can I get DistributionPointDto with nested AddressDto using nPoco?
I have a generic repository for CRUD with method:
public T FindById<T>(int id)
{
return _db.SingleById<T>(id);
}
But, when I'm trying to get DistributionPointDto, AddressDto is null
Curious if you got your code to work, but I have found that you need to use Query instead of SingleById to include referenced properties, and then you can use an Include statement/clause, however I don't think generics work with this, you have to explicitly mention the referenced property name, which is AddressDTO for you.
_db.Query<DistributionPointDTO>()
.Include(x => x.AddressDTO)
.Where(x => x.Id == id).First();
So I'm currently debating the option of either defining concrete classes with properties, or to go with a metadata-type design. For example:
public class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
public string EmployeeCode { get; set; }
public string SpecialAssignment { get; set; }
public string SomeFutureProperty { get; set; }
}
Versus a key/value pair design which can be dynamic:
public class Employee
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<MetaKeyValue> Properties { get; set; }
}
public class MetaKey
{
public Guid Id { get; set; }
public string EntityType { get; set; } // could be a dictionary or enum
public string Name { get; set; }
public string Description { get; set; }
public string Required { get; set; }
}
public class MetaKeyValue
{
public Guid Id { get; set; }
public Guid EntityId { get; set; }
public Guid MetaKeyId { get; set; }
public string Value { get; set; } // string isn't the preferred object type
}
So, the debate is that I'm not sure which one is more efficient. The target persistence is a SQL database using Entity Framework. The beauty of the metadata design is that without modifying code and planning a deployment, new "properties" could be added to an entity and the values could be added and retrieved. The negative is that it's not what I'm accustomed to as I am an old-school coder who likes concrete classes that are statically defined. Would the dynamic design bite me in the rear later down the line?
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.