EF code first many to many relation when no relation in DB - c#

Suppose I have two classes model like:
public class AuthorityUser
{
public string GUID { get; set; }
public int UserID { get; set; }
public ICollection<Authority1> Authorities { get; set; }
public AuthorityUser()
{
Authorities = new HashSet<Authority1>();
}
}
public partial class Authority1
{
public virtual int AID
{
get;
set;
}
public virtual ICollection<AuthorityUser> AuthorityUsers { get; set; }
public Authority1()
{
AuthorityUsers = new HashSet<AuthorityUser>();
}
}
I am going to make Many To Many relation between them based on UserAuthorityMap connected table in DB.
so I did this to make M:N relation in OnModelCreating()
modelBuilder.Entity<AuthorityUser>().ToTable("Gainer").HasKey(x => x.UserID);
modelBuilder.Entity<Authority1>().ToTable("Authority").HasKey(x => x.AID);
modelBuilder.Entity<AuthorityUser>()
.HasMany<Authority1>(s => s.Authorities)
.WithMany(c => c.AuthorityUsers)
.Map(cs =>
{
cs.MapLeftKey("UserID");
cs.MapRightKey("AID");
cs.ToTable("UserAuthorityMap");
});
As I mentioned in title there is no relation between them in DB so the diagram in DB is like picture below :
when I run this :
dbContext.AuthorityUsers.SingleOrDefault(x => x.UserID == 65);
the related Authorities won't be loaded from DB.
so should I use HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) to make it right or something else?

Since Authorities navigation property is not virtual, lazy loading has been turned off and thus you have 2 options left to load them.
Option 1: Eager Loading
dbContext.AuthorityUsers.Include(x => x.Authorities).SingleOrDefault(x => x.UserID == 65);
Note: Include is an extension method in the System.Data.Entity namespace so make sure you are using that namespace.
Option 2: Explicit Loading
var users = dbContext.AuthorityUsers.SingleOrDefault(x => x.UserID == 65);
dbContext.Entry(users).Collection(p => p.Authorities).Load();
Please see this article for more details.

If you followed the Entity Framework Code-First conventions you wouldn't have this problem.
If you really need to use non-conventional names for your tables and your primary keys, then indeed your two ModelBuilder statements for AuthorityUser and Authority will do what you want.
However, to make your many-to-many relationship easier, reconsider your method, and make your life easier by following the entity-framework conventions for many-to-many relation
In your case this would lead to two changes:
Make AuthorityUser.Authorities virtual
Let your classes represent your tables: let it be simple POCOs: no HashSet, no Constructor.
The reason to make your table classes simple POCOs, is because the class represents a table in a database. This table has no HashSet, and if you don't need it, why limit yourself to a HashSet? (See later)
In your case the proper many-to-many without the need tell the model builder that you configured a many-to-many would be:
class AuthorityUser
{
// Primary Key (reconsider: Id)
public int UserID { get; set; }
// an AuthorityUser belongs to zero or more Authorities (many-to-many)
public virtual ICollection<Authority> Authorities { get; set; }
... // other properties
}
class Authority
{
// primary key (reconsider: Id)
public int AID {get; set;}
// an Authority has zero or more AuthorityUsers (many-to-many)
public virtual ICollection<AuthorityUser> AuthorityUsers { get; set; }
... // other users
}
class MyDbContext : DbContext
{
public DbSet<AuthorityUser> AuthorityUsers {get; set;}
public DbSet<Authority> Authorities {get; set;}
}
You already understood that you need some Model Building to inform entity framework about your non-conventional primary keys and table names.
But removing the HashSet and declaring both ICollections in the many-to-many is enough for entity framework to understand that a many-to-many is intended. You don't need to do some model building for this. Enityt Framework will create a junction table and use it whenever needed.
When using the many-to-many you won't do a join with the junction table. Instead you think in collections:
Give me all AuthorityUsers that have xxx with their Authorities that have yyy
var result = dbContext.AuthorityUsers
.Where(authorityUser => xxx)
.Select(authorityUser => new
{
// take only the properties from authorityuser you'll need:
UserId = authorityUser.UserId,
GUID = authorityUser.GUID,
// take all authorities from this authorityUser that have yyy
Authorities = authorityUser.Authorities
.Where(authority => yyy)
.Select(authority => new
{
// take only the authority properties you'll use:
AID = authority.AID,
...
})
.ToList(),
});
}
Entity Framework knows that this needs two joins with the junction table, and perform the proper SQL statement for you.
The query: give me all Authorities that ... with all their AuthorityUsers which ... is similar.
Is your hashset needed?
No, in all your queries, entity framework will replace the HashSet by its own virtual ICollection<...>.
Your HashSet would only be useful if you'd add a new Authority with its AuthorityUsers. Without HashSet this would be like:
Authority addedAuthority = myDbContext.Authorieties.Add(new Authority()
{
GUID = ...
... // other properties
// this Authority has the following AuthorityUsers:
AuthorityUsers = new List<AuthorityUsers>()
{
new AuthorityUser() {...},
new AuthorityUser() {...},
...
},
});
Instead of a List you couls assign any ICollection, like an array, or even from a Dictionary:
Dictionary<int, AuthorityUser> authorityUsers = ...
Authority addedAuthority = myDbContext.Authorieties.Add(new Authority()
{
...
// this Authority has the following AuthorityUsers:
AuthorityUsers = authorityUsers.Values,
});
So you see that removing the HashSet give you more freedom to provide the ICollection: Better reusability. Less code, which makes it better understandable when someone else needs to maintain it. Besides it is a waste of processing power to create a HashSet that is most of the time not used.

Related

Accessing related tables with [ForeignKey] without actually having a foreign key in the DB, while using EF Core

I'm reverse-engineering a third-party database that I can't change. There are no foreign keys, but FK relationship have been enforced by the application that was using it, so the data is OK.
I've run scaffolding of tables via Scaffold-DbContext and got classes created. Now due to lack of FKs I have a problem. Here are simplified version of generated classes:
public class Person
{
public int EmployeeType {get; set; }
// other fields
}
public class EmployeeType
{
public int EmployeeType { get; set; } // this is the key in this table
// other fields
}
So an Employee has a type assigned to it, where types are stored in a lookup table. And now I'd like to query employee together with their type. So I'm trying:
public class Person
{
public int EmployeeType {get; set; }
[ForeignKey(nameof(EmployeeType))]
public EmployeeType EmployeeTypeReference { get; set; }
// other fields
}
var people = dbContext.Person
.Include(p => p.EmployeeTypeReference)
.Where(p => p.EmployeeType != null)
.Take(10)
.ToList();
var k = people.Select(c => new { c.EmployeeType, c.EmployeeTypeReference}).ToList();
And then I see this:
I.e. the related object is not retrieved.
I've also tried this:
modelBuilder.Entity<Person>(entity =>
{
entity.HasOne<EmployType>(e => e.EmployeeTypeReference)
.WithMany(t => t.People);
}
But the result is the same.
Anything else I can try doing to get the related object through with .Include?
Problem is with the user as per usual. However some people might be caught out by this as well, so I'll spell it out.
Issue was with .Include(). I was using EF6 version of from namespace System.Data.Entity. But I needed EF Core version that came from Microsoft.EntityFrameworkCore.
That was brought by dual-running EF6 (because dependencies) and EF Core in the same project.

How to make two Self-referencing one to many relation Code first [duplicate]

This question already has an answer here:
Entity Framework Code First: how to map multiple self-referencing many-to-many relationships
(1 answer)
Closed 5 years ago.
I have the following problem, I have a "local" entity which has positive local and negative locals (yes it is strange but it is), then how can you do this on Entity Framework Coder First, Fluent Api I leave you an image to explain me better
model diagram
public class Local
{
public int Id { get; set; }
public string Number { get; set; }
public virtual ICollection<Local> PositiveLocals { get; set; }
public virtual ICollection<Local> NegativeLocals { get; set; }
}
Due to the two collection navigation properties, by convention EF will map your model to many-to-many relationship with implicit junction table. So to create two one-to-many relationships you need to use the following fluent configuration:
modelBuilder.Entity<Local>()
.HasMany(e => e.PositiveLocals)
.WithOptional()
.Map(m => m.MapKey("positive_local_id"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<Local>()
.HasMany(e => e.NegativeLocals)
.WithOptional()
.Map(m => m.MapKey("negative_negative_id"))
.WillCascadeOnDelete(false);
Some things to note:
First, since your model have no inverse reference navigation properties, you have to use the parameterless overload of HasOptional / HasRequired.
Second, in this particular case you need to make the relationship optional (use HasOptional) because otherwise you won't be able to create Local record at all.
Third, you need to turn cascade delete off in order to avoid multiple cascade paths issue. It doesn't need to be turned off for both relationships as I did, but at least for the one of them. In either case you need to perform some action (deleting recursively the related PositiveLocals and NegativeLocals records) by hand before deleting a Local record.
Well, you have your entity, now all you need is a context, so it would look something like this:
using System.Data.Entity;
public class Context : DbContext
{
public DbSet<Local> Locals { get; set; }
public Context() : base("ConnectionStringKeyName") { }
}
Then you'd use it like this:
var positive = new Local
{
Id = 1,
Number = "One"
};
var negative = new Local
{
Id = -1,
Number = "Minus One"
};
negative.PositiveLocals.Add(positive);
positive.NegativeLocals.Add(negative);
using (var context = new Context())
{
context.Locals.Add(positive);
context.Locals.Add(negative);
context.SaveChanges();
}

EF6 Many to many Insert

My problem is to save existing objects that are part of a many-to-many relationship in a code-first database with EF6.
I am receiving objects from a web service and they look (simplified) like:
public class Car
{
[DataMember]
public virtual string CarId { get; set; }
[DataMember]
public virtual ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
[DataMember]
public virtual string ContractId { get; set; }
[DataMember]
public virtual ICollection<Car> Cars { get; set; }
}
I have a code-first database and set the relationship with:
modelBuilder.Entity<Contract>().HasKey(t => new {t.ContractId});
modelBuilder.Entity<Car>().HasKey(t => new {t.CarId})
.HasMany(c => c.Contracts)
.WithMany(c => c.Cars)
.Map(x =>
{
x.ToTable("CarContracts");
x.MapLeftKey("CarId");
x.MapRightKey("ContractId");
});
When I get a list of cars I can save the first car and EF creates the relation table and the contracts successfully. On the second car the save fails saying "constraint failed UNIQUE constraint failed: Contracts.ContractId", since I'm trying to insert a Contract with the same Id as an already existing Contract.
The solutions I found to this problem is to set the Contract in the dbContext to Attached:
foreach (var contract in car.Contracts)
{
context.Contract.Attach(contract);
}
This throws the same exception as the previous save. When I try to modify the Contract-list of the second car I get a NotSupportedException, the only solution I can think of is recreating the car objects and attach the same Contract-object to them, which seems unnecessarily complicated.
Is there any way to tell EF that my two different Contract-objects actually are the same?
Is there any way to tell EF that my two different Contract-objects
actually are the same?
The only way is they actually are the same object. If they are different instances even having all the same data EF won't recognize them as same entity.
So the only solution is to replace all same id contract instances by a single one.
Or simpler: create an explicit entity representing the N:N relation and then simply build a list to insert as follows:
var toInsert = new List<CarContract>();
foreach(var car in cars)
{
toInsert.AddRange(car.Select(x=>new CarContract {CarId=car.Id,ContractId=x.Id}));
}

Implementing Zero Or One to Zero Or One relationship in EF Code first by Fluent API

I have two POCO classes:
Order Class:
public class Order
{
public int Id { get; set; }
public int? QuotationId { get; set; }
public virtual Quotation Quotation { get; set; }
....
}
Quotation Class:
public class Quotation
{
public int Id { get; set; }
public virtual Order Order { get; set; }
....
}
Each Order may be made from one or zero quotation, and
each quotation may cause an order.
So I have an "one or zero" to "one or zero" relation, how can I implement this, in EF Code first by Fluent API?
By changing pocos to:
public class Order
{
public int OrderId { get; set; }
public virtual Quotation Quotation { get; set; }
}
public class Quotation
{
public int QuotationId { get; set; }
public virtual Order Order { get; set; }
}
and using these mapping files:
public class OrderMap : EntityTypeConfiguration<Order>
{
public OrderMap()
{
this.HasOptional(x => x.Quotation)
.WithOptionalPrincipal()
.Map(x => x.MapKey("OrderId"));
}
}
public class QuotationMap : EntityTypeConfiguration<Quotation>
{
public QuotationMap()
{
this.HasOptional(x => x.Order)
.WithOptionalPrincipal()
.Map(x => x.MapKey("QuotationId"));
}
}
we will have this DB(that means 0..1-0..1):
with special thanks to (Vahid Nasiri)
#Masoud's procedure was:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal()
.Map(o => o.MapKey("OrderId"));
modelBuilder.Entity<Quotation>()
.HasOptional(o => o.Order)
.WithOptionalPrincipal()
.Map(o => o.MapKey("QuotationId"));
It gives:
By changing the code to:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(o=> o.Order);
It gives:
See http://msdn.microsoft.com/en-us/data/jj591620 EF Relationships
An excellent Book
http://my.safaribooksonline.com/book/-/9781449317867
Here is a post from developer from Dec 2010. But still relevant
http://social.msdn.microsoft.com/Forums/uk/adonetefx/thread/aed3b3f5-c150-4131-a686-1bf547a68804
The above article is a nice summary or the possible combinations here.
A solution where dependant Table has key from Primary table is possible.
If you Want Independent Keys where both are Principals in a PK/FK scenario, i dont think you can do it in Code first with Fluent API. If they share a Key, You are OK.
1:1 optional assumes the dependent uses the key from Primary.
But since you need to save one of the tables before the other. You can check one of the Foreign Keys with code. OR add teh second Foreign to Database after Code first has created it.
You will get close. But EF will complain about conflicting Foreign keys if you want both to be Foreign keys. Essentially the A depends on B depends A EF doesnt like, even if the columns are nullable and technically possible on the DB.
Here use this test program to try it. Just comment in an out the Fluent API stuff to try some options.
I could NOT get EF5.0 to work with INDEPENDENT PK/FK 0:1 to 0:1
But of course there are reasonable compromises as discussed.
using System.Data.Entity;
using System.Linq;
namespace EF_DEMO
{
class Program
{
static void Main(string[] args) {
var ctx = new DemoContext();
var ord = ctx.Orders.FirstOrDefault();
//. DB should be there now...
}
}
public class Order
{
public int Id {get;set;}
public string Code {get;set;}
public int? QuotationId { get; set; } //optional since it is nullable
public virtual Quotation Quotation { get; set; }
//....
}
public class Quotation
{
public int Id {get;set;}
public string Code{get;set;}
// public int? OrderId { get; set; } //optional since it is nullable
public virtual Order Order { get; set; }
//...
}
public class DemoContext : DbContext
{
static DemoContext()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DemoContext>());
}
public DemoContext()
: base("Name=Demo") { }
public DbSet<Order> Orders { get; set; }
public DbSet<Quotation> Quotations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasKey(t => t.Id)
.HasOptional(t => t.Quotation)
.WithOptionalPrincipal(d => d.Order)
.Map(t => t.MapKey("OrderId")); // declaring here via MAP means NOT declared in POCO
modelBuilder.Entity<Quotation>().HasKey(t => t.Id)
.HasOptional(q => q.Order)
// .WithOptionalPrincipal(p => p.Quotation) //as both Principals
// .WithOptionalDependent(p => p.Quotation) // as the dependent
// .Map(t => t.MapKey("QuotationId")); done in POCO.
;
}
}
}
Adapted from this answer, try this.
First, fix your classes:
public class Order
{
public int Id {get; set;}
public virtual Quotation Quotation { get; set; }
// other properties
}
public class Quotation
{
public int Id {get; set;}
public virtual Order Order { get; set; }
// other properties
}
Then use the fluent API like that:
modelBuilder.Entity<Quotation>()
.HasOptional(quote => quote.Order)
.WithRequired(order=> order.Quotation);
Basically, for 1:1 or [0/1]:[0/1] relationships, EF needs the primary keys to be shared.
public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public virtual Instructor Instructor { get; set; }
}
The Key Attribute
There's a one-to-zero-or-one relationship between the Instructor and the OfficeAssignment entities. An office assignment only exists in relation to the instructor it's assigned to, and therefore its primary key is also its foreign key to the Instructor entity. But the Entity Framework can't automatically recognize InstructorID as the primary key of this entity because its name doesn't follow the ID or classnameID naming convention. Therefore, the Key attribute is used to identify it as the key:
https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
using DataAnnotations:
public class Order
{
[Key]
public int Id {get; set;}
public virtual Quotation Quotation { get; set; }
}
public class Quotation
{
[Key, ForeignKey(nameof(Order))]
public int Id {get; set;}
public virtual Order Order { get; set; }
}
(Note that this is using EF 6.4.4.)
It's fairly straightforward to specify, as long as you don't want foreign key properties:
modelBuilder
.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(q => q.Order);
modelBuilder
.Entity<Quotation>()
.HasOptional(q => q.Order)
.WithOptionalDependent(o => o.Quotation);
Notice here the usage of both WithOptionalPrincipal and WithOptionalDependent. This should give you a single foreign key column on the dependent side (Quotation in the example), but with no foreign key properties. If you want the foreign key on the other side, switch "Dependent" and "Principal" around.
(Note that it is not necessary to have both definitions above; WithOptionalDependent will imply the other side is the principal and vice-versa, so you can use only one of them if you wanted, but I find specifying the relationships from both sides helps prevent errors by double declaring things; any conflict will result in a model error to let you know you missed something.)
While there is an index on the foreign key column, the index does not have a unique constraint. While it is possible to add your own unique constraint (which would require a Key IS NOT NULL filter), it doesn't seem to work and you will get exceptions when updating relationships in some cases. I think this is related to the "swapping problem" where EF will perform its updates in separate queries, so enforcing uniqueness would prevent EF from "moving" a key in two steps.
EF seems to handle the association itself internally, without a unique DB constraint:
On either side, assigning an already used reference results in the other usage of the reference being removed automatically. (So if it is already the case that A1 <=> B1 when you opened the context, and then you write A1 => B2, then A1 <=> B1 is removed and A1 <=> B2 is added, regardless of which side you're on.)
If you try to create a duplicate key by assigning the same reference more than once, EF will throw an exception saying "multiplicity constraint violation". (So in the same context, you wrote both A1 => B1 and A2 => B1, or some similar conflicting mapping.)
If you update the DB manually to create a duplicate key situation, when EF encounters this it will throw an exception saying "A relationship multiplicity constraint violation occurred...this is a non-recoverable error."
It does not seem possible in EF6 to map a property to the foreign key column (at least with Fluent API). Attempting to do so results in a non-unique column name exception since it tries to use the same name for both the property and the association separately.
Note also that it is technically incorrect to have two foreign keys (ie: one on both sides). Such an arrangement would actually be two 0..1 to 0..1 associations since there would be nothing to say that keys on both ends should match. This could maybe work if you enforce the relationship some other way, through the UI and/or possibly a database constraint of some kind.
I also notice that there may be a misunderstanding/miscommunication of exactly what a 0..1 to 0..1 association is. What this means, from my understanding and the way EF seems to consider it as well, is that it is a 1 to 1 association that is optional on both sides. So, you can have objects on either side with no relationship. (Whereas a 1 to 0..1 assocation, objects on one side could exist without a relationship, but objects on the other side would always need an object to relate to.)
But 0..1 to 0..1 does not mean that you can have the association travel in one direction and not the other. If A1 => B1, then B1 => A1 (A1 <=> B1). You cannot assign B1 to A1 without also making A1 relate to B1. This is why it is possible for this association to use only a single foreign key. I think some people may be trying to have an association in which this is not true (A1 relates to B1 but B1 does not relate to A1). But that is really not one association but two 0..1 to 0..1 associations.

Mapping references to companion objects with fluent-nhibernate

I've got the following basic domain model for my MVC website accounts:
public class Account
{
public Account()
{
Details = new AccountDetails( this );
Logon = new LogonDetails(this);
}
public virtual int Id { get; private set; }
public virtual AccountDetails Details { get; set; }
public virtual LogonDetails Logon { get; set; }
...
}
public class AccountDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime Created { get; set; }
...
}
public class LogonDetails
{
// Primary Key
public virtual Account Account { get; set; }
public virtual DateTime? LastLogon { get; set; }
...
}
Both AccountDetails and LogonDetails use a mapping like this:
public class AccountDetailsOverride : IAutoMappingOverride<AccountDetails>
{
public void Override( AutoMap<AccountDetails> mapping )
{
mapping
.UseCompositeId()
.WithKeyReference( x => x.Account, "AccountId" );
mapping.IgnoreProperty( x => x.Account );
}
}
I've split the account details and logon details into separate models since I rarely need that information, whereas I need the userid and name for many site operations and authorization. I want the Details and Logon properties to be lazy-loaded only when needed. With my current mapping attempts I can get one of two behaviors:
# 1 Create table and load successfully, cannot save
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.WithColumns( x => x.Account.Id )
.Cascade.All();
mapping
.References( x => x.Logon )
.WithColumns( x => x.Account.Id )
.Cascade.All();
}
}
The tables are generated as expected. Existing data loads correctly into the model, but I can't save. Instead I get an index out of range exception. Presumably because Account.Details and Account.Logon are both trying to use the same db field for their reference (The Account.Id itself).
#2 Table includes extra fields, does not save properly
Using this mapping:
public class AutoOverride : IAutoMappingOverride<Account>
{
public void Override( AutoMap<Account> mapping )
{
mapping.LazyLoad();
mapping
.References( x => x.Details )
.Cascade.All();
mapping
.References( x => x.Logon )
.Cascade.All();
}
}
I get a table with a separate field for Details_id and Logon_id but they are null since the value of Details.Account.Id is null when the Account is persisted. So, attempting to Session.Get the account results in Details and Logon being null. If I save Account twice, the table is updated correctly and I can load it.
Help...
There must be a way of mapping this hierarchy and I'm missing something simple. Is there a way to help nhibernate pick the proper field (to solve #1) or to have it update dependent fields automatically after save (to solve#2)?
Thanks for any insight you folks can provide.
If I'm understanding your model and desired behavior, what you have is actually a one-to-one relationship between Account and AccountDetails and between Account and LogonDetails. References creates a many-to-one relationship, so that could be your problem; try HasOne instead.
That said, for this and other reasons, I avoid one-to-ones unless absolutely necessary. There may be more than what you're showing, but is it worth the headache and ugly model to avoid loading two DateTime fields?
Finally, and this is somewhat speculation since I have not tested this functionality, NHibernate 2.1's (which FNH has switched to as supported version) mapping XML schema defines a lazy attribute for property elements. The 1.0 release of FNH (should be in the next week or two) will support setting this attribute. As I said, I have not tested it but it would seem that this would allow you to lazy load individual properties which is exactly what you want.

Categories