Entity Framework .Where nested in .Include - entity-framework

I'm attempting to perform a db lookup using EF5 code-first. The basic structure and table relationships are as follows;
public partial class Member
{
public int RecordID {get; set;}
public string Name {get; set;}
...etc.
public virtual ICollection<MemberLink> MasterLinks {get; set;}
public virtual ICollection<MemberLink> SlaveLinks {get; set;}
public virtual ICollection<Message> ReceivedMessages {get; set;}
public virtual ICollection<Message> SentMessages {get; set;}
}
public partial class MemberLink
{
public int RecordID {get; set;}
public virtual Member MasterMember {get; set;}
public virtual Member SlaveMember {get; set;}
...etc.
}
public partial class Message
{
public int RecordID {get; set;}
public virtual Member Sender {get; set;}
public virtual Member Recipient {get; set;}
...etc.
}
Now, the query I'm trying to perform is using the MemberLinkRepository, and looks like;
public IList<MemberLink> GetMasterLinks(int p_MemberID)
{
return Get()
.Include ( memberLink => memberLink.MasterMember )
.Include ( memberLink => memberLink.SlaveMember )
.Include ( memberLink => memberLink.MasterMember.ReceivedMessages
.Where(
msg => msg.Sender.RecordID == memberLink.SlaveMember.RecordID) )
.Where ( memberLink => memberLink.MasterMember.RecordID == p_MemberID)
.ToList();
Except EF doesn't seem to like the nested Where. I could split this out into 2 separate repository calls (and indeed, it's looking like I might have to do that) but in the interest of reducing calls to the db I'm trying to do it in one foul swoop. Does anyone know how I can achieve this in one single query?
I hope the code illustrates what I'm trying to do... If not, I'll try and explain a little better.

The short answer is no, EF will not let you do that using Include().
Think about the result if it let you do this: in one case your MemberLink.MasterMember.ReceivedMessages will be fully populated, on another identical looking object MemberLink.MasterMember.ReceivedMessages is actually a sub-set of messages! What happens if you try to add to the ReceivedMessages? What if the addition doesn't match the filter? It is a bag of hurt.
The answer is to use projections:
public IList<MemberLinkWithFiltereredMessages> GetMasterLinks(int p_MemberID)
{
return Get()
.Include(memberLink => memberLink.MasterMember)
.Include(memberLink => memberLink.SlaveMember)
.Where(memberLink => memberLink.MasterMember.RecordID == p_MemberID)
.Select(memberLink => new MemberLinkWithFilteredMessages
{
MemberLink = memberLink,
FilteredMessages = memberLink.MasterMember.ReceivedMessages
.Where(msg => msg.Sender.RecordID == memberLink.SlaveMember.RecordID)
})
.ToList();
}
What you are really doing is asking for a specific sub-set of information, so be explicit about it.

Related

Entity Framework - many to many with one primary

I have a Channel domain model as:
public class Channel
{
public string Id {get; set;}
public List<ChannelUser> ChannelUsers {get; set;}
}
and a ChannelUser class as follows (I have additional meta data on the table, removed here for brevity, which is why I used a specfic class to represent the many-to-many relationship).
public class ChannelUser
{
public string ChannelId {get; set;}
public string UserId {get; set;}
}
The primary key for ChannelUser is (ChannelId, UserId).
One (and only one) of these ChannelUsers can be the Owner of the Channel.
Theoretically, a User can be the Owner of many channels (but obviously a ChannelUser can only be the owner of the channel it is associated to).
I want to enforce that at database level rather than having a "IsOwner" property on ChannelUser and using business logic to ensure that the flag is only set once per channel. I want to also enforce that the ChannelOwner is one of the ChannelUsers and therefore don't want a relationship directly from Channel > User but keep it as Channel > ChannelUser.
Therefore I have updated Channel class as follows:
public class Channel
{
public string Id {get; set;}
public List<ChannelUser> {get; set;}
public ChannelUser Owner {get; set;}
}
And added the following fluent API expressions:
modelBuilder.Entity<ChannelUser>().HasKey(cu => new { cu.ChannelId, cu.UserId });
modelBuilder.Entity<ChannelUser>().HasOne<Channel>(x => x.Channel).WithMany(x => x.ChannelUsers)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<ChannelUser>().HasOne<User>(x => x.User).WithMany(x => x.UserChannels)
.OnDelete(DeleteBehavior.NoAction);
When adding migration this results in:
Both relationships between 'ChannelUser.Channel' and 'Channel.ChannelUsers' and between 'ChannelUser' and 'Channel.Owner' could use {'ChannelId'} as the foreign key. To resolve this configure the foreign key properties explicitly on at least one of the relationships.
Which I understand but I think this is what is desired. Because ChannelUser.ChannelId should always be the same Channel.Id for both relationships.
Please can anyone suggest how I can craft this relationship or an alternative approach that still enforces the many to many and the one to one relationships between the tables?
This isn't ideal approach but can be done, have a look at this ER diagram :
Ideal approach would be having proper Entity Mapping like :
User Class :
public class User
{
public string Id {get; set;}
public virtual List<Channel> Channels {get; set;}
// Other properties....
}
Channel Class :
public class Channel
{
public string Id {get; set;}
public List<User> Users {get; set;}
public User Owner {get; set;}
public string? OwnerId { get; set; }
public User Owner { get; set; }
}
ChannelUser Class to join the two:
public class ChannelUser
{
public string Id {get; set;}
public string ChannelId {get; set;}
public string UserId {get; set;}
}
Mapping :
modelBuilder.Entity<User>()
.HasMany<Channel>(user => user.channels)
.WithMany(channel => channel.users)
.Map(cu =>
{
cu.MapLeftKey("UserId");
cu.MapRightKey("ChannelId");
cu.ToTable("ChannelUser");
});
// configure one-to-many relationship for ownership
modelBuilder.Entity<Channel>()
.HasRequired<User>(c => c.User)
.WithMany(u => u.ChannelId )
.HasForeignKey<int>(c => c.UserId);
Responsibility of checking if user exists should be fairly easy in controller code after normalizing this, or we can use DB procedure, trigger to create constrain that checks if given owner ID is indeed in ChannelUser table for given channel.

Updating a many-to-many relationship neither entirely code nor database first

I seem to be struggling with a combination of naming conventions and understanding.
I have inherited a database and am building a MVC site that I was unable to get the "database-first" workflow to play nicely. In the end I manually constructed my context classes and have been working away happily.
I am now in a situation where I am unable to add an entity with a relationship to several other existing entities (the many-to-many).
My database looks (simplified for this question) like this:
ListItem Option OptionListItems
====== ====== ===============
Id Id ListItem_Id
Name Name Option_Id
My context contains a property that allows me to get all of my ListItems:
public virtual DbSet<ListItem> ListItems { get; set; }
And if I use some LINQ, I do something like the following, and the items are returned and the many-to-many relationship is satisfied and I get a list of Option within my ListItem:
var item = _context.ListItems
.Where(p => p.Id == id)
.Include(p => p.Options)
.SingleOrDefault();
In fact, I had to construct the cross-reference table in the database manually which I did when I tried to run the above query and the exception I got told me I had no object called dbo.OptionListItems. So I assumed we were all good.
Now I need to create a new ListItem and link it to one or more existing Option and I'm at a loss.
Once I've created my new ListItem in isolation, and attempt to call listItem.Options.Add(...) it fails, but I also get the exact same exception if I try to get a reference to a particular Option and try to do option.ListItems.Add(...).
The error is kind of amusing and is the opposite table name to what I have:
{"Invalid object name 'dbo.ListItemOptions'."}
I suspect that it goes against the grain of EF to build a type and a property on my context to directly access the cross reference table like this:
public virtual DbSet<OptionListItem> OptionListItems { get; set; }
But I'm completely baffled by the pattern to create new relationships.
We have this (many-to-many) working declaratively. Pseudocode:
public class ListItem
{
public ListItem()
{
this.RelatedOptions = new HashSet<OptionListItems>();
}
[Key]
public int Id {get; set;}
public string Name {get; set;}
public virtual ICollection<OptionListItems> RelatedOptions {get; set;}
}
public class Option
{
public Ortion()
{
this.RelatedItems = new HashSet<OptionListItems>();
}
[Key]
public int Id {get; set;}
public string Name {get; set;}
public virtual ICollection<OptionListItems> RelatedItems {get; set;}
}
public class OptionListItems
{
[Key]
public int Id {get; set;}
[Column("ListItemId", Order = 1)]
[ForeignKey("ParentListItem")]
public int ListItemId {get; set;}
[Column("OptionId", Order = 2)]
[ForeignKey("ParentOption")]
public int OptionId {get; set;}
public virtual ListItem ParentListItem {get; set;}
public virtual Option ParentOption {get; set;}
}
This should create full relationship declaratively
Credit goes to Steve Greene for pointing me in the right direction.
The table I had was created by convention and worked when I queried WorkItem with .Include(p => p.Options) however the convention seems to break down if you try to do an update. I'm unsure why, but the construction of the mapping table seems to be <Entity1>+<Entity2>+s when querying, but <Entity2>+<Entity1>+s when updating...
The good news, by using fluentAPI, I've created a specific mapping between the entities and forced the cross reference table and both querying and updating works!
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ListItem>()
.HasMany<Option>(s => s.Options)
.WithMany(c => c.ListItems)
.Map(cs =>
{
cs.MapLeftKey("ListItem_Id");
cs.MapRightKey("Option_Id");
cs.ToTable("OptionListItems");
});
}

Automapper with reciprocal navigation properties

need a bit of help here.
I have a couple of classes that I am trying to map using Automapper. I am using EF core.
The basic domain is like this:
Public class A
{
public string Id {get; set;}
public string Name {get; set;}
public virtual Icollection<AB> AB {get; set;}
}
Public class B
{
public string Id {get; set;}
public string Name {get; set;}
public virtual ICollection<AB> AB {get; set;}
}
Public class AB
{
public string A_Id {get; set;}
public string B_Id {get; set;}
public virtual A A {get; set;}
}
My DTOs are like this:
Public class A_DTO
{
public string Id {get; set;}
public string Name {get; set;}
public ICollection<B> Bs {get; set;}
}
Public class B_DTO
{
public string Id {get; set;}
public string Name {get; set;}
public ICollection<A> As {get; set;}
}
Now where I get stuck is:
How to set up the mapping so that Automapper automatically retrieves the list of children (e.g the relevant 'Bs' for the current 'A')
How to configure my DTOs so that, for example, the retrieved 'Bs' for an 'A' do not expose the 'A's navigation property to prevent infinite recursion.
Thank you!
Partial answer here. I was researching and stumbled upon https://www.exceptionnotfound.net/entity-framework-and-wcf-loading-related-entities-with-automapper-and-reflection/
So I changed my DTOs by removing the navigation properties when the DTO is not the principal.
Public class A_DTO
{
public string Id {get; set;}
public string Name {get; set;}
}
Public class A_Nav_DTO: A_DTO
{
public ICollection<B> Bs {get; set;}
}
and in my mappings I did
CreateMap<A, A_DTO>();
CreateMap<A, A_Nav_DTO>()
.ForMember(dto => dto.B, map =>
map.MapFrom(model =>
model.AB.Select(ab => ab.B).ToList()));
Now this works, but obviously now I have to map three classes instead of two. Any suggestions on how to improve this solution?
I know it's an old question but hopefully this helps someone.
You could call Automapper's ResolveUsing method like so :
cfg.CreateMap<A, A_DTO>()
.ForMember(x => x.Bs,
opt => opt.ResolveUsing(src => RemoveInclusions(src)));
And in RemoveInclusions method you can manually set B's navigation of A's to null like :
private B RemoveInclusions(A src)
{
src.Bs.A = null;
return src.Bs;
}

Bidirectional links to the same class in Entity Framework (code first)

I would like to create bidirectional links to the same class. Id like for the relationship class to have the attributes that would explain how the two classes are related. It may be a parent-child relationship or it be a simple "reference" relationship.
Currently, if I use the setup below, Entity Framework will automatically create a 3rd foreign key in the link table for the "myChildNodes" relationship. The only way I can get Entity Framework to understand what I am trying to do on the link class is to create two collections I.E. (childOf and ParentOf).
I would like to dynamically add relationship types and not need to create a collection representing that relationship type. I would rather handle that in the repository for the node object.
Node
{
Public int id {get; set;}
Public datetime createDate {get; set;}
Public bool isModified {get; set;}
//I would like just one collection for all links from this node as the source node
Public virtual ICollection<Link> myChildNodes{get; set;}
//I don't want to use something like this that explicitly defines the relationship
//Public virtual ICollection<Node> parentOf{get; set;}
//Public virtual ICollection<Node> childOf{get; set;}
Public Node() {
}
}
Link {
Public int id {get; set;}
Public datetime createdDate {get; set;}
Public string linkType {get; set;}
[ForeignKey("SourceNode")]
Public int? SourceNodeId { get; set;}
Public Node SourceNode {get; set;}
[ForeignKey("TargetNode")]
Public int? TargetNodeId { get; set;}
Public Node TargetNode {get; set;}
Public Link() {
}
}
Has anyone had success with this design before?

Code first mapping for self-related xref table

I have read through several threads on StackOverflow and have not been able to figure this out. I am hoping someone can offer some advice. I have some POCO classes that look like this:
Person
{
int PersonCode {get; set;}
...
virtual List<PersonContact> {get; set;}
}
PersonContact
{
int PersonPersonCode {get; set;}
int ContactPersonCode {get; set;}
int PersonContactTypeCode {get; set;}
virtual PersonContactType {get; set;}
virtual Person Person {get; set;} // not sure I really need this one
virtual Person Contact {get; set;}
}
Each Person record will have zero to many PersonContact records. Each PersonContact record links one Person record to one other Person record and indicates the type of relationship between the two Person records with the PersonContactTypeCode.
I need to be able to map this so that a Person record can be navigated to his related PersonContact records. Something like this:
var john = new Person(...);
var david = new Person(...);
john.PersonContacts.Add(new PersonContact
{
Contact = david,
PersonContactType = ... // manager
});
and then
john.PersonContacts
.Where(c => c.PersonContactType.PersonContactTypeCode == "manager")
.FirstOrDefault();
would return
david
I have tried so many combinations of Data Annotations and Fluent API that I can hardly remember where I started. I seemed to have the best luck with this combination:
modelBuilder.Entity<Person>()
.HasMany(entity => entity.PersonContacts)
.WithRequired(person => person.Person)
.HasForeignKey(xref => xref.PersonPersonCode)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Person>()
.HasMany(entity => entity.PersonContacts)
.WithRequired(xref => xref.Contact)
.HasForeignKey(entity => entity.ContactPersonCode)
.WillCascadeOnDelete(false);
But, when I try to add more than one PersonContact to a Person, I get this error:
Multiplicity constraint violated. The role 'Person_PersonContacts_Source' of the
relationship '...Entities.Person_PersonContacts' has multiplicity
1 or 0..1.
I really appreciate any help, I am just completely stumped right now. By the way, I am open to changing these POCOs if necessary.
I'd guess it's because you are using the same navigation property to link to PersonContact.Person and PersonContact.Contact.
Assuming this:
Person
{
int PersonCode {get; set;}
...
virtual ICollection<PersonContact> PersonContacts {get; set;}
}
Try something like:
modelBuilder.Entity<PersonContact>()
.HasRequired(x => x.Person)
.WithMany(x => x.PersonContacts)
.HasForeignKey(x => x.PersonPersonCode)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PersonContact>()
.HasRequired(x => x.Contact)
.WithMany()
.HasForeignKey(x => x.ContactPersonCode)
.WillCascadeOnDelete(false);
Try this:
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId {get; set;}
...
public virtual ICollection<PersonContact> PersonContacts {get; set;}
}
public class PersonContact
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContactId {get; set;}
[ForeignKey("Person"), DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PersonId {get; set;}
public virtual Person Person {get; set;}
}
I have used Property Mapping instead of Fluent Mapping like you tried in your attempt.
If you have any questions let me know. As far as the relationship between your two Entities, this is what you need.
I managed to get something similar working using something like this
in my domain classes
public class PersonContact
{
public int Id { get; set; }
[Required]
public virtual Person ContactPerson { get; set; }
[Required]
public virtual Person Person { get; set; }
[Required]
public int ContactType { get; set; }
}
public class Person
{
int Id { get; set; }
private readonly List<PersonContact> _contacts = new List<Contact>();
public virtual List<PersonContact> Contacts
{
get
{
return this._contacts;
}
}
}
in my context
public DbSet<Person> People { get; set; }
public DbSet<PersonContact> Contacts { get; set; }
I made this change in a migration , and had to edit the generated create table code to set
Cascade delete to false for the fwo Foreign Keys to the person table, inside the PersonContact Table.
I get an extra Person_Id1 column in the PersonContact table. It seems to populate with the same data as Person_Id. This seems to be needed by EF when I create a bindingsource - as I get errors without it.
I wouldn't put explicit Foreign keys in, let the migration create them.