Using entity framework I've been trying to create this relationship. Basically I have 1 object which has a Result. The Result object is abstract, as it has to be one of the 3 classes that inherit from Result, i.e. Approved, Rejected, or Modified:
I'm trying to create the table structure using Entity Framework. Originally I was going for a TPCT (Table Per Concrete Type) structure, so there would be no Result table, but I wanted to keep the link back in the Action table if I wanted to reference the Result, so now I'm attempting just TPT structure. I find TPCT is cleaner, but ultimately if TPT is the only way to achieve what I want, I'm fine with it.
I've tried variations of the following for my model structure:
public class Action
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public int Result_Id {get; set;}
[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}
public string Description {get; set;}
}
public abstract class Result
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[Required]
public int Action_Id {get; set;}
[ForeignKey("Action_Id")]
public virtual Action Action {get; set;}
public string Comment {get; set;}
public class Approved : Result
{
public string Thing {get; set;}
}
public class Rejected : Result
{
public string Stuff {get; set;}
}
public class Modified : Result
{
public string Whatever {get; set;}
}
}
And then I've tried the following 2 strategies in my context file to either implement TPT:
modelBuilder.Entity<Approved>().ToTable("Approved");
modelBuilder.Entity<Rejected>().ToTable("Rejected");
modelBuilder.Entity<Modified>().ToTable("Modified");
Or for TCPT:
modelBuilder.Entity<Approved>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Approved");
});
modelBuilder.Entity<Rejected>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Rejected");
});
modelBuilder.Entity<Modified>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Modified");
});
Everytime I try to add the new migration, whatever I try, I'm faced with this error:
Unable to determine the principal end of an association between the types 'Result' and 'Action'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
The one time I was able to have it work was if I removed this reference from in the Action class:
public int Result_Id {get; set;}
[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}
But I would really like to keep that reference there so then when I go into my DB to grab that Action object, I can immediately tell if there is a Result associated to it, without having to go through all 3 Result tables to see if there is a reference to that Action (which is why I think I need to have TPT...)
Any help to get this working would be greatly appreciated!
With a lot of research and trial and error, I discovered what I needed to get the result I wanted. It's TPCT DB structure, and the Action object is able to keep the reference to Result. Here are the model classes:
public class Action
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public virtual Result Result {get; set;} //just virtual here, as Action is the dependent and Result is the principal-- i.e. this Result isn't required
public string Description {get; set;}
}
public abstract class Result
{
//got rid of the Result_Id, since it's 1:1 the Action_Id can be the Key
[Required, Key] //added "Key"
public int Action_Id {get; set;}
[ForeignKey("Action_Id")]
public Action Action {get; set;} //removed this virtual, as Action is Required for Result, that makes Result the principal
public string Comment {get; set;}
public class Approved : Result
{
public string Thing {get; set;}
}
public class Rejected : Result
{
public string Stuff {get; set;}
}
public class Modified : Result
{
public string Whatever {get; set;}
}
}
And here is the fluent API code from the context:
//this gave me TPCT like I wanted
modelBuilder.Entity<Approved>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Approved");
});
modelBuilder.Entity<Rejected>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Rejected");
});
modelBuilder.Entity<Modified>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Modified");
});
//this defined the principal-dependent relationship I was missing
modelBuilder.Entity<Action>()
.HasOptional(a => a.Result)
.WithRequired(a => a.Action)
.Map(x => x.MapKey("Action_Id"));
And then it worked! Hopefully this example can assist someone else.
Related
Is there a way to have AutoMapper only map properties that match explicitly? My Model has a property UserAccountId and also has a navigation property UserAccount that has an Id property, if the user posts UserAccountId, I want AutoMapper to map UserAccountId and leave UserAccount.Id null; I'd love to avoid using Ignore since that will prevent me from explicitly posting UserAccount.Id as well. I can't find any information on controlling AutoMapper's name matching strategy...
public class Role {
public int Id {get; set;}
public int UserAccountId {get; set;}
public UserAccount UserAccount {get; set;}
...
}
public class UserAccount {
public int Id {get; set;}
...
}
public class RoleViewModel {
public int Id {get; set;}
public int UserAccountId {get; set;}
}
public ActionResult AddRole(RoleViewModel viewModel) {
var model = GetModel(viewModel.Id);
Mapper.Map(viewModel, model);
//Do not infer and map UserAccount.Id
}
Don’t use AutoMapper for this case. It wasn’t built to support this type of scenario.
Or use ForPath(...).Ignore, but I just wouldn’t use my library for this scenario.
I'm very new to EF (using Core as a first up) and have been battling with a problem for a while now.
Briefly, I have 2 entities that relate to each other on a one-to-one basis. In the one example, which works fine, I have an "parent" entity related to a "child" entity such as below
[Table("Attribute", Schema = "dbo")]
public class Attribute
{
public Attribute()
{
}
[Key, Column(Order = 1)]
public Guid EntityColumnRefNo {get; set;}
public string PhysicalName {get; set;}
public string Caption {get; set;}
public AttributeType AttributeType {get; set;}
}
[Table("AttributeType", Schema = "dbo")]
public class AttributeType
{
public AttributeType()
{
}
[Key, Column(Order = 1)]
public Guid AttributeTypeRefNo {get; set;}
public string UserRefCode {get; set;}
public string Description {get; set;}
}
The physical column name on the Attribute table on the database for the AttributeTypeRefNo is exactly that - AttributeTypeRefNo (Not sure if that has relevance). When returning the Attribute model using the following code
var res = db?.Attribute
.Where(x => x.EntityRefNo == entityrefNo)
.Include(c => c.AttributeType)
It works perfectly fine. The Attribute returns with the populated AttributeType.
My problem occurs when adding another Model above Attribute, and reference Attribute twice.
[Table("Relations", Schema = "dbo")]
public class Relations
{
public Relations()
{
}
[Key, Column(Order = 1)]
public Guid GenRelRefNo {get; set;}
public Attribute EntityColumnTo { get; set; }
public Attribute EntityColumnFrom { get; set; }
public string CollaborationCaption {get; set;}
public string CollaborationTooltip {get; set;}
}
The Relations Model contains 2 occurrences of Attributes, each referring to a different attribute. The column names on the Table in the database are as they are on the model - EntityColumnTo and EntityColumnFrom. The code I'm using the Relations Model is
var res = db?.Relations
.Include(c => c.EntityColumnTo)
.Include(c => c.EntityColumnFrom)
.Where(x => x.EntityColumnTo.EntityRefNo == entityrefNo
|| x.EntityColumnFrom.EntityRefNo == entityrefNo).ToList();
This call doesn't work, I get the errors below, moaning about invalid column names.
System.Data.SqlClient.SqlException occurred
HResult=0x80131904
Message=Invalid column name 'EntityColumnFromEntityColumnRefNo'.
Invalid column name 'EntityColumnToEntityColumnRefNo'.
Invalid column name 'EntityColumnToEntityColumnRefNo'.
Invalid column name 'EntityColumnFromEntityColumnRefNo'.
Invalid column name 'EntityColumnFromEntityColumnRefNo'.
Invalid column name 'EntityColumnToEntityColumnRefNo'.
Invalid column name 'EntityColumnFromEntityColumnRefNo'.
Invalid column name 'EntityColumnToEntityColumnRefNo'.
Source=.Net SqlClient Data Provider
StackTrace:
<Cannot evaluate the exception stack trace>
So, what am I doing wrong? I have other examples that mirror then method I first described and they all work fine. I'm sure having 2 Declarations on the Relations model for the same Attributes type is the problem and needs some extra handling - how would the framework know what properties to bind where. I've hacked the code to make it work using extension methods and all sorts of other nonsense, but I can't believe that's the right way of doing things. Thanks in advance.
You should add Foreign Key properties to all your entities. EF supports not doing this, but it's more difficult. Once you have explicit ForeignKey properties, it's simple to decorate the model. EG
[Table("Relations", Schema = "dbo")]
public class Relations
{
public Relations()
{
}
[Key, Column(Order = 1)]
public Guid GenRelRefNo {get; set;}
[ForeignKey("EntityColumnToRefNo")]
public Attribute EntityColumnTo { get; set; }
[ForeignKey("EntityColumnFromRefNo")]
public Attribute EntityColumnFrom { get; set; }
public Guid EntityColumnToRefNo { get; set; }
public Guid EntityColumnFromRefNo { get; set; }
public string CollaborationCaption {get; set;}
public string CollaborationTooltip {get; set;}
}
I've got a problem handling self referencing model in my application using Automapper Projections. This is how my models look like:
public class Letter
{
public int? ParentId {get; set;}
public Letter ParentLetter {get; set;
public int Id {get; set;}
public string Title {get; set;}
public string Content {get; set;}
public DateTime? ReceivedTime {get; set;}
public DateTime? SendingTime {get; set;}
public List<Destination> Destinations {get; set;}
public List<Letter> Responses {get; set;}
}
public class LetterView
{
public int? ParentId {get; set;}
public int Id {get; set;}
public string Title {get; set;}
public string Content {get; set;}
public DateTime? ReceivedTime {get; set;}
public DateTime? SendingTime {get; set;}
public List<DestinationView> Destinations {get; set;}
public List<LetterView> Responses {get; set;}
}
public class Destination
{
public int Id {get; set;}
public string Name {get; set;}
..
}
public class DestinationView
{
public int Id {get; set;}
public string Name {get; set;}
}
// The mapping:
CreateMap<Destination, DestinationView>
CreateMap<Letter, LetterView>
My problem is with mapping Letter to LetterView. The problem is somewhere with the Responses, I just can't figure out what should be changed.
Whenever running unit tests and asserting mapping configurations everything works, as well as mapping a letter with multiple responses to a view model.
However, whenever I get a letter with it's resposnes from the database (Entity framework 6), the projection to LetterView throws a stackoverflow exception.
Can anyone please explain me why this happens only on projection? What should I change?
A couple of options here, but usually the best choice is to set a max depth on the Responses. AutoMapper will try to spider the properties, and you've got a self-referencing DTO. First try this:
CreateMap<Letter, LetterView>()
.ForMember(d => d.Responses, opt => opt.MaxDepth(3));
Another option is to pre-wire your DTOs with a specific depth. You'd create a LetterView, and a ChildLetterView. Your ChildLetterView would not have a "Responses" property, giving you exactly 2 levels of depth on your DTO side. You can make this as deep as you want, but be very explicit in the DTO types in where they are in the hierarchy with Parent/Child/Grandchild/Greatgrandchild type names.
You probably have lazy loading enabled on your DbContext. Circular references may produce stack overflow exception. The best way to avoid it is to disable lazy loading :
context.ContextOptions.LazyLoadingEnabled = false;
// Bring entity from database then reenable lazy loading if needed
context.ContextOptions.LazyLoadingEnabled = true;
However, you will need to include all required navigation properties since EntityFramework will not brought them back while lazy loading is deactivated. Don't forget to re enable it after if you need it for other requests.
I have the following models (and corresponding DTOs):
public class Link
{
public int Id {get; set;}
public int FirstLinkId {get; set;}
public int SecondLinkId {get; set;}
public virtual Link FirstLink {get; set;}
public virtual Link SecondLInk {get; set;}
}
public class OtherObject
{
public int Id {get; set;}
public int LinkId {get; set;}
public string Name {get; set;}
public virtual Link Link {get; set;}
}
In my scenario, I can have a Link object where FirstLink and/or SecondLink can be null, references to other objects, or references to the same object.
Now I want to load an OtherObject entity from the db using EF. I load the entity itself and also the Link object associated with it. This is done perfectly by EF.
In this particular case, both FirstLink and SecondLink are the same as Link, therefore, when automapping from model to dto it just keeps on mapping into oblivion.
My mapping is:
Mapper.CreateMap<OtherObject, OtherObjectDto>().Bidirectional()
.ForMember(model => model.LinkId, option => option.Ignore());
where Bidirectional() is this extension:
public static IMappingExpression<TDestination, TSource> Bidirectional<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
return Mapper.CreateMap<TDestination, TSource>();
}
Is there way to tell Automapper not to map further down the tree in this case?
The way I would handle this is to create separate DTO objects for the children:
public class Employee
{
public int Id {get; set;}
public string Name { get; set; }
public Employee Supervisor {get; set; }
}
public class EmployeeDto {
public int Id {get; set;}
public string Name { get; set; }
public SupervisorDto Supervisor { get; set; }
public class SupervisorDto {
public int Id {get; set;}
public string Name { get; set; }
}
}
Mapper.CreateMap<Employee, EmployeeDto>();
Mapper.CreateMap<Employee, EmployeeDto.SupervisorDto>();
Don't let your DTOs be recursive/self-referential. Be explicit in your structure on how deep you want it to go.
EF can't do recursive joins, you're only doing one level, so don't make your DTOs go nuts with infinitely deep relationships. Be explicit.
I have the following model:
public class Customer
{
public int Id {get; set;}
public string Name {get; set;}
public int AddressId {get; set;}
public virtual Address Address {get; set;}
public virtual ICollection<CustomerCategory> Categories {get; set;}
}
public class CustomerCategory
{
public int Id {get; set;}
public int CustomerId {get; set;}
public int CategoryId {get; set;}
public virtual Category Category {get; set;}
}
public class Address
{
public int Id {get; set;}
public string Street{get; set;}
public virtual PostCode PostCode {get; set;}
}
From the above, and using GraphDiff, I want to update the customer aggregate as follows:
dbContext.UpdateGraph<Customer>(entity,
map => map.AssociatedEntity(x => x.Address)
.OwnedCollection(x => x.Categories, with => with.AssociatedEntity(x => x.Category)));
But the above is not updating anything!!
What is the correct way to use GraphDiff in this case?
GraphDiff basically distinguishes two kinds of relations: owned and associated.
Owned can be interpreted as "being a part of" meaning that anything that is owned will be inserted/updated/deleted with its owner.
The other kind of relation handled by GraphDiff is associated which means that only relations to, but not the associated entities themselves are changed by GraphDiff when updating a graph.
When you use the AssociatedEntity method, the State of the child entity is not part of the aggregate, in other words, the changes that you did over the child entity will not be saved, just it will update the parent navegation property.
Use the OwnedEntity method if you want to save tha changes over the child entity, so, I suggest you try this:
dbContext.UpdateGraph<Customer>(entity, map => map.OwnedEntity(x => x.Address)
.OwnedCollection(x => x.Categories, with => with.OwnedEntity(x => x.Category)));
dbContext.SaveChanges();