I'm trying to remove a child entity from a parent collection navigation property. There is a one-to-many relationship set up b/t parent and child. Once I remove the child, I want the database to remove the assoc. child record from the database rather than orphaning that record by nullifying the foreign key.
Is there any way to do this without having to explicitly delete the child via the child DbSet in the DBContext?
I've seen other posts related to this topic, but I thought I'd distill the code down to a simpler test case:
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using NUnit.Framework;
namespace basic_tests
{
[TestFixture]
public class OneToManyTests
{
#region Setup/Teardown
[SetUp]
public void SetUp()
{
_context = new Context();
Database.SetInitializer(new DataInitializer());
}
#endregion
private Context _context;
[Test]
public void CanRemoveChildThroughParent()
{
/**
this throws : "System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for
details.System.Data.UpdateException : A relationship from the 'Child_MyParent' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'Child_MyParent_Source' must also in the 'Deleted' state.
**/
var parent = _context.Parents.FirstOrDefault();
var firstChild = parent.MyChildren.FirstOrDefault();
parent.MyChildren.Remove(firstChild);
_context.SaveChanges();
var parentRefresh = new Context().Parents.FirstOrDefault();
Assert.AreEqual(1, parentRefresh.MyChildren.Count);
var childrenCount = new Context().Children.Count();
Assert.AreEqual(1, childrenCount);
}
}
public class Parent
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Child> MyChildren { get; set; }
}
public class Child
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Parent MyParent { get; set; }
}
public class Context : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(c => c.MyParent)
.WithMany(p => p.MyChildren)
.WillCascadeOnDelete(true);
}
}
public class DataInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
for (var i = 0; i < 2; i++)
{
context.Children.Add(new Child
{
Name = "child" + i
});
}
var parent = new Parent { Name = "parent", MyChildren = context.Children.Local.ToList() };
context.Parents.Add(parent);
base.Seed(context);
}
}
}
Is there any way to do this without having to explicitly delete the
child via the child DbSet in the DBContext?
In your model: No, there is no other way. You have to call:
var parent = _context.Parents.FirstOrDefault();
var firstChild = parent.MyChildren.FirstOrDefault();
_context.Children.Remove(firstChild);
_context.SaveChanges();
I recently have learned that there is one exception which causes an automatic delete in the database when you remove the child from the parent collection. That's the so-called Identifying relationship which requires that the foreign key property in the child refering to the parent must be part of the (composite) primary key of the child:
public class Child
{
[Key, Column(Order = 0)]
public virtual int Id { get; set; }
[Key, ForeignKey("MyParent"), Column(Order = 1)]
public virtual int MyParentId { get; set; }
public virtual string Name { get; set; }
public virtual Parent MyParent { get; set; }
}
In this case your code would indeed delete the child from the database. It is explained here (last section at the bottom): http://msdn.microsoft.com/en-us/library/ee373856.aspx
var parent = context.Parents.Include(p=>p.Children).Where(p=>p.ParentId == 1);
foreach(Children child in parent.Children.ToList())
{
context.Entry(child).State = EntityState.Deleted;
}
context.Entry(parent).State = EntityState.Deleted;
context.SaveChanges();
For more info go here: https://stackoverflow.com/a/9571108/1241400
EDIT: Why less generic
public void DeleteMany<E>(IQueryable<E> entitiesToDelete) where E : class
{
foreach (var entity in entitiesToDelete)
{
DataContext.Entry(entity).State = System.Data.EntityState.Deleted;
}
}
and you'll just call it
var childrenToDelete = someRepository.FindChildrenByParentId<Children>(ParentId);
someRepository.DeleteMany<Children>(childrenToDelete);
Related
1) i have Enttiy Class In which have three tables whose code is below given
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Web;
namespace GridWithInlineEdit.Models
{
#region ENTITY BLOCK
[Table("TBLUSER", Schema = "orient")]
public class Users
{
public Users()
{
UsersDetailCollection = new List<Usersdetail>();
}
//
[Key, Column("UID", TypeName = "INT")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Uid { get; set; }
[Required]
[StringLength(50, MinimumLength = 10, ErrorMessage = "Please Enter {0} Upto 50 Characters!")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed here!")]
[Column("FNAME", TypeName = "nvarchar")]
public string Fname { get; set; }
[Required]
[StringLength(100, MinimumLength = 10, ErrorMessage = "Please Enter {0} Upto 50 Characters!")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed here!")]
[Column("LNAME", TypeName = "nvarchar")]
public string Lname { get; set; }
public ICollection<Usersdetail> UsersDetailCollection { get; set; }
}
[Table("TBLUSERDETAIL", Schema = "orient")]
public class Usersdetail
{
public Usersdetail()
{
CountryCollection = new List<Countries>();
}
[Key, Column("ID", TypeName = "INT")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[StringLength(100)]
[Column("EMAIL", TypeName = "VARCHAR")]
public String Email { get; set; }
[StringLength(11)]
[Column("PHONE", TypeName = "VARCHAR")]
public String Phone { get; set; }
[Required]
public int? UserId { get; set; }
[ForeignKey("UserId"), Column("UID", TypeName = "INT")]
public virtual Users Users { get; set; }
[Required]
public int? CountryId { get; set; }
[ForeignKey("CountryId"), Column("CID", TypeName = "INT")]
public virtual Countries Countries { get; set; }
public ICollection<Countries> CountryCollection { get; set; }
}
[Table("TBLCOUNTRY", Schema = "orient")]
public class Countries
{
[Key, Column("CID", TypeName = "INT")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Cid { get; set; }
[StringLength(50)]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed here!")]
[Column("CNAME", TypeName = "VARCHAR")]
public String Cname { get; set; }
}
#endregion
#region ENTITY MAPPING BLOCK
public class UserMap : EntityTypeConfiguration<Users>
{
#region Constructors and Destructors
internal UserMap()
{
// Primary Key
HasKey(t => t.Uid);
Property(p => p.Uid).HasColumnName("UID").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
// Table & Column Mappings
ToTable("TBLUSER");
Property(t => t.Fname).HasColumnName("FNAME").HasMaxLength(50);
Property(t => t.Lname).HasColumnName("LNAME").HasMaxLength(50);
}
#endregion
}
public class UserDetailMap : EntityTypeConfiguration<Usersdetail>
{
#region Constructors and Destructors
internal UserDetailMap()
{
// Primary Key
HasKey(t => t.ID);
HasKey(t => t.UserId);
HasKey(t => t.CountryId);
// Properties
Property(t => t.Email).HasMaxLength(100);
Property(t => t.Phone).HasMaxLength(11);
// Column Mappings
ToTable("TBLUSERDETAIL");
Property(t => t.ID).HasColumnName("ID").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
Property(t => t.Email).HasColumnName("EMAIL");
Property(t => t.Phone).HasColumnName("PHONE");
Property(t => t.UserId).HasColumnName("UID");
Property(t => t.CountryId).HasColumnName("CID");
// Relationships
HasOptional(t => t.Users).WithMany().HasForeignKey(d => d.UserId);
HasOptional(t => t.Countries).WithMany().HasForeignKey(d => d.CountryId);
}
#endregion
}
public class CountryMap : EntityTypeConfiguration<Countries>
{
#region Constructors and Destructors
internal CountryMap()
{
// Primary Key
HasKey(t => t.Cid);
// Properties
Property(t => t.Cname).HasMaxLength(50);
// Column Mappings
Property(t => t.Cid).HasColumnName("CID").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
Property(t => t.Cname).HasColumnName("CNAME");
}
#endregion
}
#endregion
}
2) Second is Init Class in which have connection string custom with sqlserver 2005
Connection string class code is below given
using System;
using System.Data.Common;
using System.Data.Entity.Infrastructure;
namespace GridWithInlineEdit.Models
{
public static class Constants
{
public static string ConnectionString
{
get { return GetDecryptedConnectionString(); }
}
private static string GetDecryptedConnectionString()
{
return #"Data Source=193.193.193.254;Initial Catalog=EFCFUsersdb;;USER ID=sa;PASSWORD=123;Persist Security Info=True";
}
}
class EncryptedIDbConnectionFactory : IDbConnectionFactory
{
#region Private Fields
IDbConnectionFactory _connectionFactory;
#endregion
#region Constructors
public EncryptedIDbConnectionFactory(IDbConnectionFactory dbConnectionFactory)
{
if (dbConnectionFactory == null)
{
throw new ArgumentNullException("dbConnectionFactory can not be null");
}
_connectionFactory = dbConnectionFactory;
}
#endregion
#region IDbConnectionFactory implementation
public DbConnection CreateConnection(string nameOrConnectionString)
{
//decryption of connection string
string decryptedConnectionString =
GetDecryptedConnectionString(nameOrConnectionString);
return _connectionFactory.CreateConnection(decryptedConnectionString);
}
#endregion
#region Private Methods
private string GetDecryptedConnectionString(string nameOrConnectionString)
{
//use some encryption library to decrypt
return nameOrConnectionString;
}
#endregion
}
}
and Init class is below given
using System.Data.Entity;
namespace GridWithInlineEdit.Models
{
public class Init : DropCreateDatabaseIfModelChanges<SampleContext>
{
protected override void Seed(SampleContext context)
{
base.Seed(context);
//context.Locations.Add(new Location() { LocationName = "Khanna, LDH" });
//context.Sessions.Add(new Session() { SessionName = "Entity Framework" });
context.SaveChanges();
}
}
}
3 ) this is samplecontext class code
using System.Data.Entity;
using System.Data.SqlClient;
namespace GridWithInlineEdit.Models
{
public class SampleContext : DbContext
{
public SampleContext()
: base(new SqlConnection(Constants.ConnectionString) ,false)
//Data Source=193.193.193.254;Initial Catalog=EFCFUsersdb;Persist Security Info=True;User ID=sa;Password=123
{
Configuration.ProxyCreationEnabled = true;
Configuration.AutoDetectChangesEnabled = true;
}
public DbSet<Users> User { get; set; }
public DbSet<Usersdetail> UserDetail { get; set; }
public DbSet<Countries> Country { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new UserMap());
modelBuilder.Configurations.Add(new UserDetailMap());
modelBuilder.Configurations.Add(new CountryMap());
}
}
}
4) in Global file of application have below code
using System.Data.Entity;
using System.Web.Mvc;
using System.Web.Routing;
using GridWithInlineEdit.Models;
namespace GridWithInlineEdit
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "User", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
//Database.DefaultConnectionFactory = new EncryptedIDbConnectionFactory(Database.DefaultConnectionFactory);
Database.SetInitializer<SampleContext>(new Init());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
}
In My Home Controller have below given code
> 1. using System.Web.Mvc; using GridWithInlineEdit.Models;
>
> namespace GridWithInlineEdit.Controllers {
> public class UserController : Controller
> {
>
>
> public ActionResult Index()
> {
> var db = new SampleContext();
> return View();
> }
>
> [HttpPost]
> public ActionResult Create()
> {
>
> //return View();
> return null;
> }
>
> } }
Now the problem is this whenever run my application database is not created and show exception server version property when i see through add watch in _db object by putting breakpoint on index action result.
pls pls resolve or give solution i have tried for this many time but i am confused why this happening
I know you have in the comments above that you applied a Windows Server patch and got it working, but with looking at your project, I had some observations:
There is nothing in your "Create" method in your controller for adding
new records
There is no "Read" method for returning data to anything
that might want to read data, such as a Kendo grid.
There is no "Delete" method for removing data records.
I'm not sure what the UserMap, DetailMap, and CountryMap are used for, along with your "RegisterRoutes", etc. on Application_Start -- these seem like a severe case of overcomplicating things, to me.
No View (presentation layer) code posted.
Maybe you fixed the Create, Read, and Delete routines later when you got it working, and maybe the UserMap, DetailMap, CountryMap, and Init all were for something, but be damned if I could make heads-or-tails of what you were doing. I saw no View code, grid or otherwise, that you were pulling your data into. When asking for help, just saying, context is everything.
While you would need to obtain and install Telerik's Kendo UI MVC components, it might be worth it to you to look at this blog for setting up the entity framework in MVC and preparing a grid for receiving data:
http://docs.telerik.com/kendo-ui/getting-started/using-kendo-with/aspnet-mvc/helpers/grid/ajax-binding
Given the following simplified model:
public class Account
{
public Account()
{
Id = Guid.NewGuid();
ContactCard = new ContactCard();
}
//[ForeignKey("ContactCard")]
public Guid Id { get; set; }
public string Name { get; set; }
public string Number { get; set; }
public ContactCard ContactCard { get; set; }
}
public class ContactCard
{
public ContactCard()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public Account Account { get; set; }
}
public class MightDbContext: DbContext
{
public DbSet<Account> Accounts { get; set; }
public DbSet<ContactCard> ContactCards { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>().HasRequired(x => x.ContactCard).WithOptional(x => x.Account);
}
}
public class MightDbInitializer : DropCreateDatabaseIfModelChanges<MightDbContext>
{
protected override void Seed(MightDbContext context)
{
var accounts = new List<Account>
{
new Account()
{
Name = "Acme Corporation Pty. Ltd.",
Number = "001ABC"
},
new Account()
{
Name = "Three Little Pigs Pty. Ltd.",
Number = "002DEF"
}
};
accounts.ForEach(c => context.Accounts.Add(c));
}
}
And the following simple console program to iterate the contents of the Accounts and ContactCards collections:
static void Main(string[] args)
{
Database.SetInitializer<MightDbContext>(new MightDbInitializer());
using (var context = new MightDbContext())
{
foreach (Account c in context.Accounts)
{
Console.WriteLine(c.ContactCard.Id);
}
var contactCards = context.ContactCards.ToList(); /* ERROR OCCURS HERE */
foreach (ContactCard a in contactCards)
{
Console.WriteLine(a.Id);
}
}
Console.Read();
}
Why do I get the following error as soon as I try to access the ContactCards collection:
Multiplicity constraint violated. The role 'Account_ContactCard_Source' of the relationship 'InvestAdmin.Might.DataAccess.Account_ContactCard' has multiplicity 1 or 0..1.
When I look at the data that has actually been stored in the database tables all seems to be correct. In fact here is that data:
Accounts:
Id Name Number
ab711bad-1b32-42ca-b68b-12f7be831bd8 Acme Corporation Pty. Ltd. 001ABC
dc20a1dd-0ed4-461d-bc9c-04a85b555740 Three Little Pigs Pty. Ltd. 002DEF
ContactCards:
Id
dc20a1dd-0ed4-461d-bc9c-04a85b555740
ab711bad-1b32-42ca-b68b-12f7be831bd8
And for completeness here is the Account_ContactCard foreign key constraint as defined in the database:
-- Script Date: 06/12/2011 7:00 AM - Generated by ExportSqlCe version 3.5.1.7
ALTER TABLE [Accounts] ADD CONSTRAINT [Account_ContactCard] FOREIGN KEY ([Id]) REFERENCES [ContactCards]([Id]) ON DELETE NO ACTION ON UPDATE NO ACTION;
I have been reading all I can on defining one to one relationships in Code First and have tried many different configurations. All end up back at the same problem.
I found that I could resolve this problem by moving the creation of the associated ContactCard from the Account constructor into the actual creation of the Account objects in the Seed method of the DBInitializer. So it appears that it was not (directly) a problem with the One to One relationship.
Follow the methods below:
public class User
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
}
public class DataContext : DbContext
{
DbSet<User> Users { get; set; }
}
public class Repository
{
DataContext db = new DataContext();
public User Attach1(User entity)
{
var ent = db.Entry<User>(entity);
ent.State = EntityState.Modified;
if (db.SaveChanges() > 0)
return ent.Entity;
return null;
}
public User Attach2(User entity)
{
return db.Users.Attach(entity);
}
}
Is there any difference between Attach1 and Attach2?
Your Attach1 and Attach2 methods perform different things and it is not clear what you expect to do in these methods. When you attach an entity to EF it will be added to the context in Unchanged state. If you modify the entity after attaching, then EF will track those changes and the entity will be in Modified state.
Attach1
This method will attach an entity and mark it as modified. So the subsequent SaveChanges() will update all the properties of the entity. Calling SaveChanges() inside the Attach method is not recommended since it does more than attaching.
Attach2
This method will attach the entity as Unchanged.
How can I create+persist+have-a-proxy-to a new instance of a code-first poco using only navigation property collections? In the bellow code, I show how you might want to do this, if using member functions to create POCOs that then create POCOs. You don't have a DbContext, but if you create an object and persist it using DbSet.Add, the returned object isn't a proxy, so you can't in turn use its DbSet.Add to add a different sub-object.
In this code, if you call MailingList.AddIncomingMessage("my message"), you get an exception at the "OOPS" comment, because the created msg isn't a proxy and thus its Message.doodads property is null.
class Doodad {
public int ID { get; set; }
public string doodad { get; set; };
}
class Message {
public int ID { get; set; }
public virtual MailingList mailingList { get; set; }
public virtual ICollection<Doodad> doodads { get; set; }
public string text { get; set; }
public void GetDoodadCreateIfNeeded(string doodad) {
try {
// won't be found since we just created this Message
return this.doodads.First(d => d.doodad == doodad);
} catch (Exception e) {
Doodad newDoodad = new Doodad() { doodad=doodad };
// OOPS! this.doodads == null, because its not a proxy object
this.doodads.Add(newDoodad);
return newDoodad;
}
}
}
class MailingList {
public int ID { get; set; }
public virtual ICollection<Message> messages { get; set; }
public void AddIncomingMessage(string message) {
var msg = new Message() { text=message };
// we have no Context, because we're in a POCO's member function
this.messages.Add(msg);
var doodad = msg.GetDoodadCreateIfNeeded("bongo drums");
}
}
EDIT: sorry guys, I forgot to put the property accessors and ID in for this simplified case, but I am using them in the actual code.
It has nothing to do with proxies. It is the same as any other code - if you want to use object / collection you must first initialize it! Your fist command:
return this.doodads.First(d => d.doodad == doodad);
doesn't throw exception because it didn't find doodad but because the doodads is null.
What do you need to do? You need to initialize collections before you first use them. You can do it:
Directly in their definition
In entity's constructor
In property getter (lazy initialization) once they are first needed - that would require to change your fields to properties which is btw. correct way to write classes in .NET
In your custom methods you can check if they are null and initialize them
Complementary to the navigation property, you need to have a property that is the Id of the foreign key.
So your MailingList will need to have this property:
[Key] // this attribute is important
public int Id { get; set; }
and you'll have to change the Message classe to have these properties:
public virtual int mailingListId { get; set;
public virtual MailingList mailingList { get; set; }
The { get; set; } property is important, so that it is a property, not just a public attribute.
I have a question about One-To-Many relationship between two tables when there is the link (join) table between.
Example tables:
ChildTable:
ID int NOT NULL PK
Relation int NOT NULL
ParentTable:
ID int NOT NULL PK
Name nvarchar(50) NOT NULL
ParentChildren:
ParentTable_ID int NOT NULL PFK
ChildTable_ID int NOT NULL PFK
Entities:
public class ChildTable
{
public ChildTable()
{
this.ParentTables = new List<ParentTable>();
}
public int ID { get; set; }
public int Relation { get; set; }
public virtual ICollection<ParentTable> ParentTables { get; set; }
}
public class ParentTable
{
public ParentTable()
{
this.ChildTables = new List<ChildTable>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<ChildTable> ChildTables { get; set; }
}
Mapping:
public class ChildTableMap : EntityTypeConfiguration<ChildTable>
{
public ChildTableMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Properties
// Table & Column Mappings
this.ToTable("ChildTable");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Relation).HasColumnName("Relation");
}
}
public class ParentTableMap : EntityTypeConfiguration<ParentTable>
{
public ParentTableMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Properties
this.Property(t => t.Name)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("ParentTable");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Name).HasColumnName("Name");
// Relationships
this.HasMany(t => t.ChildTables)
.WithMany(t => t.ParentTables)
.Map(m =>
{
m.ToTable("ParentChildren");
m.MapLeftKey("ParentTable_ID");
m.MapRightKey("ChildTable_ID");
});
}
}
Context:
public class TestContext : DbContext
{
static TestContext()
{
Database.SetInitializer<TestContext>(null);
}
public DbSet<ChildTable> ChildTables { get; set; }
public DbSet<ParentTable> ParentTables { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Configurations.Add(new ChildTableMap());
modelBuilder.Configurations.Add(new ParentTableMap());
}
}
Code to add the child and parent:
using (var context = new TestContext())
{
var parent = new ParentTable { Name = "Mother Goose" };
var child = new ChildTable { Relation = 1 };
context.ParentTables.Add(parent);
context.ChildTables.Add(child);
parent.ChildTables.Add(child);
context.SaveChanges();
}
And all works as expected, but I really have only 1 parent and many children.
How to do the mapping that includes writing to the link table (with changed ChildTable class that does not have the ICollection of ParentTable)?
It's not possible to define such a mapping. Join tables are only for many-to-many relationships. You have to keep in mind that your database schema (which you cannot change, as you said in the comments to your question) already defines a many-to-many relationship. For example these two entries...
ParentTable_ID: 1 2
ChildTable_ID: 1 1
...are valid entries in the ParentChildren table, they don't violate the PK constraint and they say that child 1 has the two parents 1 and 2.
If your business logic only allows that a child can have only one single parent, the database schema is modeled wrong. You cannot fix this flaw with EF.
A possible workaround might be to ensure in your business logic in your code that the ParentTables collection of your ChildTable entity never has more than one element, perhaps by introducing a not mapped helper property:
public class ChildTable
{
public ChildTable()
{
this.ParentTables = new List<ParentTable>();
}
public int ID { get; set; }
public int Relation { get; set; }
public virtual ICollection<ParentTable> ParentTables { get; set; }
[NotMapped] // or use .Ignore(c => c.ParentTable) in Fluent API
public ParentTable ParentTable
{
get
{
return ParentTables.SingleOrDefault();
// let is crash if there is more than one element in the list
}
set
{
var oldParent = ParentTables.SingleOrDefault();
if (oldParent != value)
{
ParentTables.Clear();
if (value != null)
ParentTables.Add(value);
}
}
}
}
It's only a little help if you only use the ParentTable property. You have to avoid to use the collection and add more than one parent. You cannot make it private because it must be mapped. It's also not safe on the other side of the relationship. You could write: parent1.ChildTables.Add(child1); parent2.ChildTables.Add(child1); which would lead to two parents for child1 in the database. You could catch this by writing helper methods for adding a child on the ParentTable entity which check if the added child doesn't already have a parent other than this parent.