EntityFramework 'join' with fixed value segments in a composite key - c#

Currently for EF5, but planning to upgrade to EF6 'soon'.
With following Tables:
Note these tables are simplified to explain the issue, the real tables are obviously more complex. The database is a legacy database and I can't change the tables.
BarRed and BarBlue are different tables with a different structure, only the way they link to Foo is identical.
[Table("Foo")]
public class Foo
{
// Id+ColorCode is the primary Key on this table
[Key,Column(Order=0)]
int Id;
[Key,Column(Order=1)]
int ColorCode; // 1=red, 2=blue, 3=green, …
public virtual ICollection<BarRed> BarRed { get; set; } // when ColorCode==1, empty/null otherwise
public virtual ICollection<BarRed> BarBlue { get; set; } // when ColorCode==2, empty/null otherwise
}
[Table("BarRed")]
public class BarRed
{
[Key]
int Id; // Id is the primary key in this table.
// There is no ColorCode column in this table, essentially the ColorCode for each record in this table is the literal value 1.
[ForeignKey("Id,1")] // This doesn't work, doesn't accept the 1
public virtual Foo Foo { get; set; }
}
[Table("BarBlue")]
public class BarBlue
{
[Key]
int Id; // Id is the primary key in this table.
// There is no ColorCode column in this table, essentially the ColorCode for each record in this table is the literal value 2.
[ForeignKey("Id,2")] // This doesn't work, doesn't accept the 2
public virtual Foo Foo { get; set; }
}
So in short, how do I get EntityFramework to link 2 tables in a One-To-Many relation when the 'Many' side table doesn't have an explicit column for one of the key segments, but a fixed literal value should be used on all rows.
I also have a similar issue/problem in the other direction, a One-To-Many where the 'One' side doesn't have an explicit column on each record but a literal value should be used.
I can join the tables from SQL and this does work and uses the indexes properly, I need this for EF to make the database accessible from OData.

Related

Entity Framework creates non-existing column in query

I face the problem that EF creates a column in the query that does not exist in the Oracle database table.
The simplified model which is created by EF looks like this (I use DB first approach):
public partial class USER
{
public int ID { get; set; }
public string NAME { get; set; }
public int PROCESS_ID { get; set; }
public virtual PROCESS PROCESS { get; set; }
}
public partial class PROCESS
{
public PROCESS()
{
this.USER = new HashSet<User>();
}
public int ID { get; set; }
public virtual ICollection<USER> USER { get; set; }
}
I set up the foreign key constraint in the oracle sql developer.
When I try to get the Users for a selected Process like this:
var users = context.Users.Where(u => u.PROCESS_ID == 0);
It produces following error:
ORA-00904: "Extent1"."R1": invalid ID
So i took a look on the produced SQL:
SELECT
"Extent1".ID,
"Extent1".NAME,
"Extent1".R1,
FROM DB.USER "Extent1"
WHERE "Extent1".R1 = :p__linq__0
Of course this produces an error because R1 isn't a column in the table. But I can't figure out where it comes from. It seems like EF can't map the foreign key properly thats why it's also missing in the generated SQL query?
Maybe someone has a tip for me :)
To follow up my comment, here is a link to the conventions.
The convention for a foreign key is that it must have the same data type as the principal entity's primary key property and the name must follow one of these patterns:
[navigation property name][principal primary key property name]Id
[principal class name][primary key property name]Id
[principal primary key property name]Id
Your convention [navigation property name]_ID isn't on the list.
Encountered the same error recently while working with Oracle using DevArt provider. Turned out it was caused by a column name being longer than 30 chars. OP mentioned that the model posted in his question is a simplified one so it still may be the case.

Ef6, Many to many, Inserts and Primary keys

I have a problem with populating the right values in a junction table for a many to many relationship. In the image below I have simplified what I am trying to do. The "Table on the left" has values in the database that I want to use. The table of the right is about to receive new records. It has a navigation property to the Junction table and the same is true for the table on the left. The junction table has navigation properties to both tables on its rear and they are set to required.
When I create the new records in the table on the right, I also add to it records in the junction table. The TOL_ID is known as it is saved in the database, but the TOR_ID is about to be created, therefore unknown. When I attempt to call the SaveChanges in my context, it tries to save the junction table records first, before the TOR_IDs have been populated for the record on the right. I though that marking the navigation property as Required would make the EF understand that TOR_ID must exist before creating the junction table row. Instead it tries to insert an existing TOL_ID and 0, which gives a violation when trying to insert many table on the right records connected to the same TOL_ID.
Note: Saving the TOR_IDs first and then connecting with junction records is not an option, as the creation of the junction table records are part of a "Slowly changing dimension" type 6 flow.
This is how it really looks like in the code:
// The newRating is the new object corresponding the Table on the right
var newRating = new ModuleRating()
{
// The moduleRating.RatedDriveUnit already exists in the db
RatedDriveUnit = moduleRating.RatedDriveUnit
};
newModule.Ratings.Add(newRating);
If you follow the Code First below classes will helpful
public class TOL
{
[Key]
public int TOL_ID { get; set; }
public int Col1 { get; set; }
public ICollection<TOR> Tors { get; set; }
}
public class TOR
{
[Key]
public int TOR_ID { get; set; }
public int Col1 { get; set; }
public ICollection<TOL> Tols { get; set; }
}
public class TolTorContext : DbContext
{
public DbSet<TOL> Tols { get; set; }
public DbSet<TOR> Tors { get; set; }
}
If you follow database first approach,make FK at Join Table and try to chhange id names TOLId , TORId
I'am sorry for spending your time. After some investigation I noticed that the supposed composite unique index (TOL_ID and TOR_ID) of the junction table had been set wrong. Instead two separate unique indexes had been applied to TOL_ID and TOR_ID, which resulted a constrain violation as soon as one of those two values appeared twice.

EF: Inserting record to M:N table fails dues to duplicate key value

I have two models Film and Rating that are reflected in database as tables with same name, both of them have Guid Id and are in relation to each other via FilmRating table (without model).
To generate FilmRating table, I have in each model list that references the other, eg. List<Film> RatedFilms and List<Rating> Ratings.
The problem originates when I try to add record to Rating table, that would also add record to FilmRating table. I have following code:
public void AddNewRating(Rating newRating, Film ratedFilm)
{
// Add rated film to the rating collection
if(newRating.RatedFilms == null)
{
newRating.RatedFilms = new List<Film>();
}
newRating.RatedFilms.Add(ratedFilm);
using (var context = new IW5Context())
{
context.Ratings.Add(newRating);
context.SaveChanges();
}
}
ratedFilm is object retrieved from database that represents film to which the rating should be related. IW5Context is simply class inheriting DbContext.
It says:
{"Violation of PRIMARY KEY constraint 'PK_dbo.Film'. Cannot insert duplicate key in object 'dbo.Film'. The duplicate key value is (5fd7ab30-b681-46d5-b34b-64afe0d1cfcb).\r\nThe statement has been terminated."}
From what I understand it tries to insert film to database, while I would expect it only to add records to Rating table and FilmRating table (and maybe check that film exists in Film table).
What should I do in order to get it to work?
Edit - adding Film & Rating models:
public class Rating : BaseObject
{
public double RatingValue { get; set; }
public string TextRating { get; set; }
public DateTime TimeAdded;
public List<Film> RatedFilms { get; set; }
}
public class Film : BaseObject
{
...
public List<Rating> Ratings
{
get; set;
}
}
Id column is type of Guid and is inherited (together with string Name) from BaseObject.
AddNewRating is called from another project (but gets called) via method that is called from ICommand implementation method.
If you are sure that the passed Film is an existing entity, then you should use DbSet<TEntity>.Attach method to let EF not attempt to insert it into the database:
using (var context = new IW5Context())
{
context.Films.Attach(ratedFilm);
context.Ratings.Add(newRating);
context.SaveChanges();
}

DBContext throwing errors attaching unique objects, with nullable foreign keys

Most posts around the ObjectStateManager are true-duplicate issues based on unique primary keys. My problem is that my table does Not have a primary key, but it does have multiple foreign keys, one of which is Nullable.
class MyObject
{
int Key1;
int? Key2;
}
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = null; });
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; }); ****
It blows up on the second call, even though this is a unique row in the database.
Any thoughts on how to get around this? or enforce checking of BOTH keys?
As #BenAaronson mentioned, you should have a surrogate, primary key in your table in this instance. Entity Framework quite simply cannot deal with entities that have no primary key defined—in fact, I'm surprised your code even compiled/ran. Perhaps your real code with real class and property names caused EF to infer a primary key using its default conventions. For example:
public class MyClass
{
public int MyClassId { get; set; }
public int MyOtherClassId { get; set; }
}
In the code above, even without explicitly declaring it, EF would assume that the MyClassId property is the primary key for the class MyClass, even if that may not have been your intention.
If EF can't infer a primary key and one is not explicitly provided, then your code wouldn't compile (or at most, it wouldn't run).
So looking at your code, what appears to be happening is that EF inferred a primary key somehow (in your example above, Key1). You then tried to attach a new object to your context:
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = null; });
This results in the context adding a new MyObject instance whose primary key value is 100 and whose Key2 property is null.
Next, you attempt to attach another item to the context:
context.MyTable.Attach(new MyObject() { Key1 = 100; Key2 = 2000; });
What this does is attempt to add a new item to the context whose primary key is 100, and this fails. This is because you already have an object being tracked by the context whose primary key value is 100 (executed by the first statement above).
Since you need to allow possibly null values for the Key2 property, you can't use a composite primary key, as you already stated. So you will need to follow #BenAaronson's advice and add a surrogate primary key:
public class Object
{
// Alternatively, you can use a mapping class to define the primary key
// I just wanted to make the example clear that this is the
// surrogate primary key property.
[Key]
private int ObjectID { get; set; } // IIRC, you can make this private...
public int Key1 { get; set; }
public int Key2 { get; set; }
}
Now, you can do the following:
context.MyTable.Add(new MyObject() { Key1 = 100, Key2 = null; });
context.MyTable.Add(new MyObject() { Key1 = 100, Key2 = 2000; });
Notice I used the Add method and not Attach. That's because when using Attach, the context is assuming that you're adding an object to the context which already exists in the database, but which was not brought into the context via a query; instead, you had a representation of it in memory, and at this point, you want the context to start tracking changes made to it and update the object in the database when you call context.SaveChanges(). When using the Attach property, the context adds the object in the Unmodified state. That's not what we want. We have brand new objects being added to the context. So we use Add. This tells the context to add the item in the Added state. You can make any changes you want to it. Since it's a new item, it will be in the Added state until you call context.SaveChanges() and the item is persisted to your data store, at which time, it's state will be updated to Unmodified.
One more thing to note at this point. If this is a "many-to-many" table, you should never need to manually add rows to this type of join table in EF (there are some caveats to this statement, see below). Instead, you should setup a mapping between the two objects whose relationship is many-to-many. It's possible to specify an optional many-to-many relationship, too. If the first object has no relationship to the second, there should be no row in the join table for the first object, and vice versa.
Regarding join table caveats as alluded to above: if your join-tables (i.e. many-to-many mapping tables) are simple (meaning the only columns in the table are those columns mapping one ID to the related ID), then you won't even see the join-table as part of your object model. This table is managed by EF in the background through navigation properties on the related objects. However, if the join-table contains properties other than just the ID properties of the related objects (and, this implies you have an existing database or explicitly structured your object model this way), then you will have an intermediate entity reference. For example:
public class A
{
public int ID { get; set; }
}
public class B
{
public int ID { get; set; }
}
public class AToB
{
// Composite primary key
[Key]
public int IdA { get; set; }
[Key]
public int IdB { get; set; }
public A SideA { get; set; }
public B SideB { get; set; }
// An additional property in the many-to-many join table
public DateTime Created { get; set; }
}
You would also have some mappings to tell EF how to wire up the foreign key relationships. What you'd wind up with in your object model then, is the following:
myA.AToB.SideB // Accesses the related B item to this A item.
myA.AToB.Created // Accesses the created property of AToB, telling you
// when the relationship between A and B was created.
In fact, if you have non-trivial join tables such as this example, EF will always include them in your object model when generating its model from an existing database.
I would strongly suggest that you check out Julie Lerman's and Rowan Miller's books on programming Entity Framework.

Dapper insert into table that has a composite PK

I have a table that has a primary key composed of two columns, neither of which are auto-incrementing, and my Dapper insert (part of Dapper Extensions) is failing on the insert saying that the first of the two columns does not allow a null, even tho the value I'm passing in is not null.
Table Student:
StudentId (PK, not null) \_ (combined to form primary key)
StudentName (PK, not null) /
Active -- just another column
C#:
public class Student {
public int StudentId { get; set; }
public string StudentName { get; set; }
public bool Active { get; set; }
}
var newStudent = new Student { StudentId = 5, StudentName = "Joe", Active = true };
var insertSuccess = myConn.Insert<Student>(newStudent);
Error:
Cannot insert the value NULL into column 'StudentId', table 'dbo.Student'; column does not allow nulls. INSERT fails.
Dapper is for some reason not getting the StudentId with a value of 5. Do I have to do something special for tables that have combined PK's, or with tables that have PK's that are not auto-incrementing? Thanks.
Adding an AutoClassMapper will change the behavior for all classes. If you wish to handle just this one class you can create a Map for just this class.
public class StudentClassMapper : ClassMapper<Student>
{
public StudentClassMapper()
{
Map(x => x.StudentId).Key(KeyType.Assigned);
Map(x => x.StudentName).Key(KeyType.Assigned);
AutoMap(); // <-- Maps the unmapped columns
}
}
Dapper.Contrib offers an annotation to solve this problem.
public class Student {
[ExplicitKey]
public int StudentId { get; set; }
[ExplicitKey]
public string StudentName { get; set; }
public bool Active { get; set; }
}
ExplicitKey means it is a key field whose value you must specify; it is not auto-generated by the database.
I'm assuming when you said "Dapper Extensions," you were referring to a different extension library. You may find you can easily switch over to Dapper.Contrib.
I'm not sure this is the problem, but AFAIK Dapper Extensions doesn't support composite primary keys by default.
You will probably have to code your own AutoClassMapper: https://github.com/tmsmith/Dapper-Extensions/wiki/AutoClassMapper
The default AutoClassMapper makes certain assumptions about your database schema and POCOs:
AutoClassMapper assumes that your table names are singular (Ex: Car table name and Car POCO name).
Each POCO has at least one property named Id or ends with Id.
If multiple properties end with Id, Dapper Extensions will use the first Id property as the primary key.
If the Id property is determined to be an Integer, the KeyType will be set to Identity.
If the Id property is determined to be a Guid, the KeyType will be set to Guid.
If the id property is anything other than an Integer our Guid, the KeyType will be set to Assigned.

Categories

Resources