I am strugging to see why entity framework does not allow 1 to 1 relationships in the following context;
ClassA {
public int ID;
[ForeignKey("ClassB")]
public int ClassBID;
public ClassB classB;
}
ClassB {
public int ID;
[ForeignKey("ClassA")]
public int ClassAID;
public ClassA classa;
}
i.e. a one to one relationship whereby I could navigate to either from linq.
The context I have is that I have a Vehicle. Each Vehicle can have a device (which is null if it doesnt). An each device will have an optional vehicle.
If someone could explain why he above is not valid (or supported) and explain how I would get around my issue I would really apreciate it.
Thanks in advance.
You have to decide that one end is non-optional (i.e., 1-to-0..1 is allowed ... 0..1-to-0..1 is not). Once you do, EF supports 1-to-0..1 by forcing the dependent side to not have it's own Key, but to instead define the dependent class' Key to be the same as its ForeignKey (which, if you think about it, makes sense for a relation that's supposed to be 1-to-1):
class A
{
public int Id { get; set; }
public int? BId { get; set; }
public virtual B B { get; set; }
}
class B
{
[Key, ForeignKey("A")]
public int Id { get; set; }
public virtual A A { get; set; }
}
Note, too, the detail that BId is int?: NULL in SQL corresponds to Nullable<> in C#.
If I understand a device can have zero or one vehicles and vice-versa.
In an old DB model one of the two tables (device or vehicles) should have a nullable field that references the other table.
To configure it in EF you have to use data annotation or fluent interface.
Here the code of the model and of the context
public class ClassA
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ClassB ClassB { get; set; }
}
public class ClassB
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ClassA ClassA { get; set; }
}
class Context : DbContext
{
public Context(DbConnection connection)
: base(connection, false)
{ }
public DbSet<ClassA> As { get; set; }
public DbSet<ClassB> Bs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassB>().HasOptional(c => c.ClassA).WithOptionalDependent(c => c.ClassB);
}
}
As you can immagine there are some restrictions on using this model. I.e. the model (POCO model) allow you to have a classA1 referencing a classB1 that references classA2 (same type but different instance of classA1). The DB and EF does not. Here an example with a query dump on how EF works in this case (I think is very interesting!!!)
using (Context context = new Context(connection))
{
ClassA classA;
ClassB classB;
// Very simple behaviour (as expected). You can see the queries after SaveChanges()
classA = new ClassA {Description = "B empty"};
context.As.Add(classA);
classA = new ClassA { Description = "B full", ClassB = new ClassB(){Description = "ClassB full"}};
context.As.Add(classA);
classB = new ClassB { Description = "B empty"};
context.Bs.Add(classB);
context.SaveChanges();
/*
insert into [ClassAs]([Description])
values (#p0);
#p0 = B full
insert into [ClassAs]([Description])
values (#p0);
#p0 = B empty
insert into [ClassBs]([Description], [ClassA_Id])
values (#p0, #p1);
#p0 = ClassB full
#p1 = 1
insert into [ClassBs]([Description], [ClassA_Id])
values (#p0, null);
#p0 = B empty
*/
// Here a new classB references an already referenced classA. But we don't want this!!!
// EF works like we want, the classA is detached from the old classB then attached to the
// new classB. Below you can see the queries
classB = new ClassB { Description = "B full with the wrong A", ClassA = classA};
context.Bs.Add(classB);
/*
update [ClassBs]
set [ClassA_Id] = null
where (([Id] = #p0) and ([ClassA_Id] = #p1))
#p0 = 1
#p1 = 1
insert into [ClassBs]([Description], [ClassA_Id])
values (#p0, #p1);
#p0 = B full with the wrong A
#p1 = 1
*/
context.SaveChanges();
}
Now last step...
Looking at the structure of the database this POCO model is
ClassBs(Id, Description, ClassA_Id : ClassAs)
ClassAs(Id, Description)
In the DB model we could have 2 different instances of ClassB that has the same ClassA instance (EF does not allow us to do this but we can do this from SQL).
After the hack using SQL you can run this test
using (Context context = new Context(connection))
{
foreach (var classB in context.Bs.ToList())
{
if (classB.ClassA == null)
continue;
Console.WriteLine("{0} {1} {2}", classB.Id, classB.ClassA.Id, classB.ClassA.ClassB.Id);
}
}
This test raises an exception
===
An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Additional information: A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
===
Can we avoid that someone from SQL does it? Yes, inserting a unique constraint on ClassA_Id field.
Related
i am a new in entity framework .. i am coding a small project for attendance , i have a base table employee derived from it a contractEmployee table and dailypaidEmployee table .
public class Employee
{
[Column(Order=1)]
public int ID { get; set; }
[Column(Order = 2)]
public string EmpName { get; set; }
[Column(Order = 3)]
public string Mobile { get; set; }
[Column(Order = 4)]
public DateTime HiringDate { get; set; }
[Column(Order = 5)]
public int DepartmentID { get; set; }
[Column(Order = 6)]
public int PositionID { get; set; }
}
public class ContractEmployee : Employee
{
[Column(Order = 7)]
public string Code { get; set; }
[Column(Order = 8)]
public string Grade { get; set; }
}
public class DailyPaidEmployee : Employee
{
[Column(Order = 9)]
public int DailyPaidAmount { get; set; }
}
public class AttendanceManagementDBContext : DbContext
{
public AttendanceManagementDBContext()
: base("name=AttendanceManagementDBContext")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>().ToTable("Employee");
modelBuilder.Entity<ContractEmployee>().ToTable("ContractEmployee");
modelBuilder.Entity<DailyPaidEmployee>().ToTable("DailyPaiedEmployee");
base.OnModelCreating(modelBuilder);
}
}
public class UnitOfWork : IUnitOfWork
{
private readonly AttendanceManagementDBContext _Context;
public UnitOfWork(AttendanceManagementDBContext Context)
{
_Context = Context;
Employees = new EmployeeRepository(Context);
}
public IEmployeeRepository Employees { get; private set; }
public int Complete()
{
return _Context.SaveChanges();
}
}
void InsertingNewContractEmployee()
{
UnitOfWork uow = new UnitOfWork(new AttendanceManagementDBContext());
ContractEmployee ce = new ContractEmployee();
ce.EmpName = txtEmpName.Text;
ce.Mobile = txtMobile.Text;
ce.HiringDate = DateTime.Parse(dtHiringDate.Value.ToShortDateString());
ce.DepartmentID = (int)cbDepartments.SelectedValue;
ce.PositionID = (int)cbPositions.SelectedValue;
ce.Code = txtCode.Text;
ce.Grade = txtGrade.Text;
uow.Employees.Add(ce);
uow.Complete();
}
void InsertingNewDailyPaidEmployee()
{
UnitOfWork uow = new UnitOfWork(new AttendanceManagementDBContext());
DailyPaidEmployee dpe = new DailyPaidEmployee();
dpe.EmpName = txtEmpName.Text;
dpe.Mobile = txtMobile.Text;
dpe.HiringDate = DateTime.Parse(dtHiringDate.Value.ToShortDateString());
dpe.DepartmentID = (int)cbDepartments.SelectedValue;
dpe.PositionID = (int)cbPositions.SelectedValue;
dpe.DailyPaidAmount = int.Parse(txtDailyPaid.Text);
uow.Employees.Add(dpe);
uow.Complete();
}
i making the addition and the update process with successful way , my problem is when i want to move a dailypaidEmployee to ContractEmployee , i don't know how to make it. i try to remove the employee from the dailypaidemployee it doesn't work , so what can i do .
You can't change types. Period.
Of course, technically you can. Even in the typed environment of C# you can. For example, you can change (sort of) an integer into a decimal. That's called conversion. The reverse, conversion from decimal to integer, however, can serve as a small demonstration of problems you may encounter when converting your types: decimals don't fit. They may be too large, or they will loose their precision (the conversion isn't lossless).
Likewise, you could convert a DailyPaidEmployee into a ContractPaidEmployee by changing its discriminator value by a simple SQL statement (not EF) and reread the entities from the database (EF). But you'll end up having a database record representing a ContractEmployee but having a DailyPaidAmount value.
Even if the application wouldn't notice that -- EF won't read the value -- it may pose unexpected problems later. Such problems always come when you're particularly not waiting for them.
Bottom line is: when entities may change "types", even when not frequently, don't use inheritance. Rather, consider the type to be a status: a DailyPaidEmployee can be promoted (not converted) into a ContractPaidEmployee, simply by flipping a status flag.
As for the details: move them to separate tables. Employee will be the stable data point. It may or may not have data in something like a DailyPayment table, or a Contract table. When an employee's status changes, you may add its first Contract, maybe remove its DailyPayment data.
Differences in behavior (usually the primary reason for using inheritance and polymorphism) can be modeled by other behavioral patterns, for instance Strategy.
I am fairly new to Entity Framework and investigating converting some legacy data access code to using EF. I want to know if the following is possible in EF and if yes how.
Say I have a Customer table like this
CustomerId | ProductId | StartDate | EndDate
--------------------------------------------
100 | 999 | 01/01/2012| null
Say I also load Product data from somewhere else (like an XML file) as a cache of product objects.
public class Customer
{
public int CustomerId {get;set;}
public int Product {get;set}
public DateTime StartDate {get;set;}
public DateTime? EndDate {get;set;}
}
public class Product
{
public int ProductId {get;set;}
public int Description {get;set}
}
Currently in CustomerDal class the method uses a StoredProc to get a Customer object like this
Customer GetCustomer(int customerId)
{
// setup connection, command, parameters for SP, loop over datareader
Customer customer = new Customer();
customer.CustomerId = rdr.GetInt32(0);
int productId = rdr.GetInt32(1);
// ProductCache is a singleton object that has been initialised before
customer.Product = ProductCache.Instance.GetProduct(productId);
customer.StartDate = rdr.GetDateTime(2);
customer.EndDate = rdr.IsDbNull(3) ? (DateTime?)null : rdr.GetDateTime(3);
return customer;
}
My question is this possible using EF when it materializes the Customer object it sets the Product property not from the DB but by another method, in this case from an in memory cache. Similary when saving a new Customer object it only gets the ProductId from the Products property and saves the value in DB.
If you attach your product instances to the EF context then when loading a Customer the Product property will be automatically filled from memory without a query to database as long as the product that is associated to the customer is already attached.
For example, starting with these entities:
public class Customer
{
public int Id { get; set; }
public int ProductId { get; set; }
public string Name { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Description { get; set; }
}
Products will be available globally, for simplicity, lets make it a static class:
public static class CachedProducts
{
public static Product[] All
{
get
{
return new Product[] { new Product { Id = 1, Description = "Foo" } };
}
}
}
With this in mind we just need to assure that every EF context starts with all the products attached to it:
public class CustomerContext : DbContext
{
public CustomerContext()
{
// Attach products to context
Array.ForEach(CachedProducts.All, p => this.Products.Attach(p));
}
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
}
And finally, to make the sample complete and runnable we seed the database, request a customer and print the associated product description:
public class DatabaseInitializer : CreateDatabaseIfNotExists<CustomerContext>
{
protected override void Seed(CustomerContext context)
{
var p = new Product { Id = 1, Description = "Foo" };
var c = new Customer { Id = 1, Product = p, Name = "John Doe" };
context.Customers.Add(c);
context.SaveChanges();
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<CustomerContext>(new DatabaseInitializer());
using (var context = new CustomerContext())
{
var customer = context.Customers.Single(c => c.Id == 1);
Console.WriteLine(customer.Product.Description);
}
}
}
If you attach a profiler to SQL Server you will notice that the customer is loaded from database but no query is performed to obtain the product since it is already attached to the context. This works when loading a customer and also when saving a new customer with an associated product.
Disclaimer: I'm not an EF expert so this approach may have some undesired side effects that I'm unable to consider.
How can I detect changes of ICollection<> properties (many-to-many relationships)?
public class Company
{
...
public virtual ICollection<Employee> Employees { get; set; }
}
using (DataContext context = new DataContext(Properties.Settings.Default.ConnectionString))
{
Company company = context.Companies.First();
company.Employees.Add(context.Employees.First());
context.SaveChanges();
}
public class DataContext : DbContext
{
public override int SaveChanges()
{
return base.SaveChanges();
// Company's entity state is "Unchanged" in this.ChangeTracker
}
}
Here is how to find all the changed many-to-many relationships. I've implemented the code as extension methods:
public static class IaExtensions
{
public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);
}
public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);
}
private static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context,
EntityState relationshipState,
Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => Tuple.Create(
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
}
Some explanation. Many-to-many relationships are represented in EF as Independent Associations, or IAs. This is because the foreign keys for the relationship are not exposed anywhere in the object model. In the database the FKs are in a join table, and this join table is hidden from the object model.
IAs are tracked in EF using "relationship entries". These are similar to the DbEntityEntry objects you get from the DbContext.Entry except that they represent a relationship between two entities rather than an entity itself. Relationship entries are not exposed in the DbContext API, so you need to drop down to ObjectContext to access them.
A new relationship entry is created when a new relationship between two entities is created, for example by adding an Employee to the Company.Employees collection. This relationship is in the Added state.
Likewise, when a relationship between two entities is removed, then the relationship entry is put into the Deleted state.
This means that to find changed many-to-many relationships (or actually any changed IA) we need to find added and deleted relationship entries. This is what the GetAddedRelationships and GetDeletedRelationships do.
Once we have relationship entries, we need to make sense of them. For this you need to know a piece of insider knowledge. The CurrentValues property of an Added (or Unchanged) relationship entry contains two values which are the EntityKey objects of the entities at either end of the relationship. Likewise, but annoyingly slightly different, the OriginalValues property of a Deleted relationship entry contains the EntityKey objects for the entities at either end of the deleted relationship.
(And, yes, this is horrible. Please don’t blame me—it is from well before my time.)
The CurrentValues/OriginalValues difference is why we pass a delegate into the GetRelationships private method.
Once we have the EntityKey objects we can use GetObjectByKey to get the actual entity instances. We return these as tuples and there you have it.
Here’s some entities, a context, and an initializer, I used to test this. (Note—testing was not extensive.)
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
public override string ToString()
{
return "Company " + Name;
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public override string ToString()
{
return "Employee " + Name;
}
}
public class DataContext : DbContext
{
static DataContext()
{
Database.SetInitializer(new DataContextInitializer());
}
public DbSet<Company> Companies { get; set; }
public DbSet<Employee> Employees { get; set; }
public override int SaveChanges()
{
foreach (var relationship in this.GetAddedRelationships())
{
Console.WriteLine(
"Relationship added between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
foreach (var relationship in this.GetDeletedRelationships())
{
Console.WriteLine(
"Relationship removed between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
return base.SaveChanges();
}
}
public class DataContextInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
var newMonics = new Company { Name = "NewMonics", Employees = new List<Employee>() };
var microsoft = new Company { Name = "Microsoft", Employees = new List<Employee>() };
var jim = new Employee { Name = "Jim" };
var arthur = new Employee { Name = "Arthur" };
var rowan = new Employee { Name = "Rowan" };
newMonics.Employees.Add(jim);
newMonics.Employees.Add(arthur);
microsoft.Employees.Add(arthur);
microsoft.Employees.Add(rowan);
context.Companies.Add(newMonics);
context.Companies.Add(microsoft);
}
}
Here’s an example of using it:
using (var context = new DataContext())
{
var microsoft = context.Companies.Single(c => c.Name == "Microsoft");
microsoft.Employees.Add(context.Employees.Single(e => e.Name == "Jim"));
var newMonics = context.Companies.Single(c => c.Name == "NewMonics");
newMonics.Employees.Remove(context.Employees.Single(e => e.Name == "Arthur"));
context.SaveChanges();
}
I cant give you the exact code for your situation, but I can tell you your situation will be simplified ten fold by having a joiner table inbetween Employees and Company just to break up the many to many relationship.
I have a table in my database called SEntries (see below the CREATE TABLE statement). It has a primary key, a couple of foreign keys and nothing special about it. I have many tables in my database similar to that one, but for some reason, this table ended up with a "Discriminator" column on the EF Proxy Class.
This is how the class is declared in C#:
public class SEntry
{
public long SEntryId { get; set; }
public long OriginatorId { get; set; }
public DateTime DatePosted { get; set; }
public string Message { get; set; }
public byte DataEntrySource { get; set; }
public string SourceLink { get; set; }
public int SourceAppId { get; set; }
public int? LocationId { get; set; }
public long? ActivityId { get; set; }
public short OriginatorObjectTypeId { get; set; }
}
public class EMData : DbContext
{
public DbSet<SEntry> SEntries { get; set; }
...
}
When I try to add a new row to that table, I get the error:
System.Data.SqlClient.SqlException: Invalid column name 'Discriminator'.
This problem only occurs if you are inheriting your C# class from another class, but SEntry is not inheriting from anything (as you can see above).
In addition to that, once I get the tool-tip on the debugger when I mouse over the EMData instance for the SEntries property, it displays:
base {System.Data.Entity.Infrastructure.DbQuery<EM.SEntry>} = {SELECT
[Extent1].[Discriminator] AS [Discriminator],
[Extent1].[SEntryId] AS [SEntryId],
[Extent1].[OriginatorId] AS [OriginatorId],
[Extent1].[DatePosted] AS [DatePosted],
[Extent1].[Message] AS [Message],
[Extent1].[DataEntrySource] AS [DataE...
Any suggestions or ideas where to get to the bottom of this issue? I tried renaming the table, the primary key and a few other things, but nothing works.
SQL-Table:
CREATE TABLE [dbo].[SEntries](
[SEntryId] [bigint] IDENTITY(1125899906842624,1) NOT NULL,
[OriginatorId] [bigint] NOT NULL,
[DatePosted] [datetime] NOT NULL,
[Message] [nvarchar](500) NOT NULL,
[DataEntrySource] [tinyint] NOT NULL,
[SourceLink] [nvarchar](100) NULL,
[SourceAppId] [int] NOT NULL,
[LocationId] [int] NULL,
[ActivityId] [bigint] NULL,
[OriginatorObjectTypeId] [smallint] NOT NULL,
CONSTRAINT [PK_SEntries] PRIMARY KEY CLUSTERED
(
[SEntryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[SEntries] WITH CHECK ADD CONSTRAINT [FK_SEntries_ObjectTypes] FOREIGN KEY([OriginatorObjectTypeId])
REFERENCES [dbo].[ObjectTypes] ([ObjectTypeId])
GO
ALTER TABLE [dbo].[SEntries] CHECK CONSTRAINT [FK_SEntries_ObjectTypes]
GO
ALTER TABLE [dbo].[SEntries] WITH CHECK ADD CONSTRAINT [FK_SEntries_SourceApps] FOREIGN KEY([SourceAppId])
REFERENCES [dbo].[SourceApps] ([SourceAppId])
GO
ALTER TABLE [dbo].[SEntries] CHECK CONSTRAINT [FK_SEntries_SourceApps]
GO
Turns out that Entity Framework will assume that any class that inherits from a POCO class that is mapped to a table on the database requires a Discriminator column, even if the derived class will not be saved to the DB.
The solution is quite simple and you just need to add [NotMapped] as an attribute of the derived class.
Example:
class Person
{
public string Name { get; set; }
}
[NotMapped]
class PersonViewModel : Person
{
public bool UpdateProfile { get; set; }
}
Now, even if you map the Person class to the Person table on the database, a "Discriminator" column will not be created because the derived class has [NotMapped].
As an additional tip, you can use [NotMapped] to properties you don't want to map to a field on the DB.
Here is the Fluent API syntax.
http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName {
get {
return this.FirstName + " " + this.LastName;
}
}
}
class PersonViewModel : Person
{
public bool UpdateProfile { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ignore a type that is not mapped to a database table
modelBuilder.Ignore<PersonViewModel>();
// ignore a property that is not mapped to a database column
modelBuilder.Entity<Person>()
.Ignore(p => p.FullName);
}
I just encountered this and my problem was caused by having two entities both with the System.ComponentModel.DataAnnotations.Schema.TableAttribute referring to the same table.
for example:
[Table("foo")]
public class foo
{
// some stuff here
}
[Table("foo")]
public class fooExtended
{
// more stuff here
}
changing the second one from foo to foo_extended fixed this for me and I'm now using Table Per Type (TPT)
I had a similar problem, not exactly the same conditions and then i saw this post. Hope it helps someone. Apparently i was using one of my EF entity models a base class for a type that was not specified as a db set in my dbcontext. To fix this issue i had to create a base class that had all the properties common to the two types and inherit from the new base class among the two types.
Example:
//Bad Flow
//class defined in dbcontext as a dbset
public class Customer{
public int Id {get; set;}
public string Name {get; set;}
}
//class not defined in dbcontext as a dbset
public class DuplicateCustomer:Customer{
public object DuplicateId {get; set;}
}
//Good/Correct flow*
//Common base class
public class CustomerBase{
public int Id {get; set;}
public string Name {get; set;}
}
//entity model referenced in dbcontext as a dbset
public class Customer: CustomerBase{
}
//entity model not referenced in dbcontext as a dbset
public class DuplicateCustomer:CustomerBase{
public object DuplicateId {get; set;}
}
Another scenario where this occurs is when you have a base class and one or more subclasses, where at least one of the subclasses introduce extra properties:
class Folder {
[key]
public string Id { get; set; }
public string Name { get; set; }
}
// Adds no props, but comes from a different view in the db to Folder:
class SomeKindOfFolder: Folder {
}
// Adds some props, but comes from a different view in the db to Folder:
class AnotherKindOfFolder: Folder {
public string FolderAttributes { get; set; }
}
If these are mapped in the DbContext like below, the "'Invalid column name 'Discriminator'" error occurs when any type based on Folder base type is accessed:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Folder>().ToTable("All_Folders");
modelBuilder.Entity<SomeKindOfFolder>().ToTable("Some_Kind_Of_Folders");
modelBuilder.Entity<AnotherKindOfFolder>().ToTable("Another_Kind_Of_Folders");
}
I found that to fix the issue, we extract the props of Folder to a base class (which is not mapped in OnModelCreating()) like so - OnModelCreating should be unchanged:
class FolderBase {
[key]
public string Id { get; set; }
public string Name { get; set; }
}
class Folder: FolderBase {
}
class SomeKindOfFolder: FolderBase {
}
class AnotherKindOfFolder: FolderBase {
public string FolderAttributes { get; set; }
}
This eliminates the issue, but I don't know why!
I get the error in another situation, and here are the problem and the solution:
I have 2 classes derived from a same base class named LevledItem:
public partial class Team : LeveledItem
{
//Everything is ok here!
}
public partial class Story : LeveledItem
{
//Everything is ok here!
}
But in their DbContext, I copied some code but forget to change one of the class name:
public class MFCTeamDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Other codes here
modelBuilder.Entity<LeveledItem>()
.Map<Team>(m => m.Requires("Type").HasValue(ItemType.Team));
}
public class ProductBacklogDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Other codes here
modelBuilder.Entity<LeveledItem>()
.Map<Team>(m => m.Requires("Type").HasValue(ItemType.Story));
}
Yes, the second Map< Team> should be Map< Story>.
And it cost me half a day to figure it out!
Old Q, but for posterity...it also also happens (.NET Core 2.1) if you have a self-referencing navigation property ("Parent" or "Children" of the same type) but the Id property name isn't what EF expects. That is, I had an "Id" property on my class called WorkflowBase, and it had an array of related child steps, which were also of type WorkflowBase, and it kept trying to associate them with a non-existent "WorkflowBaseId" (the name i suppose it prefers as a natural/conventional default). I had to explicitly configure it using HasMany(), WithOne(), and HasConstraintName() to tell it how to traverse. But I spent a few hours thinking the problem was in 'locally' mapping the object's primary key, which i attempted to fix a bunch of different ways but which was probably always working.
this error happen with me because I did the following
I changed Column name of table in database
(I did not used Update Model from database in Edmx) I Renamed manually Property name to match the change in database schema
I did some refactoring to change name of the property in the class to be the same as database schema and models in Edmx
Although all of this, I got this error
so what to do
I Deleted the model from Edmx
Right Click and Update Model from database
this will regenerate the model, and entity framework will not give you this error
hope this help you
I have a many to one association between "Project" and "Template".
Project has a property of type "Template".
The association is not bidirectional ("Template" has no knowledge of "Project").
My entity mapping for the association on "Project" is:
this.HasOptional(p => p.Template);
If I create a "Project" without specifying a template then null is correctly inserted into the "TemplateId" column of the "Projects" table.
If I specify a template then the template's Id is correctly inserted. The SQL generated:
update [Projects]
set [Description] = '' /* #0 */,
[UpdatedOn] = '2011-01-16T14:30:58.00' /* #1 */,
[ProjectTemplateId] = '5d2df249-7ac7-46f4-8e11-ad085c127e10' /* #2 */
where (([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* #3 */)
and [ProjectTemplateId] is null)
However, if I try to change the template or even set it to null, the templateId is not updated. The SQL generated:
update [Projects]
set [UpdatedOn] = '2011-01-16T14:32:14.00' /* #0 */
where ([Id] = '8c1b2d30-b83e-4229-b0c3-fed2e36bf396' /* #1 */)
As you can see, TemplateId is not updated.
This just does not make sense to me. I have even tried explicitly setting the "Template" property of "Project" to null in my code and when stepping through the code you can see it has absolutely no effect!
Thanks,
Ben
[Update]
Originally I thought this was caused by me forgetting to add the IDbSet property on my DbContext. However, now that I've tested it further I'm not so sure. Below is a complete test case:
public class PortfolioContext : DbContext, IDbContext
{
public PortfolioContext(string connectionStringName) : base(connectionStringName) { }
public IDbSet<Foo> Foos { get; set; }
public IDbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Configurations.Add(new FooMap());
modelBuilder.Configurations.Add(new BarMap());
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class {
return base.Set<TEntity>();
}
}
public class Foo {
public Guid Id { get; set; }
public string Name { get; set; }
public virtual Bar Bar { get; set; }
public Foo()
{
this.Id = Guid.NewGuid();
}
}
public class Bar
{
public Guid Id { get; set; }
public string Name { get; set; }
public Bar()
{
this.Id = Guid.NewGuid();
}
}
public class FooMap : EntityTypeConfiguration<Foo>
{
public FooMap()
{
this.ToTable("Foos");
this.HasKey(f => f.Id);
this.HasOptional(f => f.Bar);
}
}
public class BarMap : EntityTypeConfiguration<Bar>
{
public BarMap()
{
this.ToTable("Bars");
this.HasKey(b => b.Id);
}
}
And the test:
[Test]
public void Template_Test()
{
var ctx = new PortfolioContext("Portfolio");
var foo = new Foo { Name = "Foo" };
var bar = new Bar { Name = "Bar" };
foo.Bar = bar;
ctx.Set<Foo>().Add(foo);
ctx.SaveChanges();
object fooId = foo.Id;
object barId = bar.Id;
ctx.Dispose();
var ctx2 = new PortfolioContext("Portfolio");
var dbFoo = ctx2.Set<Foo>().Find(fooId);
dbFoo.Bar = null; // does not update
ctx2.SaveChanges();
}
Note that this is using SQL CE 4.
Ok, you just need to load the navigation property before doing anything to it. By loading it you essentially register it with ObjectStateManager which EF looks into to generate the update statement as a result of SaveChanges().
using (var context = new Context())
{
var dbFoo = context.Foos.Find(fooId);
((IObjectContextAdapter)context).ObjectContext.LoadProperty(dbFoo, f => f.Bar);
dbFoo.Bar = null;
context.SaveChanges();
}
This code will result in:
exec sp_executesql N'update [dbo].[Foos]
set [BarId] = null
where (([Id] = #0) and ([BarId] = #1))
',N'#0 uniqueidentifier,#1 uniqueidentifier',#0='A0B9E718-DA54-4DB0-80DA-C7C004189EF8',#1='28525F74-5108-447F-8881-EB67CCA1E97F'
If this is a bug in EF CTP5 (and not my code :p) there are two workarounds that I came up with.
1) Make the association Bi-Directional. In my case this meant adding the following to my ProjectTemplate class:
public virtual ICollection<Project> Projects {get;set;}
With this done, in order to set the "Template" property of project to null, you can just remove the project from the template - a little backward but it works:
var project = repo.GetById(id);
var template = project.Template;
template.Projects.Remove(project);
// save changes
2) The second option (which I preferred but it still smells) is to add the foreign key on your domain object. In my case I had to add the following to Project:
public Guid? TemplateId { get; set; }
public virtual ProjectTemplate Template { get; set; }
Make sure the Foreign key is a nullable type.
I then had to change my mapping like so:
this.HasOptional(p => p.Template)
.WithMany()
.HasForeignKey(p => p.TemplateId);
Then, in order to set the Template to null, I added a helper method to Project (it does actually work just by setting the foreign key to null):
public virtual void RemoveTemplate() {
this.TemplateId = null;
this.Template = null;
}
I can't say that I'm happy about polluting my domain model with foreign keys but I couldn't find any alternatives.
Hope this helps.
Ben